diff --git a/flows.yaml b/flows.yaml index 14d768d38..64ba5c5a6 100644 --- a/flows.yaml +++ b/flows.yaml @@ -454,6 +454,7 @@ integrations: output: Task delete-task: input: Id + description: Delete a task. endpoint: method: DELETE path: /tasks @@ -1741,6 +1742,172 @@ integrations: id: string BamboohrResponseStatus: status: string + basecamp: + actions: + fetch-accounts: + description: Fetch account list and user information from Basecamp + endpoint: + method: GET + path: /accounts + group: Accounts + output: UserInformation + fetch-projects: + description: Fetch all projects from Basecamp + endpoint: + method: GET + path: /projects + group: Projects + output: BasecampProjectsResponse + create-todo: + description: Create a new to-do in a specific project + list + endpoint: + method: POST + path: /todos + group: Todos + input: BasecampCreateTodoInput + output: BasecampTodoResponse + syncs: + todos: + runs: every 1 day + description: > + Syncs to-dos from Basecamp for the specified projects. Example of a + metadata input Example: `{ projects: [ { projectId: 1234, todoSetId: + 9999 }, ... ] }` + sync_type: full + track_deletes: true + input: TodosMetadata + auto_start: false + output: BasecampTodo + endpoint: + method: GET + path: /todos + group: Todos + models: + UserInformation: + identity: + id: number + firstName: string + lastName: string + email: string + accounts: Account[] + Account: + id: number + name: string + product: string + href: string + app_href: string + hidden?: boolean + BasecampProjectsResponse: + projects: BasecampProject[] + BasecampCreateTodoInput: + projectId: number + todoListId: number + content: string + description?: string + due_on?: string + starts_on?: string + notify?: boolean + assigneeEmails?: string[] + completionSubscriberEmails?: string[] + BasecampTodoResponse: + id: number + status: string + visible_to_clients: boolean + created_at: string + updated_at: string + title: string + inherits_status: boolean + type: string + url: string + app_url: string + bookmark_url: string + subscription_url: string + comments_count: integer + comments_url: string + position: integer + parent: BasecampTodoParent + bucket: BasecampTodoBucket + creator: any + description: string + completed: boolean + content: string + starts_on: string + due_on: string + assignees: any[] + completion_subscribers: any[] + completion_url: string + BasecampProject: + id: number + status: string + created_at: string + updated_at: string + name: string + description: string | null + purpose: string + clients_enabled: boolean + timesheet_enabled?: boolean + color?: string | null + last_needle_color?: string | null + last_needle_position?: string | null + previous_needle_position?: string | null + bookmark_url: string + url: string + app_url: string + dock: DockItem[] + bookmarked: boolean + BasecampPerson: + id: number + name: string + email_address: string + avatar_url?: string + admin?: boolean + owner?: boolean + created_at?: string + updated_at?: string + attachable_sgid?: string + personable_type?: string + title?: string | null + bio?: string | null + location?: string | null + BasecampTodo: + id: number + content: string + description?: string | undefined + due_on?: string | undefined + completed: boolean + created_at: string + updated_at: string + bucket_id: number + assignees?: BasecampPerson[] | undefined + BasecampTodoParent: + id: number + title: string + type: string + url: string + app_url: string + BasecampTodoBucket: + id: number + name: string + type: string + BasecampCompany: + id: number + name: string + DockItem: + id: number + title: string + name: string + enabled: boolean + position: number | null + url: string + app_url: string + ClientSide: + url: string + app_url: string + TodosMetadata: + projects: Project[] + Project: + projectId: number + todoSetId: number bill: actions: create-user: @@ -2212,7 +2379,7 @@ integrations: created_at: string updated_at: string event_memberships: EventMembership[] - event_guests: EventGuest[] + event_guests?: EventGuest[] calendar_event: CalendarEvent | null cancellation?: EventCancellation CalendarEvent: @@ -2358,6 +2525,7 @@ integrations: path: /candidates output: Candidate input: CreateCandidate + description: Create a candidate syncs: background-checks: endpoint: @@ -4038,6 +4206,7 @@ integrations: google: syncs: workspace-org-units: + description: Sync all workspace org units runs: every 6 hours track_deletes: true output: OrganizationalUnit @@ -4049,6 +4218,7 @@ integrations: - https://www.googleapis.com/auth/admin.directory.orgunit.readonly - https://www.googleapis.com/auth/admin.directory.user.readonly workspace-users: + description: Sync all workspace users runs: every hour input: Metadata auto_start: false @@ -4058,6 +4228,7 @@ integrations: method: GET path: /google/workspace-users workspace-user-access-tokens: + description: Sync all workspace users access tokens runs: every hour output: GoogleWorkspaceUserToken sync_type: full @@ -4283,6 +4454,39 @@ integrations: iconLink: string fileId: string eventType: string + google-docs: + actions: + fetch-document: + input: DocumentId + description: | + Fetches the content of a document given its ID. + output: Document + endpoint: + method: GET + path: /fetch-document + scopes: https://www.googleapis.com/auth/documents.readonly + models: + DocumentId: + id: string + Document: + documentId: string + title: string + url: string + tabs: object[] + revisionId: string + suggestionsViewMode: "DEFAULT_FOR_CURRENT_ACCESS | SUGGESTIONS_INLINE | PREVIEW_SUGGESTIONS_ACCEPTED\t| PREVIEW_WITHOUT_SUGGESTIONS" + body: object + headers: object + footers: object + footnotes: object + documentStyle: object + suggestedDocumentStyleChanges: object + namedStyles: object + suggestedNamedStylesChanges: object + lists: object + namedRanges: object + inlineObjects: object + positionedObjects: object google-drive: syncs: documents: @@ -4417,6 +4621,30 @@ integrations: DocumentInput: threadId: string attachmentId: string + google-sheet: + actions: + fetch-spreadsheet: + input: SpreadsheetId + description: | + Fetches the content of a spreadsheet given its ID. + output: Spreadsheet + version: 1.0.0 + endpoint: + method: GET + path: /spreadsheet + scopes: https://www.googleapis.com/auth/spreadsheets.readonly + models: + SpreadsheetId: + id: string + Spreadsheet: + spreadsheetId: string + properties: object + sheets: object[] + namedRanges: object[] + spreadsheetUrl: string + developerMetadata: object[] + dataSources: object[] + dataSourceSchedules: object[] gorgias: syncs: tickets: @@ -4433,6 +4661,19 @@ integrations: path: /tickets group: Tickets version: 1.0.1 + users: + description: | + Fetches the list of users + endpoint: + method: GET + path: /users + group: Users + sync_type: full + track_deletes: true + runs: every 6 hours + output: GorgiasUser + scopes: + - users:read actions: create-ticket: description: | @@ -4448,7 +4689,35 @@ integrations: path: /ticket group: Tickets output: Ticket + create-user: + description: > + Creates a new user with a role in Gorgias. Defaults to agent if a role + is not provided + input: GorgiasCreateUser + output: GorgiasUser + endpoint: + method: POST + path: /user + group: Users + scopes: + - users:write + delete-user: + description: Deletes a user in Gorgias + output: SuccessResponse + input: IdEntity + endpoint: + method: DELETE + path: /users + group: Users + scopes: + - users:write models: + IdEntity: + id: string + SuccessResponse: + success: boolean + ActionResponseError: + message: string Ticket: id: number assignee_user: AssigneeUser | null @@ -4580,6 +4849,20 @@ integrations: body_html: string body_text: string id: string + CreateUser: + firstName: string + lastName: string + email: string + GorgiasCreateUser: + firstName: string + lastName: string + email: string + role: admin | agent | basic-agent | lite-agent | observer-agent + GorgiasUser: + id: string + firstName: string + lastName: string + email: string greenhouse-basic: syncs: applications: @@ -6062,6 +6345,15 @@ integrations: path: /contact output: SuccessResponse input: IdEntity + whoami: + description: Fetch current user information + output: UserInformation + endpoint: + method: GET + path: /whoami + group: Users + scopes: + - Read admins syncs: conversations: runs: every 6 hours @@ -6235,6 +6527,9 @@ integrations: last_seen_at?: number owner_id?: string unsubscribed_from_emails?: boolean + UserInformation: + id: string + email: string jira: syncs: issues: @@ -10027,7 +10322,55 @@ integrations: path: /contents/single group: Contents version: 1.0.0 + create-database-row: + description: > + Create a new row in a specified Notion database. + + The properties are mapped to Notion-compatible formats based on the + database schema. + + Supported property types include: + + - `title` (string): Creates a title property. + + - `select` (string): Creates a select property. + + - `multi_select` (array of strings): Creates a multi-select property. + + - `status` (string): Creates a status property. + + - `date` (string or object): Supports ISO date strings or objects with + a `start` field. + + - `checkbox` (boolean): Creates a checkbox property. + + - `number` (number): Creates a number property. + + - `url` (string): Creates a URL property. + + - `email` (string): Creates an email property. + + - `phone_number` (string): Creates a phone number property. + + - `rich_text` (string): Creates a rich text property. + + - `relation` (array of IDs): Creates a relation property. + output: CreateDatabaseRowOutput + endpoint: + method: POST + path: /databases/row + group: Databases + input: CreateDatabaseRowInput + version: 1.0.0 models: + SuccessResponse: + success: boolean + CreateDatabaseRowOutput: + success: boolean + addedProperties: Property[] + Property: + propertyKey: string + notionValue: any RichPageInput: pageId: string ContentMetadata: @@ -10048,6 +10391,9 @@ integrations: parent_id?: string | undefined DatabaseInput: databaseId: string + CreateDatabaseRowInput: + databaseId: string + properties: object RowEntry: id: string row: @@ -11372,6 +11718,7 @@ integrations: Updates: id: string sync_token: string + active?: boolean BaseInvoice: created_at: string updated_at: string @@ -11479,6 +11826,7 @@ integrations: UpdateCustomer: id: string sync_token: string + active?: boolean display_name?: string suffix?: string title?: string @@ -11503,6 +11851,7 @@ integrations: UpdateItem: id: string sync_token: string + active?: boolean track_qty_onHand?: boolean qty_on_hand?: number name: string @@ -11522,6 +11871,7 @@ integrations: UpdateAccount: id: string sync_token: string + active?: boolean name: string account_type?: string account_sub_type?: string @@ -11545,6 +11895,7 @@ integrations: UpdateInvoice: id: string sync_token: string + active?: boolean customer_ref?: Reference line?: Line[] due_date?: string @@ -11559,6 +11910,7 @@ integrations: UpdateCreditMemo: id: string sync_token: string + active?: boolean customer_ref?: Reference line?: Line[] due_date?: string @@ -11693,6 +12045,8 @@ integrations: detail_type?: string deposit_account_id?: string | undefined deposit_account_name?: string | undefined + DeleteResponse: + id: string quickbooks-sandbox: syncs: accounts: @@ -11948,6 +12302,7 @@ integrations: Updates: id: string sync_token: string + active?: boolean BaseInvoice: created_at: string updated_at: string @@ -12055,6 +12410,7 @@ integrations: UpdateCustomer: id: string sync_token: string + active?: boolean display_name?: string suffix?: string title?: string @@ -12079,6 +12435,7 @@ integrations: UpdateItem: id: string sync_token: string + active?: boolean track_qty_onHand?: boolean qty_on_hand?: number name: string @@ -12098,6 +12455,7 @@ integrations: UpdateAccount: id: string sync_token: string + active?: boolean name: string account_type?: string account_sub_type?: string @@ -12121,6 +12479,7 @@ integrations: UpdateInvoice: id: string sync_token: string + active?: boolean customer_ref?: Reference line?: Line[] due_date?: string @@ -12135,6 +12494,7 @@ integrations: UpdateCreditMemo: id: string sync_token: string + active?: boolean customer_ref?: Reference line?: Line[] due_date?: string @@ -12269,6 +12629,8 @@ integrations: detail_type?: string deposit_account_id?: string | undefined deposit_account_name?: string | undefined + DeleteResponse: + id: string ramp: syncs: users: @@ -12385,6 +12747,98 @@ integrations: directManagerId?: string idempotencyKey?: string locationId?: string + recharge: + actions: + upsert-customers: + endpoint: + method: POST + path: /customers + group: Customers + description: Upsert a customer in Recharge + input: UpsertRechargeCustomerInput + output: UpsertRechargeCustomerOutput + scopes: read_customers, write_customers, write_payment_methods + syncs: + customers: + sync_type: incremental + endpoint: + method: GET + path: /customers + group: Customers + description: >- + Incrementally fetch all Recharge customers and their subscription + details. + runs: every 1 hour + output: Customer + scopes: read_customers, read_subscriptions + version: 1.0.1 + models: + Customer: + id: string + phone_number: string | null + first_name: string | null + last_name: string | null + subscriptions: + - id: string + type: string + name: string + start_date: string + end_date: string | null + next_charge_scheduled_at: string | null + UpsertRechargeCustomerInput: + email: string + external_customer_id?: ExternalCustomerId | undefined + first_name: string + last_name: string + phone?: string | undefined + tax_exempt?: boolean | undefined + UpsertRechargeCustomerOutput: + action: update | create + response: + accepts_marketing: number | null + analytics_data: + utm_params: + - utm_campaign?: string | undefined + utm_content?: string | undefined + utm_data_source?: string | undefined + utm_source?: string | undefined + utm_medium?: string | undefined + utm_term?: string | undefined + utm_timestamp?: string | undefined + billing_address1: string | null + billing_address2: string | null + billing_city: string | null + billing_company: string | null + billing_country: string | null + billing_phone: string | null + billing_province: string | null + billing_zip: string | null + created_at: string + email: string + first_charge_processed_at: string | null + first_name: string + has_card_error_in_dunning: boolean + has_valid_payment_method: boolean + hash: string + id: number + last_name: string + number_active_subscriptions: number + number_subscriptions: number + phone: string | null + processor_type: string | null + reason_payment_method_not_valid: string | null + shopify_customer_id: string | null + status: string + tax_exempt: boolean + updated_at: string + apply_credit_to_next_recurring_charge?: boolean | undefined + external_customer_id?: ExternalCustomerId | undefined + has_payment_method_in_dunning?: boolean | undefined + subscriptions_active_count?: number | undefined + subscriptions_total_count?: number | undefined + subscription_related_charge_streak?: number | undefined + ExternalCustomerId: + ecommerce: string ring-central: actions: create-user: diff --git a/integrations/asana/actions/delete-task.md b/integrations/asana/actions/delete-task.md index afccf5a2b..a78cfee62 100644 --- a/integrations/asana/actions/delete-task.md +++ b/integrations/asana/actions/delete-task.md @@ -3,7 +3,7 @@ ## General Information -- **Description:** +- **Description:** Delete a task. - **Version:** 0.0.1 - **Group:** Tasks - **Scopes:** _None_ diff --git a/integrations/asana/nango.yaml b/integrations/asana/nango.yaml index 35b1af5b1..87ff39944 100644 --- a/integrations/asana/nango.yaml +++ b/integrations/asana/nango.yaml @@ -71,6 +71,7 @@ integrations: output: Task delete-task: input: Id + description: Delete a task. endpoint: method: DELETE path: /tasks diff --git a/integrations/avalara/syncs/transactions.md b/integrations/avalara/syncs/transactions.md index e9543f714..12c917ac3 100644 --- a/integrations/avalara/syncs/transactions.md +++ b/integrations/avalara/syncs/transactions.md @@ -203,6 +203,15 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "company": "", + "backfillPeriodMs?": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/avalara/syncs/transactions.ts) diff --git a/integrations/basecamp/actions/create-todo.md b/integrations/basecamp/actions/create-todo.md new file mode 100644 index 000000000..99c6922d3 --- /dev/null +++ b/integrations/basecamp/actions/create-todo.md @@ -0,0 +1,97 @@ + +# Create Todo + +## General Information + +- **Description:** Create a new to-do in a specific project + list +- **Version:** 0.0.1 +- **Group:** Todos +- **Scopes:** _None_ +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/basecamp/actions/create-todo.ts) + + +## Endpoint Reference + +### Request Endpoint + +`POST /todos` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +```json +{ + "projectId": "", + "todoListId": "", + "content": "", + "description?": "", + "due_on?": "", + "starts_on?": "", + "notify?": "", + "assigneeEmails?": [ + "" + ], + "completionSubscriberEmails?": [ + "" + ] +} +``` + +### Request Response + +```json +{ + "id": "", + "status": "", + "visible_to_clients": "", + "created_at": "", + "updated_at": "", + "title": "", + "inherits_status": "", + "type": "", + "url": "", + "app_url": "", + "bookmark_url": "", + "subscription_url": "", + "comments_count": "", + "comments_url": "", + "position": "", + "parent": { + "id": "", + "title": "", + "type": "", + "url": "", + "app_url": "" + }, + "bucket": { + "id": "", + "name": "", + "type": "" + }, + "creator": "", + "description": "", + "completed": "", + "content": "", + "starts_on": "", + "due_on": "", + "assignees": [ + "" + ], + "completion_subscribers": [ + "" + ], + "completion_url": "" +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/basecamp/actions/create-todo.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/basecamp/actions/create-todo.md) + + + diff --git a/integrations/basecamp/actions/create-todo.ts b/integrations/basecamp/actions/create-todo.ts new file mode 100644 index 000000000..df87492ab --- /dev/null +++ b/integrations/basecamp/actions/create-todo.ts @@ -0,0 +1,75 @@ +import type { BasecampPerson, BasecampCreateTodoInput, NangoAction, ProxyConfiguration } from '../../models'; +import { findUserIdByEmail } from '../helpers/find-user.js'; +import { basecampCreateTodoInputSchema } from '../schema.zod.js'; +/** + * Action: createBasecampTodo + * + * 1) Parse input (which includes email arrays). + * 2) Fetch the project's people (GET /projects/{projectId}/people.json). + * 3) Match each email to a person, build 'assignee_ids' + 'completion_subscriber_ids'. + * 4) POST /buckets/{projectId}/todolists/{todoListId}/todos.json to create the to-do. + */ +export default async function runAction(nango: NangoAction, input: BasecampCreateTodoInput) { + // 1) Validate input + const parsed = basecampCreateTodoInputSchema.safeParse(input); + if (!parsed.success) { + const msg = parsed.error.errors.map((e) => e.message).join('; '); + throw new nango.ActionError({ message: `Invalid Basecamp create-todo input: ${msg}` }); + } + + const { projectId, todoListId, content, description, due_on, starts_on, notify, assigneeEmails, completionSubscriberEmails } = parsed.data; + + // 2) Fetch the project’s people by email -> user ID + const peopleConfig: ProxyConfiguration = { + // https://github.com/basecamp/bc3-api/blob/master/sections/people.md#get-people-on-a-project + endpoint: `/projects/${projectId}/people.json`, + retries: 10 + }; + const peopleResp = await nango.get(peopleConfig); + const projectPeople = Array.isArray(peopleResp.data) ? peopleResp.data : []; + + const assigneeIds: number[] = []; + const completionSubscriberIds: number[] = []; + + if (assigneeEmails) { + for (const email of assigneeEmails) { + const userId = findUserIdByEmail(email, projectPeople); + if (userId) { + assigneeIds.push(userId); + } else { + throw new nango.ActionError({ message: `No user found with email: ${email}` }); + } + } + } + + if (completionSubscriberEmails) { + for (const email of completionSubscriberEmails) { + const userId = findUserIdByEmail(email, projectPeople); + if (userId) { + completionSubscriberIds.push(userId); + } else { + throw new nango.ActionError({ message: `No user found with email: ${email}` }); + } + } + } + + const dataBody: Record = { + content + }; + if (description) dataBody['description'] = description; + if (due_on) dataBody['due_on'] = due_on; + if (starts_on) dataBody['starts_on'] = starts_on; + if (typeof notify === 'boolean') dataBody['notify'] = notify; + if (assigneeIds.length > 0) dataBody['assignee_ids'] = assigneeIds; + if (completionSubscriberIds.length > 0) dataBody['completion_subscriber_ids'] = completionSubscriberIds; + + const config: ProxyConfiguration = { + // https://github.com/basecamp/bc3-api/blob/master/sections/todos.md#create-a-to-do + endpoint: `/buckets/${projectId}/todolists/${todoListId}/todos.json`, + data: dataBody, + retries: 10 + }; + + const response = await nango.post(config); + return response.data; +} diff --git a/integrations/basecamp/actions/fetch-accounts.md b/integrations/basecamp/actions/fetch-accounts.md new file mode 100644 index 000000000..d230c03ed --- /dev/null +++ b/integrations/basecamp/actions/fetch-accounts.md @@ -0,0 +1,57 @@ + +# Fetch Accounts + +## General Information + +- **Description:** Fetch account list and user information from Basecamp +- **Version:** 0.0.1 +- **Group:** Accounts +- **Scopes:** _None_ +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/basecamp/actions/fetch-accounts.ts) + + +## Endpoint Reference + +### Request Endpoint + +`GET /accounts` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +_No request body_ + +### Request Response + +```json +{ + "identity": { + "id": "", + "firstName": "", + "lastName": "", + "email": "" + }, + "accounts": [ + { + "id": "", + "name": "", + "product": "", + "href": "", + "app_href": "", + "hidden?": "" + } + ] +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/basecamp/actions/fetch-accounts.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/basecamp/actions/fetch-accounts.md) + + + diff --git a/integrations/basecamp/actions/fetch-accounts.ts b/integrations/basecamp/actions/fetch-accounts.ts new file mode 100644 index 000000000..085565984 --- /dev/null +++ b/integrations/basecamp/actions/fetch-accounts.ts @@ -0,0 +1,24 @@ +import type { UserInformation, NangoAction, ProxyConfiguration } from '../../models'; +import type { BasecampAuthorizationResponse } from '../types'; + +export default async function runAction(nango: NangoAction): Promise { + const config: ProxyConfiguration = { + baseUrlOverride: 'https://launchpad.37signals.com', + // https://github.com/basecamp/api/blob/master/sections/authentication.md#get-authorization + endpoint: '/authorization.json', + retries: 10 + }; + + const { data } = await nango.get(config); + const { identity, accounts } = data; + + return { + identity: { + id: identity.id, + email: identity.email_address, + firstName: identity.first_name, + lastName: identity.last_name + }, + accounts + }; +} diff --git a/integrations/basecamp/actions/fetch-projects.md b/integrations/basecamp/actions/fetch-projects.md new file mode 100644 index 000000000..a1bd9b1dc --- /dev/null +++ b/integrations/basecamp/actions/fetch-projects.md @@ -0,0 +1,73 @@ + +# Fetch Projects + +## General Information + +- **Description:** Fetch all projects from Basecamp +- **Version:** 0.0.1 +- **Group:** Projects +- **Scopes:** _None_ +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/basecamp/actions/fetch-projects.ts) + + +## Endpoint Reference + +### Request Endpoint + +`GET /projects` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +_No request body_ + +### Request Response + +```json +{ + "projects": [ + { + "id": "", + "status": "", + "created_at": "", + "updated_at": "", + "name": "", + "description": "", + "purpose": "", + "clients_enabled": "", + "timesheet_enabled?": "", + "color?": "", + "last_needle_color?": "", + "last_needle_position?": "", + "previous_needle_position?": "", + "bookmark_url": "", + "url": "", + "app_url": "", + "dock": [ + { + "id": "", + "title": "", + "name": "", + "enabled": "", + "position": "", + "url": "", + "app_url": "" + } + ], + "bookmarked": "" + } + ] +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/basecamp/actions/fetch-projects.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/basecamp/actions/fetch-projects.md) + + + diff --git a/integrations/basecamp/actions/fetch-projects.ts b/integrations/basecamp/actions/fetch-projects.ts new file mode 100644 index 000000000..251803b94 --- /dev/null +++ b/integrations/basecamp/actions/fetch-projects.ts @@ -0,0 +1,27 @@ +import type { BasecampProject, BasecampProjectsResponse, NangoAction, ProxyConfiguration } from '../../models'; + +/** + * Action: fetch-projects + * Fetches *all* projects from Basecamp's /projects.json endpoint. + */ +export default async function runAction(nango: NangoAction): Promise { + const allProjects: BasecampProject[] = []; + + const config: ProxyConfiguration = { + //https://github.com/basecamp/bc3-api/blob/master/sections/projects.md#projects + endpoint: '/projects.json', + retries: 10, + paginate: { + type: 'link', + link_rel_in_response_header: 'next' + } + }; + + for await (const projectsPage of nango.paginate(config)) { + for (const project of projectsPage) { + allProjects.push(project); + } + } + + return { projects: allProjects }; +} diff --git a/integrations/basecamp/fixtures/create-todo.json b/integrations/basecamp/fixtures/create-todo.json new file mode 100644 index 000000000..173ea2de3 --- /dev/null +++ b/integrations/basecamp/fixtures/create-todo.json @@ -0,0 +1,11 @@ +{ + "projectId": 40818344, + "todoListId": 8262661192, + "content": "Implement feature Y", + "description": "This task involves implementing feature Y for project Z.", + "due_on": "2025-12-31", + "starts_on": "2025-12-01", + "notify": true, + "assigneeEmails": ["test.user@some-email.com"], + "completionSubscriberEmails": ["test.user@some-email.com"] +} diff --git a/integrations/basecamp/fixtures/metadata.json b/integrations/basecamp/fixtures/metadata.json new file mode 100644 index 000000000..843770a3a --- /dev/null +++ b/integrations/basecamp/fixtures/metadata.json @@ -0,0 +1,3 @@ +{ + "projects": [{ "projectId": 40818344, "todoSetId": 8262656026 }] +} diff --git a/integrations/basecamp/helpers/find-user.ts b/integrations/basecamp/helpers/find-user.ts new file mode 100644 index 000000000..55f586dad --- /dev/null +++ b/integrations/basecamp/helpers/find-user.ts @@ -0,0 +1,13 @@ +import type { BasecampPerson } from '../../models'; + +/** + * Finds a user's ID by their email address within a list of project people. + * + * @param email - The email address to search for. + * @param projectPeople - The list of people in the project. + * @returns The user ID if found, otherwise undefined. + */ +export function findUserIdByEmail(email: string, projectPeople: BasecampPerson[]): number | undefined { + const person = projectPeople.find((p) => p.email_address.toLowerCase() === email.toLowerCase()); + return person?.id; +} diff --git a/integrations/basecamp/mocks/fetch-projects/output.json b/integrations/basecamp/mocks/fetch-projects/output.json new file mode 100644 index 000000000..bdcdd8a6e --- /dev/null +++ b/integrations/basecamp/mocks/fetch-projects/output.json @@ -0,0 +1,187 @@ +{ + "projects": [ + { + "id": 40818346, + "status": "active", + "created_at": "2025-01-28T07:57:22.252Z", + "updated_at": "2025-01-28T07:57:30.565Z", + "name": "Getting Started", + "description": "Quickly get up to speed with everything Basecamp", + "purpose": "guide", + "clients_enabled": false, + "timesheet_enabled": false, + "color": null, + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12UW5WamEyVjBMelF3T0RFNE16UTJQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InJlYWRhYmxlIn19--5ce4228a24635ac9bae81bbd37bdec12b2744f05.json", + "url": "https://3.basecampapi.com/5904101/projects/40818346.json", + "app_url": "https://3.basecamp.com/5904101/projects/40818346", + "dock": [ + { + "id": 8262656083, + "title": "Message Board", + "name": "message_board", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/message_boards/8262656083.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/message_boards/8262656083" + }, + { + "id": 8262656086, + "title": "Learn the basics", + "name": "todoset", + "enabled": true, + "position": 1, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/todosets/8262656086.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/todosets/8262656086" + }, + { + "id": 8262656090, + "title": "Docs & Files", + "name": "vault", + "enabled": true, + "position": 2, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/vaults/8262656090.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/vaults/8262656090" + }, + { + "id": 8262656092, + "title": "Chat", + "name": "chat", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/chats/8262656092.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/chats/8262656092" + }, + { + "id": 8262656094, + "title": "Schedule", + "name": "schedule", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/schedules/8262656094.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/schedules/8262656094" + }, + { + "id": 8262656097, + "title": "Automatic Check-ins", + "name": "questionnaire", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/questionnaires/8262656097.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/questionnaires/8262656097" + }, + { + "id": 8262656098, + "title": "Email Forwards", + "name": "inbox", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/inboxes/8262656098.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/inboxes/8262656098" + }, + { + "id": 8262656099, + "title": "Card Table", + "name": "kanban_board", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/card_tables/8262656099.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/card_tables/8262656099" + } + ], + "bookmarked": false + }, + { + "id": 40818344, + "status": "active", + "created_at": "2025-01-28T07:57:21.460Z", + "updated_at": "2025-01-29T15:01:01.263Z", + "name": "My Project", + "description": null, + "purpose": "topic", + "clients_enabled": false, + "timesheet_enabled": false, + "color": null, + "last_needle_color": "green", + "last_needle_position": null, + "previous_needle_position": null, + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12UW5WamEyVjBMelF3T0RFNE16UTBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InJlYWRhYmxlIn19--8826e633401b8455a3f73968b60dd5c9b792f087.json", + "url": "https://3.basecampapi.com/5904101/projects/40818344.json", + "app_url": "https://3.basecamp.com/5904101/projects/40818344", + "dock": [ + { + "id": 8262656024, + "title": "Message Board", + "name": "message_board", + "enabled": true, + "position": 1, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/message_boards/8262656024.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/message_boards/8262656024" + }, + { + "id": 8262656026, + "title": "To-dos", + "name": "todoset", + "enabled": true, + "position": 2, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todosets/8262656026.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todosets/8262656026" + }, + { + "id": 8262656028, + "title": "Docs & Files", + "name": "vault", + "enabled": true, + "position": 3, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/vaults/8262656028.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/vaults/8262656028" + }, + { + "id": 8262656030, + "title": "Chat", + "name": "chat", + "enabled": true, + "position": 4, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/chats/8262656030.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/chats/8262656030" + }, + { + "id": 8262656033, + "title": "Schedule", + "name": "schedule", + "enabled": true, + "position": 5, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/schedules/8262656033.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/schedules/8262656033" + }, + { + "id": 8262656035, + "title": "Automatic Check-ins", + "name": "questionnaire", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/questionnaires/8262656035.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/questionnaires/8262656035" + }, + { + "id": 8262656036, + "title": "Email Forwards", + "name": "inbox", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/inboxes/8262656036.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/inboxes/8262656036" + }, + { + "id": 8262656039, + "title": "Card Table", + "name": "kanban_board", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/card_tables/8262656039.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/card_tables/8262656039" + } + ], + "bookmarked": true + } + ] +} diff --git a/integrations/basecamp/mocks/nango/get/proxy/buckets/40818344/todolists/8262661192/todos.json/todos/532f18494b602a099f3e66df5ca8840efa2bb3cf.json b/integrations/basecamp/mocks/nango/get/proxy/buckets/40818344/todolists/8262661192/todos.json/todos/532f18494b602a099f3e66df5ca8840efa2bb3cf.json new file mode 100644 index 000000000..3fce9a251 --- /dev/null +++ b/integrations/basecamp/mocks/nango/get/proxy/buckets/40818344/todolists/8262661192/todos.json/todos/532f18494b602a099f3e66df5ca8840efa2bb3cf.json @@ -0,0 +1,933 @@ +{ + "method": "get", + "endpoint": "buckets/40818344/todolists/8262661192/todos.json", + "requestIdentityHash": "532f18494b602a099f3e66df5ca8840efa2bb3cf", + "requestIdentity": { + "method": "get", + "endpoint": "buckets/40818344/todolists/8262661192/todos.json", + "params": [["per_page", 100]], + "headers": [] + }, + "response": [ + { + "id": 8262662772, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-28T08:00:32.429Z", + "updated_at": "2025-01-28T08:00:32.538Z", + "title": "Fetch todos from basecamp", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8262662772.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todos/8262662772", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qSTJOakkzTnpJX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--5d68c0ce4b25ee6e312ed2535b703bd1eeb3fed3.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8262662772/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8262662772/comments.json", + "position": 1, + "parent": { + "id": 8262661192, + "title": "Basecamp Nango integration", + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "", + "completed": false, + "content": "Fetch todos from basecamp", + "starts_on": null, + "due_on": "2025-01-29", + "assignees": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_subscribers": [], + "completion_url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8262662772/completion.json" + }, + { + "id": 8262666574, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-28T08:02:40.874Z", + "updated_at": "2025-01-28T08:02:40.949Z", + "title": "Write back todos via an action", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8262666574.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todos/8262666574", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qSTJOalkxTnpRX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--2230e2c761beb0d6d335ceea8e99ff1a330c92f6.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8262666574/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8262666574/comments.json", + "position": 2, + "parent": { + "id": 8262661192, + "title": "Basecamp Nango integration", + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "", + "completed": false, + "content": "Write back todos via an action", + "starts_on": null, + "due_on": "2025-01-29", + "assignees": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_subscribers": [], + "completion_url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8262666574/completion.json" + }, + { + "id": 8267922718, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-29T10:04:06.619Z", + "updated_at": "2025-01-29T10:04:06.753Z", + "title": "Implement feature X", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8267922718.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todos/8267922718", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qYzVNakkzTVRnX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--fa21dd225b69eaeb64252d4eadf69e04fa418d5b.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8267922718/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8267922718/comments.json", + "position": 3, + "parent": { + "id": 8262661192, + "title": "Basecamp Nango integration", + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "This task involves implementing feature X for project Y.", + "completed": false, + "content": "Implement feature X", + "starts_on": "2025-12-01", + "due_on": "2025-12-31", + "assignees": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_subscribers": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8267922718/completion.json" + }, + { + "id": 8268320194, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-29T12:29:31.035Z", + "updated_at": "2025-01-29T12:29:31.141Z", + "title": "Implement feature Y", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268320194.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todos/8268320194", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qZ3pNakF4T1RRX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--74620d0e7d15c8a075895c8800a5dad68d9317c7.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268320194/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268320194/comments.json", + "position": 4, + "parent": { + "id": 8262661192, + "title": "Basecamp Nango integration", + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "content": "Implement feature Y", + "starts_on": "2025-12-01", + "due_on": "2025-12-31", + "assignees": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_subscribers": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268320194/completion.json" + }, + { + "id": 8268323643, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-29T12:30:40.241Z", + "updated_at": "2025-01-29T12:30:40.345Z", + "title": "Implement feature Y", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268323643.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todos/8268323643", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qZ3pNak0yTkRNX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--649ab56dc11c50a57de7e1e6caf528bd1a268a51.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268323643/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268323643/comments.json", + "position": 5, + "parent": { + "id": 8262661192, + "title": "Basecamp Nango integration", + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "content": "Implement feature Y", + "starts_on": "2025-12-01", + "due_on": "2025-12-31", + "assignees": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_subscribers": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268323643/completion.json" + }, + { + "id": 8268961325, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-29T14:58:57.803Z", + "updated_at": "2025-01-29T14:58:57.985Z", + "title": "Implement feature Y", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268961325.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todos/8268961325", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qZzVOakV6TWpVX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--ecbf9789cae8bb66719bc9020aa0c3d1b6762ffd.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268961325/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268961325/comments.json", + "position": 6, + "parent": { + "id": 8262661192, + "title": "Basecamp Nango integration", + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "content": "Implement feature Y", + "starts_on": "2025-12-01", + "due_on": "2025-12-31", + "assignees": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_subscribers": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268961325/completion.json" + }, + { + "id": 8268963748, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-29T14:59:24.894Z", + "updated_at": "2025-01-29T14:59:25.021Z", + "title": "Implement feature Y", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268963748.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todos/8268963748", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qZzVOak0zTkRnX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--e95dda09deac6dd87d41bac3ec6d2b266b9ad268.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268963748/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268963748/comments.json", + "position": 7, + "parent": { + "id": 8262661192, + "title": "Basecamp Nango integration", + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "content": "Implement feature Y", + "starts_on": "2025-12-01", + "due_on": "2025-12-31", + "assignees": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_subscribers": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268963748/completion.json" + }, + { + "id": 8268972800, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-29T15:01:00.644Z", + "updated_at": "2025-01-29T15:01:00.765Z", + "title": "Implement feature Y", + "inherits_status": true, + "type": "Todo", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268972800.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todos/8268972800", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qZzVOekk0TURBX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--15b8700158ce030d0c524f08dfe03843ec2252f1.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268972800/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8268972800/comments.json", + "position": 8, + "parent": { + "id": 8262661192, + "title": "Basecamp Nango integration", + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "content": "Implement feature Y", + "starts_on": "2025-12-01", + "due_on": "2025-12-31", + "assignees": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_subscribers": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "completion_url": "https://3.basecampapi.com/5904101/buckets/40818344/todos/8268972800/completion.json" + } + ], + "status": 200, + "headers": { + "date": "Thu, 30 Jan 2025 08:21:17 GMT", + "content-type": "application/json; charset=utf-8", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "90a00c2b78bdc22f-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "cache-control": "max-age=0, private, must-revalidate", + "etag": "W/\"85349f9e46e0224b475c5d7d981531d4\"", + "strict-transport-security": "max-age=15552000; includeSubDomains; preload", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "alt-svc": "h3=\":443\"; ma=86400", + "content-security-policy": "font-src 'self' blob: data: https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com fonts.gstatic.com https://rsms.me https://use.typekit.net; object-src 'none'; script-src 'self' https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com *.braintreegateway.com *.sentry-cdn.com https://basecamp.com https://www.dropbox.com/static/api/2/dropins.js https://platform.twitter.com hcaptcha.com *.hcaptcha.com beacon-v2.helpscout.net 'sha256-tkb4UZeVJlSA6VA48VjvAuLrjlKnMlkZ3aYzHpTVQ+Y=' https://cdn01.boxcdn.net/js/static/select.js embedr.flickr.com/assets/ widgets.flickr.com/embedr/ 'sha256-aCvRIQ79zbEtvxwsqDbuavE4Sa35jGPLpcm4Y1yIUA0=' 'sha256-Ez5uQZcKGUzoXDIfdPNg1TIyAIO39xBSe6Xba9lAhR4=' https://assets.tickspot.com https://secure.tickspot.com 'report-sample' 'nonce-da39a3ee5e6b4b0d3255bfef95601890afd80709'; style-src 'self' 'unsafe-inline' https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com fonts.googleapis.com https://rsms.me/inter/inter.css https://assets.tickspot.com 'report-sample'; base-uri 'self'; form-action 'self' https://3.basecamp.com https://launchpad.37signals.com https://basecamp.com/; frame-ancestors 'self' https://3.basecamp.com", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "referrer-policy": "strict-origin-when-cross-origin", + "rndr-id": "618eadf4-fd26-415c", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-permitted-cross-domain-policies": "none", + "x-queue-time": "0.0010561943054199219", + "x-ratelimit": "{\"name\":\"API\",\"period\":10,\"limit\":50,\"remaining\":48,\"until\":\"2025-01-30T08:21:20Z\"}", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2391", + "x-ratelimit-reset": "1738225305", + "x-render-origin-server": "cloudflare", + "x-request-id": "bf8964ae-ac01-4fb6-b3db-69ed5bcfa30f", + "x-robots-tag": "none", + "x-runtime": "0.029399", + "x-total-count": "8", + "x-xss-protection": "1; mode=block", + "server": "cloudflare" + } +} diff --git a/integrations/basecamp/mocks/nango/get/proxy/buckets/40818344/todosets/8262656026/todolists.json/todos/ed42f9ae00dcc851a083c331a4bbd565275a5469.json b/integrations/basecamp/mocks/nango/get/proxy/buckets/40818344/todosets/8262656026/todolists.json/todos/ed42f9ae00dcc851a083c331a4bbd565275a5469.json new file mode 100644 index 000000000..a0702517a --- /dev/null +++ b/integrations/basecamp/mocks/nango/get/proxy/buckets/40818344/todosets/8262656026/todolists.json/todos/ed42f9ae00dcc851a083c331a4bbd565275a5469.json @@ -0,0 +1,113 @@ +{ + "method": "get", + "endpoint": "buckets/40818344/todosets/8262656026/todolists.json", + "requestIdentityHash": "ed42f9ae00dcc851a083c331a4bbd565275a5469", + "requestIdentity": { + "method": "get", + "endpoint": "buckets/40818344/todosets/8262656026/todolists.json", + "params": [], + "headers": [] + }, + "response": [ + { + "id": 8262661192, + "status": "active", + "visible_to_clients": false, + "created_at": "2025-01-28T07:59:48.889Z", + "updated_at": "2025-01-29T15:01:00.784Z", + "title": "Basecamp Nango integration", + "inherits_status": true, + "type": "Todolist", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192", + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaTVuYVdRNkx5OWlZek12VW1WamIzSmthVzVuTHpneU5qSTJOakV4T1RJX1pYaHdhWEpsYzE5cGJnWTZCa1ZVIiwiZXhwIjpudWxsLCJwdXIiOiJyZWFkYWJsZSJ9fQ==--edaacca3dae214d8b63069bfa278a06080cd6dca.json", + "subscription_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8262661192/subscription.json", + "comments_count": 0, + "comments_url": "https://3.basecampapi.com/5904101/buckets/40818344/recordings/8262661192/comments.json", + "position": 1, + "parent": { + "id": 8262656026, + "title": "To-dos", + "type": "Todoset", + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todosets/8262656026.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todosets/8262656026" + }, + "bucket": { + "id": 40818344, + "name": "My Project", + "type": "Project" + }, + "creator": { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + }, + "description": "
This is the list of tasks I have for the Basecamp integration
", + "completed": false, + "completed_ratio": "0/8", + "name": "Basecamp Nango integration", + "todos_url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192/todos.json", + "groups_url": "https://3.basecampapi.com/5904101/buckets/40818344/todolists/8262661192/groups.json", + "app_todos_url": "https://3.basecamp.com/5904101/buckets/40818344/todolists/8262661192/todos" + } + ], + "status": 200, + "headers": { + "date": "Thu, 30 Jan 2025 08:21:16 GMT", + "content-type": "application/json; charset=utf-8", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "90a00c2569dac22f-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "cache-control": "max-age=0, private, must-revalidate", + "etag": "W/\"c170369b34f6f459744a17628a8008ea\"", + "strict-transport-security": "max-age=15552000; includeSubDomains; preload", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "alt-svc": "h3=\":443\"; ma=86400", + "content-security-policy": "font-src 'self' blob: data: https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com fonts.gstatic.com https://rsms.me https://use.typekit.net; object-src 'none'; script-src 'self' https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com *.braintreegateway.com *.sentry-cdn.com https://basecamp.com https://www.dropbox.com/static/api/2/dropins.js https://platform.twitter.com hcaptcha.com *.hcaptcha.com beacon-v2.helpscout.net 'sha256-tkb4UZeVJlSA6VA48VjvAuLrjlKnMlkZ3aYzHpTVQ+Y=' https://cdn01.boxcdn.net/js/static/select.js embedr.flickr.com/assets/ widgets.flickr.com/embedr/ 'sha256-aCvRIQ79zbEtvxwsqDbuavE4Sa35jGPLpcm4Y1yIUA0=' 'sha256-Ez5uQZcKGUzoXDIfdPNg1TIyAIO39xBSe6Xba9lAhR4=' https://assets.tickspot.com https://secure.tickspot.com 'report-sample' 'nonce-da39a3ee5e6b4b0d3255bfef95601890afd80709'; style-src 'self' 'unsafe-inline' https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com fonts.googleapis.com https://rsms.me/inter/inter.css https://assets.tickspot.com 'report-sample'; base-uri 'self'; form-action 'self' https://3.basecamp.com https://launchpad.37signals.com https://basecamp.com/; frame-ancestors 'self' https://3.basecamp.com; report-uri https://o33603.ingest.us.sentry.io/api/4508280687624192/security/?sentry_key=0ee4ee9190f5c1183633750d914b0ce2&sentry_environment=production&sentry_release=12ad690904113c3393b2eeb4a6055db269771a52", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "referrer-policy": "strict-origin-when-cross-origin", + "rndr-id": "94d8b7aa-70b2-469f", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-permitted-cross-domain-policies": "none", + "x-queue-time": "0.0011591911315917969", + "x-ratelimit": "{\"name\":\"API\",\"period\":10,\"limit\":50,\"remaining\":49,\"until\":\"2025-01-30T08:21:20Z\"}", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2392", + "x-ratelimit-reset": "1738225305", + "x-render-origin-server": "cloudflare", + "x-request-id": "c0534748-55ad-4564-970c-b10d6c073de1", + "x-robots-tag": "none", + "x-runtime": "0.047016", + "x-total-count": "1", + "x-xss-protection": "1; mode=block", + "server": "cloudflare" + } +} diff --git a/integrations/basecamp/mocks/nango/get/proxy/projects.json/fetch-projects/fe7767e13399643aef7ba533ad7c620beefa6fcc.json b/integrations/basecamp/mocks/nango/get/proxy/projects.json/fetch-projects/fe7767e13399643aef7ba533ad7c620beefa6fcc.json new file mode 100644 index 000000000..3dc65b2ff --- /dev/null +++ b/integrations/basecamp/mocks/nango/get/proxy/projects.json/fetch-projects/fe7767e13399643aef7ba533ad7c620beefa6fcc.json @@ -0,0 +1,234 @@ +{ + "method": "get", + "endpoint": "projects.json", + "requestIdentityHash": "fe7767e13399643aef7ba533ad7c620beefa6fcc", + "requestIdentity": { + "method": "get", + "endpoint": "projects.json", + "params": [], + "headers": [] + }, + "response": [ + { + "id": 40818346, + "status": "active", + "created_at": "2025-01-28T07:57:22.252Z", + "updated_at": "2025-01-28T07:57:30.565Z", + "name": "Getting Started", + "description": "Quickly get up to speed with everything Basecamp", + "purpose": "guide", + "clients_enabled": false, + "timesheet_enabled": false, + "color": null, + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12UW5WamEyVjBMelF3T0RFNE16UTJQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InJlYWRhYmxlIn19--5ce4228a24635ac9bae81bbd37bdec12b2744f05.json", + "url": "https://3.basecampapi.com/5904101/projects/40818346.json", + "app_url": "https://3.basecamp.com/5904101/projects/40818346", + "dock": [ + { + "id": 8262656083, + "title": "Message Board", + "name": "message_board", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/message_boards/8262656083.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/message_boards/8262656083" + }, + { + "id": 8262656086, + "title": "Learn the basics", + "name": "todoset", + "enabled": true, + "position": 1, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/todosets/8262656086.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/todosets/8262656086" + }, + { + "id": 8262656090, + "title": "Docs & Files", + "name": "vault", + "enabled": true, + "position": 2, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/vaults/8262656090.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/vaults/8262656090" + }, + { + "id": 8262656092, + "title": "Chat", + "name": "chat", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/chats/8262656092.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/chats/8262656092" + }, + { + "id": 8262656094, + "title": "Schedule", + "name": "schedule", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/schedules/8262656094.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/schedules/8262656094" + }, + { + "id": 8262656097, + "title": "Automatic Check-ins", + "name": "questionnaire", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/questionnaires/8262656097.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/questionnaires/8262656097" + }, + { + "id": 8262656098, + "title": "Email Forwards", + "name": "inbox", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/inboxes/8262656098.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/inboxes/8262656098" + }, + { + "id": 8262656099, + "title": "Card Table", + "name": "kanban_board", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818346/card_tables/8262656099.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818346/card_tables/8262656099" + } + ], + "bookmarked": false + }, + { + "id": 40818344, + "status": "active", + "created_at": "2025-01-28T07:57:21.460Z", + "updated_at": "2025-01-29T15:01:01.263Z", + "name": "My Project", + "description": null, + "purpose": "topic", + "clients_enabled": false, + "timesheet_enabled": false, + "color": null, + "last_needle_color": "green", + "last_needle_position": null, + "previous_needle_position": null, + "bookmark_url": "https://3.basecampapi.com/5904101/my/bookmarks/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12UW5WamEyVjBMelF3T0RFNE16UTBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6InJlYWRhYmxlIn19--8826e633401b8455a3f73968b60dd5c9b792f087.json", + "url": "https://3.basecampapi.com/5904101/projects/40818344.json", + "app_url": "https://3.basecamp.com/5904101/projects/40818344", + "dock": [ + { + "id": 8262656024, + "title": "Message Board", + "name": "message_board", + "enabled": true, + "position": 1, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/message_boards/8262656024.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/message_boards/8262656024" + }, + { + "id": 8262656026, + "title": "To-dos", + "name": "todoset", + "enabled": true, + "position": 2, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/todosets/8262656026.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/todosets/8262656026" + }, + { + "id": 8262656028, + "title": "Docs & Files", + "name": "vault", + "enabled": true, + "position": 3, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/vaults/8262656028.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/vaults/8262656028" + }, + { + "id": 8262656030, + "title": "Chat", + "name": "chat", + "enabled": true, + "position": 4, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/chats/8262656030.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/chats/8262656030" + }, + { + "id": 8262656033, + "title": "Schedule", + "name": "schedule", + "enabled": true, + "position": 5, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/schedules/8262656033.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/schedules/8262656033" + }, + { + "id": 8262656035, + "title": "Automatic Check-ins", + "name": "questionnaire", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/questionnaires/8262656035.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/questionnaires/8262656035" + }, + { + "id": 8262656036, + "title": "Email Forwards", + "name": "inbox", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/inboxes/8262656036.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/inboxes/8262656036" + }, + { + "id": 8262656039, + "title": "Card Table", + "name": "kanban_board", + "enabled": false, + "position": null, + "url": "https://3.basecampapi.com/5904101/buckets/40818344/card_tables/8262656039.json", + "app_url": "https://3.basecamp.com/5904101/buckets/40818344/card_tables/8262656039" + } + ], + "bookmarked": true + } + ], + "status": 200, + "headers": { + "date": "Thu, 30 Jan 2025 08:28:21 GMT", + "content-type": "application/json; charset=utf-8", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "90a01683df0b7d95-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "cache-control": "max-age=0, private, must-revalidate", + "etag": "W/\"62b4487d96efdd86bbc46a3a465fdd0d\"", + "strict-transport-security": "max-age=15552000; includeSubDomains; preload", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "alt-svc": "h3=\":443\"; ma=86400", + "content-security-policy": "font-src 'self' blob: data: https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com fonts.gstatic.com https://rsms.me https://use.typekit.net; object-src 'none'; script-src 'self' https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com *.braintreegateway.com *.sentry-cdn.com https://basecamp.com https://www.dropbox.com/static/api/2/dropins.js https://platform.twitter.com hcaptcha.com *.hcaptcha.com beacon-v2.helpscout.net 'sha256-tkb4UZeVJlSA6VA48VjvAuLrjlKnMlkZ3aYzHpTVQ+Y=' https://cdn01.boxcdn.net/js/static/select.js embedr.flickr.com/assets/ widgets.flickr.com/embedr/ 'sha256-aCvRIQ79zbEtvxwsqDbuavE4Sa35jGPLpcm4Y1yIUA0=' 'sha256-Ez5uQZcKGUzoXDIfdPNg1TIyAIO39xBSe6Xba9lAhR4=' https://assets.tickspot.com https://secure.tickspot.com 'report-sample' 'nonce-da39a3ee5e6b4b0d3255bfef95601890afd80709'; style-src 'self' 'unsafe-inline' https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com fonts.googleapis.com https://rsms.me/inter/inter.css https://assets.tickspot.com 'report-sample'; base-uri 'self'; form-action 'self' https://3.basecamp.com https://launchpad.37signals.com https://basecamp.com/; frame-ancestors 'self' https://3.basecamp.com", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "referrer-policy": "strict-origin-when-cross-origin", + "rndr-id": "37ff2a29-d515-4fef", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-permitted-cross-domain-policies": "none", + "x-queue-time": "0.0012919902801513672", + "x-ratelimit": "{\"name\":\"API\",\"period\":10,\"limit\":50,\"remaining\":49,\"until\":\"2025-01-30T08:28:30Z\"}", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2395", + "x-ratelimit-reset": "1738225753", + "x-render-origin-server": "cloudflare", + "x-request-id": "cd450f8d-b2c8-4064-99fc-843db73e26fe", + "x-robots-tag": "none", + "x-runtime": "0.048767", + "x-total-count": "2", + "x-xss-protection": "1; mode=block", + "server": "cloudflare" + } +} diff --git a/integrations/basecamp/mocks/nango/get/proxy/projects/40818344/people.json/create-todo/627724fe08c8a439b03b9fd8515c94aca59d4779.json b/integrations/basecamp/mocks/nango/get/proxy/projects/40818344/people.json/create-todo/627724fe08c8a439b03b9fd8515c94aca59d4779.json new file mode 100644 index 000000000..28db9fabe --- /dev/null +++ b/integrations/basecamp/mocks/nango/get/proxy/projects/40818344/people.json/create-todo/627724fe08c8a439b03b9fd8515c94aca59d4779.json @@ -0,0 +1,77 @@ +{ + "method": "get", + "endpoint": "projects/40818344/people.json", + "requestIdentityHash": "627724fe08c8a439b03b9fd8515c94aca59d4779", + "requestIdentity": { + "method": "get", + "endpoint": "projects/40818344/people.json", + "params": [], + "headers": [] + }, + "response": [ + { + "id": 47545474, + "attachable_sgid": "eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxuYVdRNkx5OWlZek12VUdWeWMyOXVMelEzTlRRMU5EYzBQMlY0Y0dseVpYTmZhVzRHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImF0dGFjaGFibGUifX0=--9375d1ccfe5a43434a2c5db37749ebb9eb0e6a78", + "name": "test user", + "email_address": "test.user@some-email.com", + "personable_type": "User", + "title": null, + "bio": null, + "location": null, + "created_at": "2025-01-28T07:57:21.360Z", + "updated_at": "2025-01-28T07:57:21.447Z", + "admin": true, + "owner": true, + "client": false, + "employee": true, + "time_zone": "Europe/Moscow", + "avatar_url": "https://bc3-production-assets-cdn.basecamp-static.com/5904101/people/BAhpBIJ81QI=--5e31d9db6756eba019d44d883ced295845a97090/avatar?v=1", + "company": { + "id": 3829597, + "name": "Nango" + }, + "can_ping": true, + "can_manage_projects": true, + "can_manage_people": true, + "can_access_timesheet": false + } + ], + "status": 200, + "headers": { + "date": "Thu, 30 Jan 2025 08:29:39 GMT", + "content-type": "application/json; charset=utf-8", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "90a0186e897cc22c-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "cache-control": "max-age=0, private, must-revalidate", + "etag": "W/\"e96ab124aa59341d690188638ee5844c\"", + "strict-transport-security": "max-age=15552000; includeSubDomains; preload", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "alt-svc": "h3=\":443\"; ma=86400", + "content-security-policy": "font-src 'self' blob: data: https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com fonts.gstatic.com https://rsms.me https://use.typekit.net; object-src 'none'; script-src 'self' https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com *.braintreegateway.com *.sentry-cdn.com https://basecamp.com https://www.dropbox.com/static/api/2/dropins.js https://platform.twitter.com hcaptcha.com *.hcaptcha.com beacon-v2.helpscout.net 'sha256-tkb4UZeVJlSA6VA48VjvAuLrjlKnMlkZ3aYzHpTVQ+Y=' https://cdn01.boxcdn.net/js/static/select.js embedr.flickr.com/assets/ widgets.flickr.com/embedr/ 'sha256-aCvRIQ79zbEtvxwsqDbuavE4Sa35jGPLpcm4Y1yIUA0=' 'sha256-Ez5uQZcKGUzoXDIfdPNg1TIyAIO39xBSe6Xba9lAhR4=' https://assets.tickspot.com https://secure.tickspot.com 'report-sample' 'nonce-da39a3ee5e6b4b0d3255bfef95601890afd80709'; style-src 'self' 'unsafe-inline' https://bc3-production-assets-cdn.basecamp-static.com https://bc3-rollout-assets-cdn.basecamp-static.com fonts.googleapis.com https://rsms.me/inter/inter.css https://assets.tickspot.com 'report-sample'; base-uri 'self'; form-action 'self' https://3.basecamp.com https://launchpad.37signals.com https://basecamp.com/; frame-ancestors 'self' https://3.basecamp.com", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "referrer-policy": "strict-origin-when-cross-origin", + "rndr-id": "5271d917-d94e-4bf0", + "timing-allow-origin": "*", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-permitted-cross-domain-policies": "none", + "x-queue-time": "0.0005953311920166016", + "x-ratelimit": "{\"name\":\"API\",\"period\":10,\"limit\":50,\"remaining\":49,\"until\":\"2025-01-30T08:29:40Z\"}", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2386", + "x-ratelimit-reset": "1738225816", + "x-render-origin-server": "cloudflare", + "x-request-id": "f175a9ac-82b8-40b4-a734-93ec05d9818f", + "x-robots-tag": "none", + "x-runtime": "0.027693", + "x-total-count": "1", + "x-xss-protection": "1; mode=block", + "server": "cloudflare" + } +} diff --git a/integrations/basecamp/mocks/nango/getMetadata.json b/integrations/basecamp/mocks/nango/getMetadata.json new file mode 100644 index 000000000..843770a3a --- /dev/null +++ b/integrations/basecamp/mocks/nango/getMetadata.json @@ -0,0 +1,3 @@ +{ + "projects": [{ "projectId": 40818344, "todoSetId": 8262656026 }] +} diff --git a/integrations/basecamp/mocks/todos/BasecampTodo/batchDelete.json b/integrations/basecamp/mocks/todos/BasecampTodo/batchDelete.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/integrations/basecamp/mocks/todos/BasecampTodo/batchDelete.json @@ -0,0 +1 @@ +[] diff --git a/integrations/basecamp/mocks/todos/BasecampTodo/batchSave.json b/integrations/basecamp/mocks/todos/BasecampTodo/batchSave.json new file mode 100644 index 000000000..026c13fca --- /dev/null +++ b/integrations/basecamp/mocks/todos/BasecampTodo/batchSave.json @@ -0,0 +1,138 @@ +[ + { + "id": 8262662772, + "content": "Fetch todos from basecamp", + "description": "", + "completed": false, + "created_at": "2025-01-28T08:00:32.429Z", + "updated_at": "2025-01-28T08:00:32.538Z", + "due_on": "2025-01-29", + "bucket_id": 40818344, + "assignees": [ + { + "id": 47545474, + "name": "test user", + "email_address": "test.user@some-email.com" + } + ] + }, + { + "id": 8262666574, + "content": "Write back todos via an action", + "description": "", + "completed": false, + "created_at": "2025-01-28T08:02:40.874Z", + "updated_at": "2025-01-28T08:02:40.949Z", + "due_on": "2025-01-29", + "bucket_id": 40818344, + "assignees": [ + { + "id": 47545474, + "name": "test user", + "email_address": "test.user@some-email.com" + } + ] + }, + { + "id": 8267922718, + "content": "Implement feature X", + "description": "This task involves implementing feature X for project Y.", + "completed": false, + "created_at": "2025-01-29T10:04:06.619Z", + "updated_at": "2025-01-29T10:04:06.753Z", + "due_on": "2025-12-31", + "bucket_id": 40818344, + "assignees": [ + { + "id": 47545474, + "name": "test user", + "email_address": "test.user@some-email.com" + } + ] + }, + { + "id": 8268320194, + "content": "Implement feature Y", + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "created_at": "2025-01-29T12:29:31.035Z", + "updated_at": "2025-01-29T12:29:31.141Z", + "due_on": "2025-12-31", + "bucket_id": 40818344, + "assignees": [ + { + "id": 47545474, + "name": "test user", + "email_address": "test.user@some-email.com" + } + ] + }, + { + "id": 8268323643, + "content": "Implement feature Y", + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "created_at": "2025-01-29T12:30:40.241Z", + "updated_at": "2025-01-29T12:30:40.345Z", + "due_on": "2025-12-31", + "bucket_id": 40818344, + "assignees": [ + { + "id": 47545474, + "name": "test user", + "email_address": "test.user@some-email.com" + } + ] + }, + { + "id": 8268961325, + "content": "Implement feature Y", + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "created_at": "2025-01-29T14:58:57.803Z", + "updated_at": "2025-01-29T14:58:57.985Z", + "due_on": "2025-12-31", + "bucket_id": 40818344, + "assignees": [ + { + "id": 47545474, + "name": "test user", + "email_address": "test.user@some-email.com" + } + ] + }, + { + "id": 8268963748, + "content": "Implement feature Y", + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "created_at": "2025-01-29T14:59:24.894Z", + "updated_at": "2025-01-29T14:59:25.021Z", + "due_on": "2025-12-31", + "bucket_id": 40818344, + "assignees": [ + { + "id": 47545474, + "name": "test user", + "email_address": "test.user@some-email.com" + } + ] + }, + { + "id": 8268972800, + "content": "Implement feature Y", + "description": "This task involves implementing feature Y for project Z.", + "completed": false, + "created_at": "2025-01-29T15:01:00.644Z", + "updated_at": "2025-01-29T15:01:00.765Z", + "due_on": "2025-12-31", + "bucket_id": 40818344, + "assignees": [ + { + "id": 47545474, + "name": "test user", + "email_address": "test.user@some-email.com" + } + ] + } +] diff --git a/integrations/basecamp/nango.yaml b/integrations/basecamp/nango.yaml new file mode 100644 index 000000000..9aa3d9789 --- /dev/null +++ b/integrations/basecamp/nango.yaml @@ -0,0 +1,179 @@ +integrations: + basecamp: + actions: + fetch-accounts: + description: Fetch account list and user information from Basecamp + endpoint: + method: GET + path: /accounts + group: Accounts + output: UserInformation + fetch-projects: + description: Fetch all projects from Basecamp + endpoint: + method: GET + path: /projects + group: Projects + output: BasecampProjectsResponse + + create-todo: + description: Create a new to-do in a specific project + list + endpoint: + method: POST + path: /todos + group: Todos + input: BasecampCreateTodoInput + output: BasecampTodoResponse + + syncs: + todos: + runs: every 1 day + description: | + Syncs to-dos from Basecamp for the specified projects. Example of a metadata input Example: `{ projects: [ { projectId: 1234, todoSetId: 9999 }, ... ] }` + sync_type: full + track_deletes: true + input: TodosMetadata + auto_start: false + output: BasecampTodo + endpoint: + method: GET + path: /todos + group: Todos + +models: + UserInformation: + identity: + id: number + firstName: string + lastName: string + email: string + accounts: Account[] + Account: + id: number + name: string + product: string + href: string + app_href: string + hidden?: boolean + BasecampProjectsResponse: + projects: BasecampProject[] + + BasecampCreateTodoInput: + projectId: number + todoListId: number + content: string + description?: string + due_on?: string + starts_on?: string + notify?: boolean + assigneeEmails?: string[] + completionSubscriberEmails?: string[] + + BasecampTodoResponse: + id: number + status: string + visible_to_clients: boolean + created_at: string + updated_at: string + title: string + inherits_status: boolean + type: string + url: string + app_url: string + bookmark_url: string + subscription_url: string + comments_count: integer + comments_url: string + position: integer + parent: BasecampTodoParent + bucket: BasecampTodoBucket + creator: any + description: string + completed: boolean + content: string + starts_on: string + due_on: string + assignees: any[] + completion_subscribers: any[] + completion_url: string + + BasecampProject: + id: number + status: string + created_at: string + updated_at: string + name: string + description: string | null + purpose: string + clients_enabled: boolean + timesheet_enabled?: boolean + color?: string | null + last_needle_color?: string | null + last_needle_position?: string | null + previous_needle_position?: string | null + bookmark_url: string + url: string + app_url: string + dock: DockItem[] + bookmarked: boolean + + BasecampPerson: + id: number + name: string + email_address: string + avatar_url?: string + admin?: boolean + owner?: boolean + created_at?: string + updated_at?: string + attachable_sgid?: string + personable_type?: string + title?: string | null + bio?: string | null + location?: string | null + + BasecampTodo: + id: number + content: string + description?: string | undefined + due_on?: string | undefined + completed: boolean + created_at: string + updated_at: string + bucket_id: number + assignees?: BasecampPerson[] | undefined + + BasecampTodoParent: + id: number + title: string + type: string + url: string + app_url: string + + BasecampTodoBucket: + id: number + name: string + type: string + + BasecampCompany: + id: number + name: string + + DockItem: + id: number + title: string + name: string + enabled: boolean + position: number | null + url: string + app_url: string + + ClientSide: + url: string + app_url: string + + TodosMetadata: + projects: Project[] + Project: + projectId: number + todoSetId: number diff --git a/integrations/basecamp/schema.zod.ts b/integrations/basecamp/schema.zod.ts new file mode 100644 index 000000000..055b039e2 --- /dev/null +++ b/integrations/basecamp/schema.zod.ts @@ -0,0 +1,139 @@ +// Generated by ts-to-zod +import { z } from 'zod'; + +export const dockItemSchema = z.object({ + id: z.number(), + title: z.string(), + name: z.string(), + enabled: z.boolean(), + position: z.number().nullable(), + url: z.string(), + app_url: z.string() +}); + +export const basecampProjectSchema = z.object({ + id: z.number(), + status: z.string(), + created_at: z.string(), + updated_at: z.string(), + name: z.string(), + description: z.string().nullable(), + purpose: z.string(), + clients_enabled: z.boolean(), + timesheet_enabled: z.boolean().optional(), + color: z.string().optional().nullable(), + last_needle_color: z.string().optional().nullable(), + last_needle_position: z.string().optional().nullable(), + previous_needle_position: z.string().optional().nullable(), + bookmark_url: z.string(), + url: z.string(), + app_url: z.string(), + dock: z.array(dockItemSchema), + bookmarked: z.boolean() +}); + +export const basecampProjectsResponseSchema = z.object({ + projects: z.array(basecampProjectSchema) +}); + +export const basecampCreateTodoInputSchema = z.object({ + projectId: z.number(), + todoListId: z.number(), + content: z.string(), + description: z.string().optional(), + due_on: z.string().optional(), + starts_on: z.string().optional(), + notify: z.boolean().optional(), + assigneeEmails: z.array(z.string()).optional(), + completionSubscriberEmails: z.array(z.string()).optional() +}); + +export const basecampTodoParentSchema = z.object({ + id: z.number(), + title: z.string(), + type: z.string(), + url: z.string(), + app_url: z.string() +}); + +export const basecampTodoBucketSchema = z.object({ + id: z.number(), + name: z.string(), + type: z.string() +}); + +export const basecampTodoResponseSchema = z.object({ + id: z.number(), + status: z.string(), + visible_to_clients: z.boolean(), + created_at: z.string(), + updated_at: z.string(), + title: z.string(), + inherits_status: z.boolean(), + type: z.string(), + url: z.string(), + app_url: z.string(), + bookmark_url: z.string(), + subscription_url: z.string(), + comments_count: z.number(), + comments_url: z.string(), + position: z.number(), + parent: basecampTodoParentSchema, + bucket: basecampTodoBucketSchema, + creator: z.any(), + description: z.string(), + completed: z.boolean(), + content: z.string(), + starts_on: z.string(), + due_on: z.string(), + assignees: z.array(z.any()), + completion_subscribers: z.array(z.any()), + completion_url: z.string() +}); + +export const basecampPersonSchema = z.object({ + id: z.number(), + name: z.string(), + email_address: z.string(), + avatar_url: z.string().optional(), + admin: z.boolean().optional(), + owner: z.boolean().optional(), + created_at: z.string().optional(), + updated_at: z.string().optional(), + attachable_sgid: z.string().optional(), + personable_type: z.string().optional(), + title: z.string().optional().nullable(), + bio: z.string().optional().nullable(), + location: z.string().optional().nullable() +}); + +export const basecampTodoSchema = z.object({ + id: z.number(), + content: z.string(), + description: z.union([z.string(), z.undefined()]).optional(), + due_on: z.union([z.string(), z.undefined()]).optional(), + completed: z.boolean(), + created_at: z.string(), + updated_at: z.string(), + bucket_id: z.number(), + assignees: z.union([z.array(basecampPersonSchema), z.undefined()]).optional() +}); + +export const basecampCompanySchema = z.object({ + id: z.number(), + name: z.string() +}); + +export const clientSideSchema = z.object({ + url: z.string(), + app_url: z.string() +}); + +export const projectSchema = z.object({ + projectId: z.number(), + todoSetId: z.number() +}); + +export const todosMetadataSchema = z.object({ + projects: z.array(projectSchema) +}); diff --git a/integrations/netsuite-tba/syncs/journal-entries.md b/integrations/basecamp/syncs/todos.md similarity index 63% rename from integrations/netsuite-tba/syncs/journal-entries.md rename to integrations/basecamp/syncs/todos.md index 7951d9655..d1e2557c6 100644 --- a/integrations/netsuite-tba/syncs/journal-entries.md +++ b/integrations/basecamp/syncs/todos.md @@ -1,22 +1,22 @@ -# Journal Entries +# Todos ## General Information -- **Description:** Fetches all JournalEntries in Netsuite +- **Description:** Syncs to-dos from Basecamp for the specified projects. Example of a metadata input Example: `{ projects: [ { projectId: 1234, todoSetId: 9999 }, ... ] }` -- **Version:** 1.0.0 -- **Group:** Others +- **Version:** 0.0.1 +- **Group:** Todos - **Scopes:** _None_ - **Endpoint Type:** Sync -- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/netsuite-tba/syncs/journal-entries.ts) +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/basecamp/syncs/todos.ts) ## Endpoint Reference ### Request Endpoint -`GET /journalEntry` +`GET /todos` ### Request Query Parameters @@ -33,28 +33,26 @@ _No request body_ ```json { - "id": "", - "date": "", - "transactionId": "", - "void": "", - "approved": "", - "currency": "", - "createdDate": "", - "updatedDate": "", - "isReversal": "", - "subsidiary": { - "id": "", - "name": "" - }, - "lines": [ + "id": "", + "content": "", + "description?": "", + "due_on?": "", + "completed": "", + "created_at": "", + "updated_at": "", + "bucket_id": "", + "assignees?": "" +} +``` + +### Expected Metadata + +```json +{ + "projects": [ { - "journalLineId": "", - "accountId": "", - "accountName": "", - "cleared": "", - "credit?": "", - "debit?": "", - "description": "" + "projectId": "", + "todoSetId": "" } ] } @@ -62,8 +60,8 @@ _No request body_ ## Changelog -- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/netsuite-tba/syncs/journal-entries.ts) -- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/netsuite-tba/syncs/journal-entries.md) +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/basecamp/syncs/todos.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/basecamp/syncs/todos.md) diff --git a/integrations/basecamp/syncs/todos.ts b/integrations/basecamp/syncs/todos.ts new file mode 100644 index 000000000..6eed8dc88 --- /dev/null +++ b/integrations/basecamp/syncs/todos.ts @@ -0,0 +1,75 @@ +import type { BasecampTodo, BasecampPerson, TodosMetadata, NangoSync, ProxyConfiguration } from '../../models'; +import { todosMetadataSchema } from '../schema.zod.js'; + +/** + * The shape of the metadata we read from nango.getMetadata(). + * Example: { projects: [ { projectId: 1234, todoSetId: 9999 }, ... ] } + */ +export default async function runSync(nango: NangoSync): Promise { + const rawMetadata = await nango.getMetadata(); + const parsed = todosMetadataSchema.safeParse(rawMetadata); + if (!parsed.success) { + const msg = parsed.error.errors.map((e) => `${e.message} at path ${e.path.join('.')}`).join('; '); + throw new Error(`Invalid metadata for Basecamp todos sync: ${msg}`); + } + + const { projects } = parsed.data; + + for (const { projectId, todoSetId } of projects) { + const listConfig: ProxyConfiguration = { + // https://github.com/basecamp/bc3-api/blob/master/sections/todolists.md + endpoint: `/buckets/${projectId}/todosets/${todoSetId}/todolists.json`, + retries: 10, + paginate: { + type: 'link', + link_rel_in_response_header: 'next' + } + }; + + const finalTodos: BasecampTodo[] = []; + for await (const listsPage of nango.paginate(listConfig)) { + for (const list of listsPage) { + const listId = list.id; + if (!listId) continue; + + const todosConfig: ProxyConfiguration = { + // https://github.com/basecamp/bc3-api/blob/master/sections/todos.md#get-to-dos + endpoint: `/buckets/${projectId}/todolists/${listId}/todos.json`, + retries: 10, + paginate: { + type: 'link', + link_rel_in_response_header: 'next', + limit_name_in_request: 'per_page', + limit: 100 + } + }; + + for await (const bcTodosPage of nango.paginate(todosConfig)) { + for (const bcTodo of bcTodosPage) { + const toStore: BasecampTodo = { + id: bcTodo.id, + content: bcTodo.content, + description: bcTodo.description || '', + completed: bcTodo.completed || false, + created_at: bcTodo.created_at, + updated_at: bcTodo.updated_at, + due_on: bcTodo.due_on, + bucket_id: projectId, + assignees: Array.isArray(bcTodo.assignees) + ? bcTodo.assignees.map((a: BasecampPerson) => ({ + id: a.id, + name: a.name, + email_address: a.email_address + })) + : [] + }; + finalTodos.push(toStore); + } + } + } + } + if (finalTodos.length > 0) { + await nango.batchSave(finalTodos, 'BasecampTodo'); + } + } +} diff --git a/integrations/basecamp/tests/basecamp-fetch-projects.test.ts b/integrations/basecamp/tests/basecamp-fetch-projects.test.ts new file mode 100644 index 000000000..f0df47859 --- /dev/null +++ b/integrations/basecamp/tests/basecamp-fetch-projects.test.ts @@ -0,0 +1,19 @@ +import { vi, expect, it, describe } from 'vitest'; + +import runAction from '../actions/fetch-projects.js'; + +describe('basecamp fetch-projects tests', () => { + const nangoMock = new global.vitest.NangoActionMock({ + dirname: __dirname, + name: 'fetch-projects', + Model: 'BasecampProjectsResponse' + }); + + 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); + }); +}); diff --git a/integrations/basecamp/tests/basecamp-todos.test.ts b/integrations/basecamp/tests/basecamp-todos.test.ts new file mode 100644 index 000000000..d65cdb298 --- /dev/null +++ b/integrations/basecamp/tests/basecamp-todos.test.ts @@ -0,0 +1,45 @@ +import { vi, expect, it, describe } from 'vitest'; + +import fetchData from '../syncs/todos.js'; + +describe('basecamp todos tests', () => { + const nangoMock = new global.vitest.NangoSyncMock({ + dirname: __dirname, + name: 'todos', + Model: 'BasecampTodo' + }); + + const models = 'BasecampTodo'.split(','); + const batchSaveSpy = vi.spyOn(nangoMock, 'batchSave'); + + it('should get, map correctly the data and batchSave the result', async () => { + await fetchData(nangoMock); + + for (const model of models) { + const expectedBatchSaveData = await nangoMock.getBatchSaveData(model); + + const spiedData = batchSaveSpy.mock.calls.flatMap((call) => { + if (call[1] === model) { + return call[0]; + } + + return []; + }); + + const spied = JSON.parse(JSON.stringify(spiedData)); + + expect(spied).toStrictEqual(expectedBatchSaveData); + } + }); + + it('should get, map correctly the data and batchDelete the result', async () => { + await fetchData(nangoMock); + + for (const model of models) { + const batchDeleteData = await nangoMock.getBatchDeleteData(model); + if (batchDeleteData && batchDeleteData.length > 0) { + expect(nangoMock.batchDelete).toHaveBeenCalledWith(batchDeleteData, model); + } + } + }); +}); diff --git a/integrations/basecamp/types.ts b/integrations/basecamp/types.ts new file mode 100644 index 000000000..e0b2529ae --- /dev/null +++ b/integrations/basecamp/types.ts @@ -0,0 +1,16 @@ +export interface BasecampAuthorizationResponse { + expires_at: string; + identity: { + id: number; + first_name: string; + last_name: string; + email_address: string; + }; + accounts: { + product: string; + id: number; + name: string; + href: string; + app_href: string; + }[]; +} diff --git a/integrations/calendly/nango.yaml b/integrations/calendly/nango.yaml index 95696edfe..8b49b5c76 100644 --- a/integrations/calendly/nango.yaml +++ b/integrations/calendly/nango.yaml @@ -98,7 +98,7 @@ models: created_at: string updated_at: string event_memberships: EventMembership[] - event_guests: EventGuest[] + event_guests?: EventGuest[] calendar_event: CalendarEvent | null cancellation?: EventCancellation CalendarEvent: diff --git a/integrations/calendly/syncs/events.md b/integrations/calendly/syncs/events.md index f18b18194..8406420d8 100644 --- a/integrations/calendly/syncs/events.md +++ b/integrations/calendly/syncs/events.md @@ -62,7 +62,7 @@ _No request body_ "user_name": "" } ], - "event_guests": [ + "event_guests?": [ { "email": "", "created_at": "", diff --git a/integrations/checkr-partner/actions/create-candidate.md b/integrations/checkr-partner/actions/create-candidate.md index 107bfabb5..0f8753e8a 100644 --- a/integrations/checkr-partner/actions/create-candidate.md +++ b/integrations/checkr-partner/actions/create-candidate.md @@ -3,7 +3,7 @@ ## General Information -- **Description:** +- **Description:** Create a candidate - **Version:** 0.0.1 - **Group:** Others - **Scopes:** _None_ diff --git a/integrations/checkr-partner/nango.yaml b/integrations/checkr-partner/nango.yaml index ac9a9ef13..e99d86c2d 100644 --- a/integrations/checkr-partner/nango.yaml +++ b/integrations/checkr-partner/nango.yaml @@ -27,6 +27,7 @@ integrations: path: /candidates output: Candidate input: CreateCandidate + description: Create a candidate syncs: background-checks: endpoint: diff --git a/integrations/dropbox/syncs/files.md b/integrations/dropbox/syncs/files.md index 2ae215a90..08055a6d5 100644 --- a/integrations/dropbox/syncs/files.md +++ b/integrations/dropbox/syncs/files.md @@ -39,6 +39,15 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "files": "", + "folders": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/dropbox/syncs/files.ts) diff --git a/integrations/github/syncs/list-files.md b/integrations/github/syncs/list-files.md index be679dbb0..5584324fe 100644 --- a/integrations/github/syncs/list-files.md +++ b/integrations/github/syncs/list-files.md @@ -40,6 +40,16 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "owner": "", + "repo": "", + "branch": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/github/syncs/list-files.ts) diff --git a/integrations/google-docs/actions/fetch-document.md b/integrations/google-docs/actions/fetch-document.md new file mode 100644 index 000000000..d51a908ab --- /dev/null +++ b/integrations/google-docs/actions/fetch-document.md @@ -0,0 +1,67 @@ + +# Fetch Document + +## General Information + +- **Description:** Fetches the content of a document given its ID. + +- **Version:** 0.0.1 +- **Group:** Others +- **Scopes:** `https://www.googleapis.com/auth/documents.readonly` +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/google-docs/actions/fetch-document.ts) + + +## Endpoint Reference + +### Request Endpoint + +`GET /fetch-document` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +```json +{ + "id": "" +} +``` + +### Request Response + +```json +{ + "documentId": "", + "title": "", + "url": "", + "tabs": [ + "" + ], + "revisionId": "", + "suggestionsViewMode": "", + "body": "", + "headers": "", + "footers": "", + "footnotes": "", + "documentStyle": "", + "suggestedDocumentStyleChanges": "", + "namedStyles": "", + "suggestedNamedStylesChanges": "", + "lists": "", + "namedRanges": "", + "inlineObjects": "", + "positionedObjects": "" +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/google-docs/actions/fetch-document.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/google-docs/actions/fetch-document.md) + + +## Additional Information +The return type of this action is JSON. Users can convert the JSON response to any file format as needed once the action returns. This flexibility allows for various use cases, such as converting the response to a Base64 string, CSV, or other formats for further processing or analysis. diff --git a/integrations/google-docs/actions/fetch-document.ts b/integrations/google-docs/actions/fetch-document.ts new file mode 100644 index 000000000..27d0df2bf --- /dev/null +++ b/integrations/google-docs/actions/fetch-document.ts @@ -0,0 +1,27 @@ +import type { NangoAction, ProxyConfiguration, DocumentId, Document } from '../../models'; + +export default async function runAction(nango: NangoAction, input: DocumentId): Promise { + if (!input || !input.id) { + throw new nango.ActionError({ + message: 'Invalid input', + details: 'The input must be an object with an "id" property.' + }); + } + + const config: ProxyConfiguration = { + // https://developers.google.com/docs/api/reference/rest/v1/documents/get + endpoint: `/v1/documents/${input.id}`, + params: { + includeTabsContent: 'true' + }, + retries: 10 + }; + + const documentResponse = await nango.get(config); + + if (documentResponse.status !== 200) { + throw new nango.ActionError(`Failed to fetch document: Status Code ${documentResponse.status}`); + } + + return documentResponse.data; +} diff --git a/integrations/google-docs/nango.yaml b/integrations/google-docs/nango.yaml new file mode 100644 index 000000000..08fbed334 --- /dev/null +++ b/integrations/google-docs/nango.yaml @@ -0,0 +1,35 @@ +integrations: + google-docs: + actions: + fetch-document: + input: DocumentId + description: | + Fetches the content of a document given its ID. + output: Document + endpoint: + method: GET + path: /fetch-document + scopes: https://www.googleapis.com/auth/documents.readonly + +models: + DocumentId: + id: string + Document: + documentId: string + title: string + url: string + tabs: object[] + revisionId: string + suggestionsViewMode: DEFAULT_FOR_CURRENT_ACCESS | SUGGESTIONS_INLINE | PREVIEW_SUGGESTIONS_ACCEPTED | PREVIEW_WITHOUT_SUGGESTIONS + body: object + headers: object + footers: object + footnotes: object + documentStyle: object + suggestedDocumentStyleChanges: object + namedStyles: object + suggestedNamedStylesChanges: object + lists: object + namedRanges: object + inlineObjects: object + positionedObjects: object diff --git a/integrations/google-drive/syncs/documents.md b/integrations/google-drive/syncs/documents.md index c36514a71..1c95055b7 100644 --- a/integrations/google-drive/syncs/documents.md +++ b/integrations/google-drive/syncs/documents.md @@ -47,6 +47,15 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "files": "", + "folders": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/google-drive/syncs/documents.ts) diff --git a/integrations/google-mail/syncs/emails.md b/integrations/google-mail/syncs/emails.md index 3bd7450f2..1119f1e58 100644 --- a/integrations/google-mail/syncs/emails.md +++ b/integrations/google-mail/syncs/emails.md @@ -53,6 +53,14 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "backfillPeriodMs": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/google-mail/syncs/emails.ts) diff --git a/integrations/google-sheet/actions/fetch-spreadsheet.md b/integrations/google-sheet/actions/fetch-spreadsheet.md new file mode 100644 index 000000000..3bdc50ec7 --- /dev/null +++ b/integrations/google-sheet/actions/fetch-spreadsheet.md @@ -0,0 +1,65 @@ + +# Fetch Spreadsheet + +## General Information + +- **Description:** Fetches the content of a spreadsheet given its ID. + +- **Version:** 1.0.0 +- **Group:** Others +- **Scopes:** `https://www.googleapis.com/auth/spreadsheets.readonly` +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/google-sheet/actions/fetch-spreadsheet.ts) + + +## Endpoint Reference + +### Request Endpoint + +`GET /spreadsheet` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +```json +{ + "id": "" +} +``` + +### Request Response + +```json +{ + "spreadsheetId": "", + "properties": "", + "sheets": [ + "" + ], + "namedRanges": [ + "" + ], + "spreadsheetUrl": "", + "developerMetadata": [ + "" + ], + "dataSources": [ + "" + ], + "dataSourceSchedules": [ + "" + ] +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/google-sheet/actions/fetch-spreadsheet.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/google-sheet/actions/fetch-spreadsheet.md) + + +## Additional Information +The return type of this action is JSON. Users can convert the JSON response to any file format as needed once the action returns. This flexibility allows for various use cases, such as converting the response to a Base64 string, CSV, or other formats for further processing or analysis. diff --git a/integrations/google-sheet/actions/fetch-spreadsheet.ts b/integrations/google-sheet/actions/fetch-spreadsheet.ts new file mode 100644 index 000000000..c482026a8 --- /dev/null +++ b/integrations/google-sheet/actions/fetch-spreadsheet.ts @@ -0,0 +1,28 @@ +import type { NangoAction, ProxyConfiguration, SpreadsheetId, Spreadsheet } from '../../models'; + +export default async function runAction(nango: NangoAction, input: SpreadsheetId): Promise { + if (!input || !input.id) { + throw new nango.ActionError({ + message: 'Invalid input', + details: 'The input must be an object with an "id" property.' + }); + } + + // Fetch the sheet content from Google Sheets API + const config: ProxyConfiguration = { + // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/get + endpoint: `/v4/spreadsheets/${input.id}`, + params: { + includeGridData: 'true' + }, + retries: 10 + }; + + const response = await nango.get(config); + + if (response.status !== 200) { + throw new nango.ActionError(`Failed to fetch document: Status Code ${response.status}`); + } + + return response.data; +} diff --git a/integrations/google-sheet/nango.yaml b/integrations/google-sheet/nango.yaml new file mode 100644 index 000000000..980166bcd --- /dev/null +++ b/integrations/google-sheet/nango.yaml @@ -0,0 +1,26 @@ +integrations: + google-sheet: + actions: + fetch-spreadsheet: + input: SpreadsheetId + description: | + Fetches the content of a spreadsheet given its ID. + output: Spreadsheet + version: 1.0.0 + endpoint: + method: GET + path: /spreadsheet + scopes: https://www.googleapis.com/auth/spreadsheets.readonly + +models: + SpreadsheetId: + id: string + Spreadsheet: + spreadsheetId: string + properties: object + sheets: object[] + namedRanges: object[] + spreadsheetUrl: string + developerMetadata: object[] + dataSources: object[] + dataSourceSchedules: object[] diff --git a/integrations/google-sheet/schema.zod.ts b/integrations/google-sheet/schema.zod.ts new file mode 100644 index 000000000..686e00f18 --- /dev/null +++ b/integrations/google-sheet/schema.zod.ts @@ -0,0 +1,17 @@ +// Generated by ts-to-zod +import { z } from 'zod'; + +export const spreadsheetIdSchema = z.object({ + id: z.string() +}); + +export const spreadsheetSchema = z.object({ + spreadsheetId: z.string(), + properties: z.record(z.any()), + sheets: z.array(z.record(z.any())), + namedRanges: z.array(z.record(z.any())), + spreadsheetUrl: z.string(), + developerMetadata: z.array(z.record(z.any())), + dataSources: z.array(z.record(z.any())), + dataSourceSchedules: z.array(z.record(z.any())) +}); diff --git a/integrations/google/nango.yaml b/integrations/google/nango.yaml index c52eac8df..eb8d8c575 100644 --- a/integrations/google/nango.yaml +++ b/integrations/google/nango.yaml @@ -2,6 +2,7 @@ integrations: google: syncs: workspace-org-units: + description: Sync all workspace org units runs: every 6 hours track_deletes: true output: OrganizationalUnit @@ -13,6 +14,7 @@ integrations: - https://www.googleapis.com/auth/admin.directory.orgunit.readonly - https://www.googleapis.com/auth/admin.directory.user.readonly workspace-users: + description: Sync all workspace users runs: every hour input: Metadata auto_start: false @@ -22,6 +24,7 @@ integrations: method: GET path: /google/workspace-users workspace-user-access-tokens: + description: Sync all workspace users access tokens runs: every hour output: GoogleWorkspaceUserToken sync_type: full diff --git a/integrations/google/syncs/workspace-org-units.md b/integrations/google/syncs/workspace-org-units.md index 7e0bbe570..c53ee3149 100644 --- a/integrations/google/syncs/workspace-org-units.md +++ b/integrations/google/syncs/workspace-org-units.md @@ -3,7 +3,7 @@ ## General Information -- **Description:** +- **Description:** Sync all workspace org units - **Version:** 0.0.1 - **Group:** Others - **Scopes:** `https://www.googleapis.com/auth/admin.directory.orgunit.readonly, https://www.googleapis.com/auth/admin.directory.user.readonly` diff --git a/integrations/google/syncs/workspace-user-access-tokens.md b/integrations/google/syncs/workspace-user-access-tokens.md index 79a81ef19..e4d576f80 100644 --- a/integrations/google/syncs/workspace-user-access-tokens.md +++ b/integrations/google/syncs/workspace-user-access-tokens.md @@ -3,7 +3,7 @@ ## General Information -- **Description:** +- **Description:** Sync all workspace users access tokens - **Version:** 0.0.1 - **Group:** Others - **Scopes:** `https://www.googleapis.com/auth/admin.directory.user.readonly, https://www.googleapis.com/auth/admin.directory.user.security` diff --git a/integrations/google/syncs/workspace-users.md b/integrations/google/syncs/workspace-users.md index a335edb4f..698f0e484 100644 --- a/integrations/google/syncs/workspace-users.md +++ b/integrations/google/syncs/workspace-users.md @@ -3,7 +3,7 @@ ## General Information -- **Description:** +- **Description:** Sync all workspace users - **Version:** 0.0.1 - **Group:** Others - **Scopes:** _None_ @@ -52,6 +52,19 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "orgsToSync": [ + { + "id": "", + "path": "" + } + ] +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/google/syncs/workspace-users.ts) diff --git a/integrations/gorgias/actions/create-user.md b/integrations/gorgias/actions/create-user.md new file mode 100644 index 000000000..124c483b9 --- /dev/null +++ b/integrations/gorgias/actions/create-user.md @@ -0,0 +1,53 @@ + +# Create User + +## General Information + +- **Description:** Creates a new user with a role in Gorgias. Defaults to agent if a role is not provided + +- **Version:** 0.0.1 +- **Group:** Users +- **Scopes:** `users:write` +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/gorgias/actions/create-user.ts) + + +## Endpoint Reference + +### Request Endpoint + +`POST /user` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +```json +{ + "firstName": "", + "lastName": "", + "email": "", + "role": "" +} +``` + +### Request Response + +```json +{ + "id": "", + "firstName": "", + "lastName": "", + "email": "" +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/gorgias/actions/create-user.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/gorgias/actions/create-user.md) + + + diff --git a/integrations/gorgias/actions/create-user.ts b/integrations/gorgias/actions/create-user.ts new file mode 100644 index 000000000..5edb1b218 --- /dev/null +++ b/integrations/gorgias/actions/create-user.ts @@ -0,0 +1,52 @@ +import type { NangoAction, ProxyConfiguration, GorgiasCreateUser, GorgiasUser } from '../../models'; +import { gorgiasCreateUserSchema } from '../schema.zod.js'; +import type { GorgiasCreateUserReq, GorgiasUserResponse } from '../types'; + +/** + * Creates a new user in Gorgias. + * + * @param {NangoAction} nango - The Nango action instance. + * @param {GorgiasCreateUser} input - The input data for creating a user. + * @returns {Promise} - A promise that resolves to the created Gorgias user. + * @throws {nango.ActionError} - Throws an error if the input validation fails. + */ +export default async function runAction(nango: NangoAction, input: GorgiasCreateUser): Promise { + const parsedInput = gorgiasCreateUserSchema.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 data: GorgiasCreateUserReq = { + name: `${parsedInput.data.firstName} ${parsedInput.data.lastName}`, + email: parsedInput.data.email.toLowerCase(), + role: { + name: parsedInput.data.role || 'agent' + } + }; + + const config: ProxyConfiguration = { + // https://developers.gorgias.com/reference/create-user + endpoint: '/api/users', + retries: 10, + data + }; + + const response = await nango.post(config); + + const { data: dataResponse } = response; + + const user: GorgiasUser = { + id: dataResponse.id.toString(), + firstName: dataResponse.name.split(' ')[0] || '', + lastName: dataResponse.name.split(' ')[1] || '', + email: dataResponse.email + }; + + return user; +} diff --git a/integrations/gorgias/actions/delete-user.md b/integrations/gorgias/actions/delete-user.md new file mode 100644 index 000000000..96c02518c --- /dev/null +++ b/integrations/gorgias/actions/delete-user.md @@ -0,0 +1,46 @@ + +# Delete User + +## General Information + +- **Description:** Deletes a user in Gorgias +- **Version:** 0.0.1 +- **Group:** Users +- **Scopes:** `users:write` +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/gorgias/actions/delete-user.ts) + + +## Endpoint Reference + +### Request Endpoint + +`DELETE /users` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +```json +{ + "id": "" +} +``` + +### Request Response + +```json +{ + "success": "" +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/gorgias/actions/delete-user.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/gorgias/actions/delete-user.md) + + + diff --git a/integrations/gorgias/actions/delete-user.ts b/integrations/gorgias/actions/delete-user.ts new file mode 100644 index 000000000..2403b1e14 --- /dev/null +++ b/integrations/gorgias/actions/delete-user.ts @@ -0,0 +1,30 @@ +import type { NangoAction, ProxyConfiguration, SuccessResponse, IdEntity } from '../../models'; + +/** + * Deletes a user based on the provided email address. + * + * @param {NangoAction} nango - The Nango action instance. + * @param {EmailEntity} input - The input containing the email of the user to be deleted. + * @throws {nango.ActionError} - Throws an error if the email is not provided. + * + * {@link https://developers.gorgias.com/reference/delete-user} for more information on the Gorgias API endpoint. + */ +export default async function runAction(nango: NangoAction, input: IdEntity): Promise { + if (!input.id) { + throw new nango.ActionError({ + message: 'Id is required to delete a user' + }); + } + + const config: ProxyConfiguration = { + // https://developers.gorgias.com/reference/delete-user + endpoint: `/api/users/${input.id}`, + retries: 10 + }; + + await nango.delete(config); + + return { + success: true + }; +} diff --git a/integrations/gorgias/fixtures/create-user.json b/integrations/gorgias/fixtures/create-user.json new file mode 100644 index 000000000..51639f33f --- /dev/null +++ b/integrations/gorgias/fixtures/create-user.json @@ -0,0 +1,6 @@ +{ + "email": "Johndo2l@testdomain.tld", + "firstName": "John", + "lastName": "Doe", + "role": "agent" +} diff --git a/integrations/gorgias/fixtures/delete-user.json b/integrations/gorgias/fixtures/delete-user.json new file mode 100644 index 000000000..bcc3f75a8 --- /dev/null +++ b/integrations/gorgias/fixtures/delete-user.json @@ -0,0 +1,3 @@ +{ + "id": "455843658" +} diff --git a/integrations/gorgias/mocks/create-user/input.json b/integrations/gorgias/mocks/create-user/input.json new file mode 100644 index 000000000..51639f33f --- /dev/null +++ b/integrations/gorgias/mocks/create-user/input.json @@ -0,0 +1,6 @@ +{ + "email": "Johndo2l@testdomain.tld", + "firstName": "John", + "lastName": "Doe", + "role": "agent" +} diff --git a/integrations/gorgias/mocks/create-user/output.json b/integrations/gorgias/mocks/create-user/output.json new file mode 100644 index 000000000..084246f27 --- /dev/null +++ b/integrations/gorgias/mocks/create-user/output.json @@ -0,0 +1,6 @@ +{ + "id": "455925547", + "firstName": "John", + "lastName": "Doe", + "email": "johndo2l@testdomain.tld" +} diff --git a/integrations/gorgias/mocks/delete-user/input.json b/integrations/gorgias/mocks/delete-user/input.json new file mode 100644 index 000000000..7041e1aee --- /dev/null +++ b/integrations/gorgias/mocks/delete-user/input.json @@ -0,0 +1,3 @@ +{ + "id": 455843658 +} diff --git a/integrations/gorgias/mocks/delete-user/output.json b/integrations/gorgias/mocks/delete-user/output.json new file mode 100644 index 000000000..33c0c847e --- /dev/null +++ b/integrations/gorgias/mocks/delete-user/output.json @@ -0,0 +1,3 @@ +{ + "success": true +} diff --git a/integrations/gorgias/mocks/nango/delete/proxy/api/users/454003867/delete-user/4541b3ef41998ebf4ba8bbdc07e5a0e661de7173.json b/integrations/gorgias/mocks/nango/delete/proxy/api/users/454003867/delete-user/4541b3ef41998ebf4ba8bbdc07e5a0e661de7173.json new file mode 100644 index 000000000..37c1cdf0c --- /dev/null +++ b/integrations/gorgias/mocks/nango/delete/proxy/api/users/454003867/delete-user/4541b3ef41998ebf4ba8bbdc07e5a0e661de7173.json @@ -0,0 +1,36 @@ +{ + "method": "delete", + "endpoint": "api/users/454003867", + "requestIdentityHash": "4541b3ef41998ebf4ba8bbdc07e5a0e661de7173", + "requestIdentity": { + "method": "delete", + "endpoint": "api/users/454003867", + "params": [], + "headers": [] + }, + "response": "", + "status": 204, + "headers": { + "date": "Thu, 16 Jan 2025 16:31:15 GMT", + "connection": "keep-alive", + "cf-ray": "902f7ea29d870372-NBO", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "6380cb49-dff3-478c", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2383", + "x-ratelimit-reset": "1737045094", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "vary": "Accept-Encoding", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/gorgias/mocks/nango/delete/proxy/api/users/455843658/delete-user/7dcd1de4b5f46d6b29659d53227eab98bb2f2729.json b/integrations/gorgias/mocks/nango/delete/proxy/api/users/455843658/delete-user/7dcd1de4b5f46d6b29659d53227eab98bb2f2729.json new file mode 100644 index 000000000..0d796baef --- /dev/null +++ b/integrations/gorgias/mocks/nango/delete/proxy/api/users/455843658/delete-user/7dcd1de4b5f46d6b29659d53227eab98bb2f2729.json @@ -0,0 +1,36 @@ +{ + "method": "delete", + "endpoint": "api/users/455843658", + "requestIdentityHash": "7dcd1de4b5f46d6b29659d53227eab98bb2f2729", + "requestIdentity": { + "method": "delete", + "endpoint": "api/users/455843658", + "params": [], + "headers": [] + }, + "response": "", + "status": 204, + "headers": { + "date": "Thu, 16 Jan 2025 17:48:09 GMT", + "connection": "keep-alive", + "cf-ray": "902fef4c4d4ab195-NBO", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "ffd3bd60-42a4-493f", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2396", + "x-ratelimit-reset": "1737049745", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "vary": "Accept-Encoding", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/gorgias/mocks/nango/post/proxy/api/users/create-user/04dd13300a8b792236860fa99c4bedeeaed97cab.json b/integrations/gorgias/mocks/nango/post/proxy/api/users/create-user/04dd13300a8b792236860fa99c4bedeeaed97cab.json new file mode 100644 index 000000000..34e3ea85f --- /dev/null +++ b/integrations/gorgias/mocks/nango/post/proxy/api/users/create-user/04dd13300a8b792236860fa99c4bedeeaed97cab.json @@ -0,0 +1,65 @@ +{ + "method": "post", + "endpoint": "api/users", + "requestIdentityHash": "9d7b0f7cb5b3c6b67ba90c8d88d94ee2637fa496", + "requestIdentity": { + "method": "post", + "endpoint": "api/users", + "params": [], + "headers": [["Content-Length", "77"]], + "data": "{\"name\":\"John Doe\",\"email\":\"johndo2l@testdomain.tld\",\"role\":{\"name\":\"agent\"}}" + }, + "response": { + "id": 455925547, + "active": true, + "bio": null, + "created_datetime": "2025-01-16T18:36:45.698253+00:00", + "country": null, + "deactivated_datetime": null, + "email": "johndo2l@testdomain.tld", + "external_id": null, + "firstname": "John", + "has_password": false, + "has_2fa_enabled": false, + "lastname": "Doe", + "language": null, + "meta": { + "sso": null, + "profile_picture_url": null + }, + "name": "John Doe", + "role": { + "name": "agent" + }, + "timezone": "Africa/Nairobi", + "updated_datetime": "2025-01-16T18:36:45.698257+00:00", + "uri": "https://testfx.gorgias.com/api/users/455925547/" + }, + "status": 200, + "headers": { + "date": "Thu, 16 Jan 2025 18:36:45 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "300", + "connection": "keep-alive", + "cf-ray": "9030367f8bf9b19c-NBO", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"203-qyNWr+SpWX1Xin2YCrydsiMymfQ\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "62bf5cba-d312-49bf", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2395", + "x-ratelimit-reset": "1737052659", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/gorgias/nango.yaml b/integrations/gorgias/nango.yaml index 0a7bbaca7..fe9223860 100644 --- a/integrations/gorgias/nango.yaml +++ b/integrations/gorgias/nango.yaml @@ -15,6 +15,19 @@ integrations: path: /tickets group: Tickets version: 1.0.1 + users: + description: | + Fetches the list of users + endpoint: + method: GET + path: /users + group: Users + sync_type: full + track_deletes: true + runs: every 6 hours + output: GorgiasUser + scopes: + - users:read actions: create-ticket: description: | @@ -30,7 +43,35 @@ integrations: path: /ticket group: Tickets output: Ticket + create-user: + description: | + Creates a new user with a role in Gorgias. Defaults to agent if a role is not provided + input: GorgiasCreateUser + output: GorgiasUser + endpoint: + method: POST + path: /user + group: Users + scopes: + - users:write + delete-user: + description: Deletes a user in Gorgias + output: SuccessResponse + input: IdEntity + endpoint: + method: DELETE + path: /users + group: Users + scopes: + - users:write models: + #generic + IdEntity: + id: string + SuccessResponse: + success: boolean + ActionResponseError: + message: string Ticket: id: number assignee_user: AssigneeUser | null @@ -132,3 +173,15 @@ models: body_html: string body_text: string id: string + CreateUser: + firstName: string + lastName: string + email: string + GorgiasCreateUser: + __extends: CreateUser + role: admin | agent | basic-agent | lite-agent | observer-agent + GorgiasUser: + id: string + firstName: string + lastName: string + email: string diff --git a/integrations/gorgias/schema.zod.ts b/integrations/gorgias/schema.zod.ts index cbe1d1689..c53cdaab4 100644 --- a/integrations/gorgias/schema.zod.ts +++ b/integrations/gorgias/schema.zod.ts @@ -1,6 +1,18 @@ // 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 actionResponseErrorSchema = z.object({ + message: z.string() +}); + export const userSchema = z.object({ id: z.number(), firstname: z.string(), @@ -244,3 +256,25 @@ export const createTicketInputSchema = z.object({ messages: z.array(createTicketMessageSchema) }) }); + +export const createUserSchema = z.object({ + firstName: z.string(), + lastName: z.string(), + email: z.string() +}); + +export const gorgiasUserSchema = z.object({ + id: z.string(), + firstName: z.string(), + lastName: z.string(), + email: z.string() +}); + +export const gorgiasCreateUserSchema = z.object({ + firstName: z.string(), + lastName: z.string(), + email: z.string(), + role: z.union([z.literal('admin'), z.literal('agent'), z.literal('basic-agent'), z.literal('lite-agent'), z.literal('observer-agent')]) +}); + +export const anonymousGorgiasActionCreateuserOutputSchema = z.literal('CreateUserOutput'); diff --git a/integrations/gorgias/syncs/users.md b/integrations/gorgias/syncs/users.md new file mode 100644 index 000000000..f9adec8b9 --- /dev/null +++ b/integrations/gorgias/syncs/users.md @@ -0,0 +1,49 @@ + +# Users + +## General Information + +- **Description:** Fetches the list of users + +- **Version:** 0.0.1 +- **Group:** Users +- **Scopes:** `users:read` +- **Endpoint Type:** Sync +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/gorgias/syncs/users.ts) + + +## Endpoint Reference + +### Request Endpoint + +`GET /users` + +### Request Query Parameters + +- **modified_after:** `(optional, string)` A timestamp (e.g., `2023-05-31T11:46:13.390Z`) used to fetch records modified after this date and time. If not provided, all records are returned. The modified_after parameter is less precise than cursor, as multiple records may share the same modification timestamp. +- **limit:** `(optional, integer)` The maximum number of records to return per page. Defaults to 100. +- **cursor:** `(optional, string)` A marker used to fetch records modified after a specific point in time.If not provided, all records are returned.Each record includes a cursor value found in _nango_metadata.cursor.Save the cursor from the last record retrieved to track your sync progress.Use the cursor parameter together with the limit parameter to paginate through records.The cursor is more precise than modified_after, as it can differentiate between records with the same modification timestamp. +- **filter:** `(optional, added | updated | deleted)` Filter to only show results that have been added or updated or deleted. + +### Request Body + +_No request body_ + +### Request Response + +```json +{ + "id": "", + "firstName": "", + "lastName": "", + "email": "" +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/gorgias/syncs/users.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/gorgias/syncs/users.md) + + + diff --git a/integrations/gorgias/syncs/users.ts b/integrations/gorgias/syncs/users.ts new file mode 100644 index 000000000..29e262d55 --- /dev/null +++ b/integrations/gorgias/syncs/users.ts @@ -0,0 +1,40 @@ +import type { NangoSync, ProxyConfiguration, GorgiasUser } from '../../models'; +import type { GorgiasUserResponse } from '../types'; + +/** + * Fetches data from the Gorgias API and saves it using NangoSync. + * + * @param {NangoSync} nango - The NangoSync instance used for fetching and saving data. + * + * @returns {Promise} A promise that resolves when the data fetching and saving process is complete. + * + * {@link https://developers.gorgias.com/reference/list-users} for more information on the Gorgias API endpoint. + */ +export default async function fetchData(nango: NangoSync) { + const config: ProxyConfiguration = { + // https://developers.gorgias.com/reference/list-users + endpoint: `/api/users`, + retries: 10, + paginate: { + type: 'cursor', + cursor_path_in_response: 'meta.next_cursor', + cursor_name_in_request: 'cursor', + response_path: 'data', + limit: 30, + limit_name_in_request: 'limit' + } + }; + + for await (const zUsers of nango.paginate(config)) { + const users: GorgiasUser[] = zUsers.map((zUser: GorgiasUserResponse) => { + return { + id: zUser.id.toString(), + firstName: zUser.firstname, + lastName: zUser.lastname, + email: zUser.email + }; + }); + + await nango.batchSave(users, 'GorgiasUser'); + } +} diff --git a/integrations/gorgias/tests/gorgias-create-user.test.ts b/integrations/gorgias/tests/gorgias-create-user.test.ts new file mode 100644 index 000000000..0791d3360 --- /dev/null +++ b/integrations/gorgias/tests/gorgias-create-user.test.ts @@ -0,0 +1,19 @@ +import { vi, expect, it, describe } from 'vitest'; + +import runAction from '../actions/create-user.js'; + +describe('gorgias create-user tests', () => { + const nangoMock = new global.vitest.NangoActionMock({ + dirname: __dirname, + name: 'create-user', + Model: 'GorgiasUser' + }); + + 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); + }); +}); diff --git a/integrations/gorgias/tests/gorgias-delete-user.test.ts b/integrations/gorgias/tests/gorgias-delete-user.test.ts new file mode 100644 index 000000000..b43c43c16 --- /dev/null +++ b/integrations/gorgias/tests/gorgias-delete-user.test.ts @@ -0,0 +1,19 @@ +import { vi, expect, it, describe } from 'vitest'; + +import runAction from '../actions/delete-user.js'; + +describe('gorgias 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); + }); +}); diff --git a/integrations/gorgias/types.ts b/integrations/gorgias/types.ts index 67a1382c5..e52ac75da 100644 --- a/integrations/gorgias/types.ts +++ b/integrations/gorgias/types.ts @@ -373,3 +373,26 @@ interface SettingsItem { export interface GorgiasSettingsResponse { data: SettingsItem[]; } +export interface GorgiasUserResponse { + id: number; + active: boolean; + name: string; + email: string; + bio?: string | null; + country?: string | null; + external_id?: string; + language?: string | null; + firstname: string; + lastname: string; + meta?: Record; + role: { + name: 'admin' | 'agent' | 'basic-agent' | 'lite-agent' | 'observer-agent' | 'bot'; + }; +} +export interface GorgiasCreateUserReq { + name: string; + email: string; + role: { + name: 'admin' | 'agent' | 'basic-agent' | 'lite-agent' | 'observer-agent' | 'bot'; + }; +} diff --git a/integrations/intercom/actions/whoami.md b/integrations/intercom/actions/whoami.md new file mode 100644 index 000000000..a559d8fff --- /dev/null +++ b/integrations/intercom/actions/whoami.md @@ -0,0 +1,43 @@ + +# Whoami + +## General Information + +- **Description:** Fetch current user information +- **Version:** 0.0.1 +- **Group:** Users +- **Scopes:** `Read admins` +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/intercom/actions/whoami.ts) + + +## Endpoint Reference + +### Request Endpoint + +`GET /whoami` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +_No request body_ + +### Request Response + +```json +{ + "id": "", + "email": "" +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/intercom/actions/whoami.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/intercom/actions/whoami.md) + + + diff --git a/integrations/intercom/actions/whoami.ts b/integrations/intercom/actions/whoami.ts new file mode 100644 index 000000000..743d1da58 --- /dev/null +++ b/integrations/intercom/actions/whoami.ts @@ -0,0 +1,19 @@ +import type { NangoAction, ProxyConfiguration, UserInformation } from '../../models'; +import type { WhoAmIResponse } from '../types'; + +export default async function runAction(nango: NangoAction): Promise { + const config: ProxyConfiguration = { + // https://developers.intercom.com/docs/references/1.1/rest-api/admins/viewing-the-current-admin + endpoint: 'me', + retries: 10 + }; + + const { data } = await nango.get(config); + + const user: UserInformation = { + id: data.id, + email: data.email + }; + + return user; +} diff --git a/integrations/intercom/nango.yaml b/integrations/intercom/nango.yaml index f016325ae..9237c0d44 100644 --- a/integrations/intercom/nango.yaml +++ b/integrations/intercom/nango.yaml @@ -22,6 +22,15 @@ integrations: path: /contact output: SuccessResponse input: IdEntity + whoami: + description: Fetch current user information + output: UserInformation + endpoint: + method: GET + path: /whoami + group: Users + scopes: + - 'Read admins' syncs: conversations: runs: every 6 hours @@ -195,3 +204,6 @@ models: last_seen_at?: number owner_id?: string unsubscribed_from_emails?: boolean + UserInformation: + id: string + email: string diff --git a/integrations/intercom/types.ts b/integrations/intercom/types.ts index 4e113a28d..57af4c6e9 100644 --- a/integrations/intercom/types.ts +++ b/integrations/intercom/types.ts @@ -435,3 +435,26 @@ export interface IntercomAdminUser { team_ids: string[]; team_priority_level: unknown; } + +export interface WhoAmIResponse { + type: 'admin'; + id: string; + email: string; + name: string; + email_verified: boolean; + app: { + type: 'app'; + id_code: string; + name: string; + created_at: number; + secure: boolean; + identity_verification: boolean; + timezone: string; + region: string; + }; + avatar: { + type: 'avatar'; + image_url: string; + }; + has_inbox_seat: boolean; +} diff --git a/integrations/jira/helpers/get-cloud-data.ts b/integrations/jira/helpers/get-cloud-data.ts index 7c7a58b6f..232d4598c 100644 --- a/integrations/jira/helpers/get-cloud-data.ts +++ b/integrations/jira/helpers/get-cloud-data.ts @@ -13,8 +13,8 @@ export async function getCloudData(nango: NangoAction): Promise<{ cloudId: strin const connection = await nango.getConnection(); const metadata = await nango.getMetadata(); - const cloudId = connection.connection_config['cloudId'] ?? metadata.cloudId; - const baseUrl = connection.connection_config['baseUrl'] ?? metadata.baseUrl; + const cloudId = connection.connection_config['cloudId'] ?? metadata?.cloudId; + const baseUrl = connection.connection_config['baseUrl'] ?? metadata?.baseUrl; if (cloudId && baseUrl) { return { cloudId, baseUrl }; diff --git a/integrations/jira/syncs/issue-types.md b/integrations/jira/syncs/issue-types.md index 87acf8898..4e604a33d 100644 --- a/integrations/jira/syncs/issue-types.md +++ b/integrations/jira/syncs/issue-types.md @@ -40,6 +40,20 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "projectIdsToSync": [ + { + "id": "" + } + ], + "cloudId?": "", + "baseUrl?": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/jira/syncs/issue-types.ts) diff --git a/integrations/jira/syncs/issues.md b/integrations/jira/syncs/issues.md index bf5e27c11..2d51f7d7e 100644 --- a/integrations/jira/syncs/issues.md +++ b/integrations/jira/syncs/issues.md @@ -63,6 +63,20 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "projectIdsToSync": [ + { + "id": "" + } + ], + "cloudId?": "", + "baseUrl?": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/jira/syncs/issues.ts) diff --git a/integrations/metabase/mocks/nango/get/proxy/api/user/users/a7db2686215dccc733982d0f8731a24c830d8077.json b/integrations/metabase/mocks/nango/get/proxy/api/user/users/a7db2686215dccc733982d0f8731a24c830d8077.json index 784adf245..dd9e5c56b 100644 --- a/integrations/metabase/mocks/nango/get/proxy/api/user/users/a7db2686215dccc733982d0f8731a24c830d8077.json +++ b/integrations/metabase/mocks/nango/get/proxy/api/user/users/a7db2686215dccc733982d0f8731a24c830d8077.json @@ -230,8 +230,8 @@ "common_name": "John Dough" }, { - "email": "khaliq+trial@nango.dev", - "first_name": "Khaliq", + "email": "john+trial@some-email.com", + "first_name": "Johnny", "locale": null, "last_login": "2025-01-07T12:19:34.090199Z", "is_active": true, @@ -241,11 +241,11 @@ "is_superuser": true, "login_attributes": null, "id": 1, - "last_name": "Gant", + "last_name": "Doeseph", "date_joined": "2025-01-06T07:58:52.810204Z", "sso_source": null, "personal_collection_id": 6, - "common_name": "Khaliq Gant" + "common_name": "Johnny Doeseph" }, { "email": "john.dimath@example.com", diff --git a/integrations/metabase/mocks/users/User/batchSave.json b/integrations/metabase/mocks/users/User/batchSave.json index 23ea7707a..02c6720d3 100644 --- a/integrations/metabase/mocks/users/User/batchSave.json +++ b/integrations/metabase/mocks/users/User/batchSave.json @@ -73,9 +73,9 @@ }, { "id": "1", - "firstName": "Khaliq", - "lastName": "Gant", - "email": "khaliq+trial@nango.dev" + "firstName": "Johnny", + "lastName": "Doeseph", + "email": "john+trial@some-email.com" }, { "id": "4", diff --git a/integrations/microsoft-teams/syncs/users.md b/integrations/microsoft-teams/syncs/users.md index b38905c11..c46b97ca9 100644 --- a/integrations/microsoft-teams/syncs/users.md +++ b/integrations/microsoft-teams/syncs/users.md @@ -58,6 +58,16 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "orgsToSync": [ + "" + ] +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/microsoft-teams/syncs/users.ts) diff --git a/integrations/notion/actions/create-database-row.md b/integrations/notion/actions/create-database-row.md new file mode 100644 index 000000000..db3cbf35a --- /dev/null +++ b/integrations/notion/actions/create-database-row.md @@ -0,0 +1,74 @@ + +# Create Database Row + +## General Information + +- **Description:** Create a new row in a specified Notion database. +The properties are mapped to Notion-compatible formats based on the database schema. +Supported property types include: +- `title` (string): Creates a title property. +- `select` (string): Creates a select property. +- `multi_select` (array of strings): Creates a multi-select property. +- `status` (string): Creates a status property. +- `date` (string or object): Supports ISO date strings or objects with a `start` field. +- `checkbox` (boolean): Creates a checkbox property. +- `number` (number): Creates a number property. +- `url` (string): Creates a URL property. +- `email` (string): Creates an email property. +- `phone_number` (string): Creates a phone number property. +- `rich_text` (string): Creates a rich text property. +- `relation` (array of IDs): Creates a relation property. + +- **Version:** 1.0.0 +- **Group:** Databases +- **Scopes:** _None_ +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/notion/actions/create-database-row.ts) + + +## Endpoint Reference + +### Request Endpoint + +`POST /databases/row` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +```json +{ + "databaseId": "", + "properties": "" +} +``` + +### Request Response + +```json +{ + "success": "", + "addedProperties": [ + { + "propertyKey": "", + "notionValue": "" + } + ] +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/notion/actions/create-database-row.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/notion/actions/create-database-row.md) + + +### Steps to Use +1. **Find the `databaseId`**: + - Use the `databases` sync (refer to the implementation [here](https://github.com/NangoHQ/integration-templates/blob/main/integrations/notion/syncs/databases.ts)) to retrieve a list of databases and their respective IDs. +2. **Provide `properties`**: + - The `properties` input is a key-value pair where the key is the name of the database column, and the value is the data you want to populate in that column. +3. **Dynamic Mapping**: + - This action dynamically formulates the data to match the database schema in Notion. diff --git a/integrations/notion/actions/create-database-row.ts b/integrations/notion/actions/create-database-row.ts new file mode 100644 index 000000000..573aa4bb4 --- /dev/null +++ b/integrations/notion/actions/create-database-row.ts @@ -0,0 +1,100 @@ +import type { NangoAction, ProxyConfiguration, RowEntry, CreateDatabaseRowInput, CreateDatabaseRowOutput } from '../../models'; +import { mapPropertiesToNotionFormat } from '../helpers/map-properties.js'; +import { createDatabaseRowInputSchema, notionPropertySchema } from '../schema.zod.js'; +import type { NotionCreatePageResponse, Database as NotionDatabase, NotionGetDatabaseResponse } from '../types.js'; + +export default async function runAction(nango: NangoAction, input: CreateDatabaseRowInput): Promise { + const parseResult = createDatabaseRowInputSchema.safeParse(input); + if (!parseResult.success) { + const message = parseResult.error.errors.map((err) => `${err.message} at path ${err.path.join('.')}`).join('; '); + throw new nango.ActionError({ + message: `Invalid create-database-row input: ${message}` + }); + } + const { databaseId, properties } = parseResult.data; + + const databaseResponse = await nango.get({ + // https://developers.notion.com/reference/retrieve-a-database + endpoint: `/v1/databases/${databaseId}`, + retries: 10 + }); + const data = databaseResponse.data; + + const schema: Record = {}; + + if (data.properties && Object.keys(data.properties).length > 0) { + for (const [key, propertyDef] of Object.entries(data.properties)) { + const check = notionPropertySchema.safeParse(propertyDef); + if (check.success) { + schema[key] = { type: check.data.type }; + } + } + } else { + // NOTE: If the top-level database object doesn't provide properties, + // we iterate over all rows to infer them from existing usage. + // This ensures we don't miss columns that haven't been filled out yet. + const queryProxyConfig: ProxyConfiguration = { + method: 'POST', + // https://developers.notion.com/reference/post-database-query + endpoint: `/v1/databases/${databaseId}/query` + }; + + const entries: RowEntry[] = []; + for await (const dbPages of nango.paginate(queryProxyConfig)) { + for (const dbPage of dbPages) { + const id = dbPage.id; + const row = dbPage.properties; + entries.push({ id, row }); + } + } + + for (const entry of entries) { + for (const [key, propertyInfo] of Object.entries(entry.row)) { + if (!schema[key]) { + const check = notionPropertySchema.safeParse(propertyInfo); + if (check.success) { + schema[key] = { type: check.data.type }; + } + } + } + } + } + + const schemaMap: Record = {}; + for (const realKey of Object.keys(schema)) { + schemaMap[realKey.toLowerCase()] = realKey; + } + + const finalUserProps: Record = {}; + for (const [userKey, userValue] of Object.entries(properties)) { + const userKeyLower = userKey.toLowerCase(); + if (schemaMap[userKeyLower]) { + const realSchemaKey = schemaMap[userKeyLower]; + finalUserProps[realSchemaKey] = userValue; + } + } + + const dbRow = mapPropertiesToNotionFormat(schema, finalUserProps); + + const createConfig: ProxyConfiguration = { + // https://developers.notion.com/reference/post-page + endpoint: '/v1/pages', + data: { + parent: { database_id: databaseId }, + properties: dbRow + }, + retries: 5 + }; + + await nango.post(createConfig); + + const addedProperties = Object.entries(dbRow).map(([propertyKey, notionValue]) => ({ + propertyKey, + notionValue + })); + + return { + success: true, + addedProperties + }; +} diff --git a/integrations/notion/fixtures/create-database-row.json b/integrations/notion/fixtures/create-database-row.json new file mode 100644 index 000000000..bca844db3 --- /dev/null +++ b/integrations/notion/fixtures/create-database-row.json @@ -0,0 +1,16 @@ +{ + "databaseId": "17ad4eb5-37cf-80f9-ae08-d78355254dc8", + "properties": { + "name": "Goals for 2005", + "Priority": "P3", + "Labels": ["Bug", "UI/UX"], + "Status": "Done", + "Due Date": "2024-01-31", + "Done?": true, + "Count": 42, + "Link": "https://example.com", + "Contact": "person@example.com", + "Phone": "+254712345678", + "Description": "Some longer text content..." + } +} diff --git a/integrations/notion/helpers/map-properties.ts b/integrations/notion/helpers/map-properties.ts new file mode 100644 index 000000000..14f413af4 --- /dev/null +++ b/integrations/notion/helpers/map-properties.ts @@ -0,0 +1,41 @@ +export function mapPropertiesToNotionFormat(schema: Record, userInput: Record): Record { + const dbRow: Record = {}; + + for (const [key, value] of Object.entries(userInput)) { + const notionType = schema[key]?.type; + + if (notionType === 'title' && typeof value === 'string') { + dbRow[key] = { title: [{ text: { content: value } }] }; + } else if (notionType === 'select' && typeof value === 'string') { + dbRow[key] = { select: { name: value } }; + } else if (notionType === 'multi_select' && Array.isArray(value)) { + dbRow[key] = { multi_select: value.map((v) => ({ name: v })) }; + } else if (notionType === 'status' && typeof value === 'string') { + dbRow[key] = { status: { name: value } }; + } else if (notionType === 'date') { + if (typeof value === 'string') { + dbRow[key] = { date: { start: value } }; + } else if (value?.start) { + dbRow[key] = { date: value }; + } + } else if (notionType === 'checkbox' && typeof value === 'boolean') { + dbRow[key] = { checkbox: value }; + } else if (notionType === 'number' && typeof value === 'number') { + dbRow[key] = { number: value }; + } else if (notionType === 'url' && typeof value === 'string') { + dbRow[key] = { url: value }; + } else if (notionType === 'email' && typeof value === 'string') { + dbRow[key] = { email: value }; + } else if (notionType === 'phone_number' && typeof value === 'string') { + dbRow[key] = { phone_number: value }; + } else if (notionType === 'rich_text' && typeof value === 'string') { + dbRow[key] = { rich_text: [{ text: { content: value } }] }; + } else if (notionType === 'relation' && Array.isArray(value)) { + dbRow[key] = { relation: value.map((id) => ({ id })) }; + } else { + // Unmapped + } + } + + return dbRow; +} diff --git a/integrations/notion/mocks/create-database-row/input.json b/integrations/notion/mocks/create-database-row/input.json new file mode 100644 index 000000000..bca844db3 --- /dev/null +++ b/integrations/notion/mocks/create-database-row/input.json @@ -0,0 +1,16 @@ +{ + "databaseId": "17ad4eb5-37cf-80f9-ae08-d78355254dc8", + "properties": { + "name": "Goals for 2005", + "Priority": "P3", + "Labels": ["Bug", "UI/UX"], + "Status": "Done", + "Due Date": "2024-01-31", + "Done?": true, + "Count": 42, + "Link": "https://example.com", + "Contact": "person@example.com", + "Phone": "+254712345678", + "Description": "Some longer text content..." + } +} diff --git a/integrations/notion/mocks/create-database-row/output.json b/integrations/notion/mocks/create-database-row/output.json new file mode 100644 index 000000000..91aafcbdf --- /dev/null +++ b/integrations/notion/mocks/create-database-row/output.json @@ -0,0 +1,33 @@ +{ + "success": true, + "addedProperties": [ + { + "propertyKey": "Name", + "notionValue": { + "title": [ + { + "text": { + "content": "Goals for 2005" + } + } + ] + } + }, + { + "propertyKey": "Priority", + "notionValue": { + "select": { + "name": "P3" + } + } + }, + { + "propertyKey": "Status", + "notionValue": { + "select": { + "name": "Done" + } + } + } + ] +} diff --git a/integrations/notion/mocks/nango/get/proxy/v1/databases/17ad4eb5-37cf-80f9-ae08-d78355254dc8/create-database-row/f52e378d266785eb5b1a29b6c819742b315b0bd6.json b/integrations/notion/mocks/nango/get/proxy/v1/databases/17ad4eb5-37cf-80f9-ae08-d78355254dc8/create-database-row/f52e378d266785eb5b1a29b6c819742b315b0bd6.json new file mode 100644 index 000000000..de411d01e --- /dev/null +++ b/integrations/notion/mocks/nango/get/proxy/v1/databases/17ad4eb5-37cf-80f9-ae08-d78355254dc8/create-database-row/f52e378d266785eb5b1a29b6c819742b315b0bd6.json @@ -0,0 +1,271 @@ +{ + "method": "get", + "endpoint": "v1/databases/17ad4eb5-37cf-80f9-ae08-d78355254dc8", + "requestIdentityHash": "f52e378d266785eb5b1a29b6c819742b315b0bd6", + "requestIdentity": { + "method": "get", + "endpoint": "v1/databases/17ad4eb5-37cf-80f9-ae08-d78355254dc8", + "params": [], + "headers": [] + }, + "response": { + "object": "database", + "id": "17ad4eb5-37cf-80f9-ae08-d78355254dc8", + "cover": null, + "icon": { + "type": "external", + "external": { + "url": "https://www.notion.so/icons/activity_blue.svg" + } + }, + "created_time": "2025-01-13T17:08:00.000Z", + "created_by": { + "object": "user", + "id": "171d872b-594c-811d-9c24-000291467762" + }, + "last_edited_by": { + "object": "user", + "id": "171d872b-594c-811d-9c24-000291467762" + }, + "last_edited_time": "2025-01-16T17:49:00.000Z", + "title": [ + { + "type": "text", + "text": { + "content": "Goals", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Goals", + "href": null + } + ], + "description": [], + "is_inline": false, + "properties": { + "Status 1": { + "id": "%3BIPx", + "name": "Status 1", + "type": "status", + "status": { + "options": [ + { + "id": "7ee8b3b1-e2bf-4b88-ab1b-bd78f9d447bd", + "name": "Not started", + "color": "default", + "description": null + }, + { + "id": "c5a401e7-1001-479e-878c-7c72b5b914a4", + "name": "In progress", + "color": "blue", + "description": null + }, + { + "id": "c0ba5796-dddb-4ed3-8062-83fa4bd4aa27", + "name": "Done", + "color": "green", + "description": null + } + ], + "groups": [ + { + "id": "ec89a639-8ba3-4d98-bb72-82af551f0620", + "name": "To-do", + "color": "gray", + "option_ids": ["7ee8b3b1-e2bf-4b88-ab1b-bd78f9d447bd"] + }, + { + "id": "aba046aa-8720-47bd-afe0-bafa170fee74", + "name": "In progress", + "color": "blue", + "option_ids": ["c5a401e7-1001-479e-878c-7c72b5b914a4"] + }, + { + "id": "12eae27b-b140-4489-b738-8326b028d3b1", + "name": "Complete", + "color": "green", + "option_ids": ["c0ba5796-dddb-4ed3-8062-83fa4bd4aa27"] + } + ] + } + }, + "Parent Goal": { + "id": "F%40Qh", + "name": "Parent Goal", + "type": "relation", + "relation": { + "database_id": "17ad4eb5-37cf-80f9-ae08-d78355254dc8", + "type": "dual_property", + "dual_property": { + "synced_property_name": "Child Goals", + "synced_property_id": "%7DNvF" + } + } + }, + "Priority": { + "id": "_bh%7B", + "name": "Priority", + "type": "select", + "select": { + "options": [ + { + "id": "18cff4f2-5638-4b46-bc72-a38668b1bae2", + "name": "P0", + "color": "green", + "description": null + }, + { + "id": "c2bfeab8-815c-416a-bff5-feec98c565b7", + "name": "P1", + "color": "yellow", + "description": null + }, + { + "id": "1282a943-c3c4-4fee-9a34-3f0ebab8db1f", + "name": "P2", + "color": "red", + "description": null + }, + { + "id": "b8fff526-6c86-460e-900b-5561e4f484c8", + "name": "High", + "color": "pink", + "description": null + }, + { + "id": "da9866fd-e31d-4e75-bbc4-1ef4c2c2ef07", + "name": "-2", + "color": "blue", + "description": null + }, + { + "id": "698656f8-655f-45ef-9df8-e9b37177d912", + "name": "P3", + "color": "brown", + "description": null + } + ] + } + }, + "Status": { + "id": "_zwC", + "name": "Status", + "type": "select", + "select": { + "options": [ + { + "id": "; + +export const createDatabaseRowInputSchema = z.object({ + databaseId: z.string().min(1, 'Database ID is required.'), + properties: z.record(z.any()).optional().default({}) +}); diff --git a/integrations/notion/tests/map-properties-to-notion-format.test.ts b/integrations/notion/tests/map-properties-to-notion-format.test.ts new file mode 100644 index 000000000..503690033 --- /dev/null +++ b/integrations/notion/tests/map-properties-to-notion-format.test.ts @@ -0,0 +1,100 @@ +// mapPropertiesToNotionFormat.test.ts +import { describe, it, expect } from 'vitest'; +import { mapPropertiesToNotionFormat } from '../helpers/map-properties'; + +describe('mapPropertiesToNotionFormat', () => { + it('handles title, select, and relation', () => { + const schema = { + Name: { type: 'title' }, + Priority: { type: 'select' }, + 'Parent Goal': { type: 'relation' } + }; + + const userInput = { + Name: 'Increase website visits by 10%', + Priority: 'P1', + 'Parent Goal': ['another-page-id'] + }; + + const result = mapPropertiesToNotionFormat(schema, userInput); + + expect(result).toEqual({ + Name: { + title: [{ text: { content: 'Increase website visits by 10%' } }] + }, + Priority: { select: { name: 'P1' } }, + 'Parent Goal': { relation: [{ id: 'another-page-id' }] } + }); + }); + + it('handles multi_select, date, checkbox, number', () => { + const schema = { + Labels: { type: 'multi_select' }, + 'Due Date': { type: 'date' }, + 'Done?': { type: 'checkbox' }, + Count: { type: 'number' } + }; + + const userInput = { + Labels: ['Bug', 'UI/UX'], + 'Due Date': '2024-01-31', + 'Done?': true, + Count: 42 + }; + + const result = mapPropertiesToNotionFormat(schema, userInput); + + expect(result).toEqual({ + Labels: { multi_select: [{ name: 'Bug' }, { name: 'UI/UX' }] }, + 'Due Date': { date: { start: '2024-01-31' } }, + 'Done?': { checkbox: true }, + Count: { number: 42 } + }); + }); + + it('handles status, email, phone, rich_text', () => { + const schema = { + Status: { type: 'status' }, + Contact: { type: 'email' }, + Phone: { type: 'phone_number' }, + Description: { type: 'rich_text' } + }; + + const userInput = { + Status: 'In progress', + Contact: 'person@example.com', + Phone: '+254712345678', + Description: 'Some text content...' + }; + + const result = mapPropertiesToNotionFormat(schema, userInput); + + expect(result).toEqual({ + Status: { status: { name: 'In progress' } }, + Contact: { email: 'person@example.com' }, + Phone: { phone_number: '+254712345678' }, + Description: { + rich_text: [{ text: { content: 'Some text content...' } }] + } + }); + }); + + it('skips unmapped properties or invalid types', () => { + const schema = { + Name: { type: 'title' }, + Priority: { type: 'select' } + }; + + const userInput = { + Name: 123, + Priority: 'High', + Extra: '???' + }; + + const result = mapPropertiesToNotionFormat(schema, userInput); + + expect(result).toEqual({ + Priority: { select: { name: 'High' } } + }); + }); +}); diff --git a/integrations/notion/types.ts b/integrations/notion/types.ts index 1e856db3c..8a4212d57 100644 --- a/integrations/notion/types.ts +++ b/integrations/notion/types.ts @@ -99,6 +99,17 @@ export interface Database extends BaseObject { properties: Record; } +export interface NotionGetDatabaseResponse { + object: 'database'; + id: string; + properties?: Record; +} + +export interface NotionCreatePageResponse { + object: 'page'; + id: string; +} + export interface BlockPage { object: 'block'; id: string; diff --git a/integrations/outlook/syncs/emails.md b/integrations/outlook/syncs/emails.md index 5450d80f7..0b4288792 100644 --- a/integrations/outlook/syncs/emails.md +++ b/integrations/outlook/syncs/emails.md @@ -53,6 +53,14 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "backfillPeriodMs": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/outlook/syncs/emails.ts) diff --git a/integrations/quickbooks/helpers/cdc-paginate.ts b/integrations/quickbooks/helpers/cdc-paginate.ts new file mode 100644 index 000000000..1c8e6bcdc --- /dev/null +++ b/integrations/quickbooks/helpers/cdc-paginate.ts @@ -0,0 +1,41 @@ +import type { NangoSync, ProxyConfiguration } from '../../models'; +import type { CDCConfig } from '../types'; +import { getCompany } from '../utils/get-company.js'; + +export async function* cdcPaginate(nango: NangoSync, config: CDCConfig): AsyncGenerator { + const BATCH_SIZE = 1000; // Max Quickbooks Server responses can handle + const startSyncPoint = config.lastSyncDate; + + const realmId = await getCompany(nango); // Get company connection details + + const proxyConfig: ProxyConfiguration = { + // https://developer.intuit.com/app/developer/qbo/docs/learn/explore-the-quickbooks-online-api/change-data-capture + endpoint: `/v3/company/${realmId}/cdc`, + params: { + entities: config.entity, + changedSince: startSyncPoint.toISOString() + }, + headers: { + 'Content-Type': 'text/plain' + }, + paginate: { + type: 'offset', + offset_name_in_request: 'startPosition', + response_path: `CDCResponse[0].QueryResponse[0].${config.entity}`, + limit_name_in_request: 'maxResults', + limit: BATCH_SIZE + }, + retries: 3 + }; + + await nango.log('Starting CDC sync for entity:', { + entity: config.entity, + changedSince: startSyncPoint.toISOString() + }); + + for await (const records of nango.paginate(proxyConfig)) { + if (records.length > 0) { + yield records; + } + } +} diff --git a/integrations/quickbooks/helpers/paginate.ts b/integrations/quickbooks/helpers/paginate.ts index d546002be..78439067b 100644 --- a/integrations/quickbooks/helpers/paginate.ts +++ b/integrations/quickbooks/helpers/paginate.ts @@ -1,5 +1,6 @@ import type { NangoSync, ProxyConfiguration } from '../../models'; import { getCompany } from '../utils/get-company.js'; +import { cdcPaginate } from './cdc-paginate.js'; export interface PaginationParams { model: string; @@ -26,6 +27,17 @@ export async function* paginate( throw new Error("'model' parameter is required."); } + const MAX_LOOKBACK_DAYS = 29; // Quickbooks CDC only allows 30 days of lookback + const thirtyDaysAgo = new Date(); + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - MAX_LOOKBACK_DAYS); + if (nango.lastSyncDate && nango.lastSyncDate > thirtyDaysAgo) { + yield* cdcPaginate(nango, { + entity: model, + lastSyncDate: nango.lastSyncDate + }); + return; + } + let startPosition = initialPage; const responseKey = 'QueryResponse'; // Constant across all diff --git a/integrations/quickbooks/mappers/to-invoice.ts b/integrations/quickbooks/mappers/to-invoice.ts index 926f617b5..45fb22e7b 100644 --- a/integrations/quickbooks/mappers/to-invoice.ts +++ b/integrations/quickbooks/mappers/to-invoice.ts @@ -11,8 +11,8 @@ import { mapReference } from '../utils/map-reference.js'; */ export function toInvoice(invoice: QuickBooksInvoice): Invoice { return { - created_at: new Date(invoice.MetaData?.CreateTime).toISOString(), - updated_at: new Date(invoice.MetaData?.LastUpdatedTime).toISOString(), + created_at: invoice.MetaData?.CreateTime ? new Date(invoice.MetaData.CreateTime).toISOString() : '', + updated_at: new Date(invoice.MetaData.LastUpdatedTime).toISOString(), id: invoice.Id, txn_date: invoice.TxnDate, due_date: invoice.DueDate, diff --git a/integrations/quickbooks/mappers/to-journal-entry.ts b/integrations/quickbooks/mappers/to-journal-entry.ts index 5188a1439..c55a07d42 100644 --- a/integrations/quickbooks/mappers/to-journal-entry.ts +++ b/integrations/quickbooks/mappers/to-journal-entry.ts @@ -1,33 +1,35 @@ -import type { QuickBooksJournalEntry } from '../types'; +import type { QuickBooksJournalEntry, QuickBooksJournalLine } from '../types'; import type { JournalEntry, JournalEntryLine } from '../../models'; export function toJournalEntry(journalEntries: QuickBooksJournalEntry[]): JournalEntry[] { - return journalEntries.map( - (entry): JournalEntry => ({ + return journalEntries.map((entry): JournalEntry => { + return { id: entry.Id, date: new Date(entry.TxnDate).toISOString(), created_at: new Date(entry.MetaData.CreateTime).toISOString(), updated_at: new Date(entry.MetaData.LastUpdatedTime).toISOString(), currency: entry.CurrencyRef?.value?.toLowerCase(), note: entry.PrivateNote, - lines: entry.Line.map( - (line): JournalEntryLine => ({ - id: line.Id, - type: line.DetailType, - account_id: line.JournalEntryLineDetail.AccountRef.value, - account_name: line.JournalEntryLineDetail.AccountRef.name || '', - net_amount: line.Amount, - posting_type: line.JournalEntryLineDetail.PostingType, - description: line.Description, - entity_type: line.JournalEntryLineDetail.Entity?.Type, - entity_type_id: line.JournalEntryLineDetail.Entity?.EntityRef?.value, - entity_type_name: line.JournalEntryLineDetail.Entity?.EntityRef?.name, - department_id: line.JournalEntryLineDetail.DepartmentRef?.name, - department_name: line.JournalEntryLineDetail.DepartmentRef?.value, - class_id: line.JournalEntryLineDetail.ClassRef?.value, - class_name: line.JournalEntryLineDetail.ClassRef?.name - }) - ) - }) - ); + lines: entry.Line?.map(toJournalEntryLine) + }; + }); +} +// separate journal entry line to its own function +function toJournalEntryLine(line: QuickBooksJournalLine): JournalEntryLine { + return { + id: line.Id, + type: line.DetailType, + account_id: line.JournalEntryLineDetail.AccountRef.value, + account_name: line.JournalEntryLineDetail.AccountRef.name || '', + net_amount: line.Amount, + posting_type: line.JournalEntryLineDetail.PostingType, + description: line.Description || '', + entity_type: line.JournalEntryLineDetail.Entity?.Type, + entity_type_id: line.JournalEntryLineDetail.Entity?.EntityRef?.value, + entity_type_name: line.JournalEntryLineDetail.Entity?.EntityRef?.name, + department_id: line.JournalEntryLineDetail.DepartmentRef?.value, + department_name: line.JournalEntryLineDetail.DepartmentRef?.name, + class_id: line.JournalEntryLineDetail.ClassRef?.value, + class_name: line.JournalEntryLineDetail.ClassRef?.name + }; } diff --git a/integrations/quickbooks/mappers/to-payment.ts b/integrations/quickbooks/mappers/to-payment.ts index c12e53a1d..61b84e3a7 100644 --- a/integrations/quickbooks/mappers/to-payment.ts +++ b/integrations/quickbooks/mappers/to-payment.ts @@ -9,10 +9,13 @@ import { mapReference } from '../utils/map-reference.js'; * @returns Payment object representing QuickBooks payment information. */ export function toPayment(quickBooksPayment: QuickBooksPayment): Payment { + if (!quickBooksPayment.TxnDate || !quickBooksPayment.MetaData?.CreateTime || !quickBooksPayment.MetaData?.LastUpdatedTime) { + throw new Error(`Missing required fields for transfer ${quickBooksPayment.Id}`); + } const payment: Payment = { id: quickBooksPayment.Id, amount_cents: Math.round(quickBooksPayment.TotalAmt * 100), - customer_name: quickBooksPayment.CustomerRef.name ?? null, + customer_name: quickBooksPayment.CustomerRef?.name ?? null, txn_date: quickBooksPayment.TxnDate, created_at: new Date(quickBooksPayment.MetaData.CreateTime).toISOString(), updated_at: new Date(quickBooksPayment.MetaData.LastUpdatedTime).toISOString() diff --git a/integrations/quickbooks/mappers/to-purchase.ts b/integrations/quickbooks/mappers/to-purchase.ts index ff75eb601..c993d8748 100644 --- a/integrations/quickbooks/mappers/to-purchase.ts +++ b/integrations/quickbooks/mappers/to-purchase.ts @@ -2,6 +2,9 @@ import type { Purchase } from '../../models'; import type { QuickBooksPurchase } from '../types'; export function toPurchase(purchase: QuickBooksPurchase): Purchase { + if (!purchase.TxnDate || !purchase.MetaData?.CreateTime || !purchase.MetaData?.LastUpdatedTime) { + throw new Error(`Missing required Date fields for transfer ${purchase.Id}`); + } return { id: purchase.Id, account_id: purchase.AccountRef.value, diff --git a/integrations/quickbooks/mappers/to-transfer.ts b/integrations/quickbooks/mappers/to-transfer.ts index 9b053dd96..c7e85389a 100644 --- a/integrations/quickbooks/mappers/to-transfer.ts +++ b/integrations/quickbooks/mappers/to-transfer.ts @@ -2,6 +2,9 @@ import type { Transfer } from '../../models'; import type { QuickBooksTransfer } from '../types'; export function toTransfer(transfer: QuickBooksTransfer): Transfer { + if (!transfer.TxnDate || !transfer.MetaData?.CreateTime || !transfer.MetaData?.LastUpdatedTime) { + throw new Error(`Missing required fields for transfer ${transfer.Id}`); + } return { id: transfer.Id, from_account_id: transfer.FromAccountRef?.value, diff --git a/integrations/quickbooks/nango.yaml b/integrations/quickbooks/nango.yaml index edefde49e..32c1ac235 100644 --- a/integrations/quickbooks/nango.yaml +++ b/integrations/quickbooks/nango.yaml @@ -253,6 +253,7 @@ models: Updates: id: string sync_token: string + active?: boolean BaseInvoice: __extends: Metadata id: string @@ -516,3 +517,5 @@ models: detail_type?: string deposit_account_id?: string | undefined deposit_account_name?: string | undefined + DeleteResponse: + id: string diff --git a/integrations/quickbooks/syncs/accounts.ts b/integrations/quickbooks/syncs/accounts.ts index 409c16142..d915c8dff 100644 --- a/integrations/quickbooks/syncs/accounts.ts +++ b/integrations/quickbooks/syncs/accounts.ts @@ -19,22 +19,18 @@ export default async function fetchData(nango: NangoSync): Promise { additionalFilter: 'Active IN (true, false)' }; - let allAccounts: QuickBooksAccount[] = []; - // Fetch all accounts with pagination - for await (const accounts of paginate(nango, config)) { - allAccounts = [...allAccounts, ...accounts]; - } - - // Filter and process active accounts - const activeAccounts = allAccounts.filter((account) => account.Active); - const mappedActiveAccounts = activeAccounts.map(toAccount); - await nango.batchSave(mappedActiveAccounts, 'Account'); + for await (const qAccounts of paginate(nango, config)) { + // Filter and process active accounts + const activeAccounts = qAccounts.filter((account) => account.Active); + const mappedActiveAccounts = activeAccounts.map(toAccount); + await nango.batchSave(mappedActiveAccounts, 'Account'); - // Handle archived accounts only if it's an incremental refresh - if (nango.lastSyncDate) { - const archivedAccounts = allAccounts.filter((account) => !account.Active); - const mappedArchivedAccounts = archivedAccounts.map(toAccount); - await nango.batchDelete(mappedArchivedAccounts, 'Account'); + // Handle archived accounts only if it's an incremental refresh + if (nango.lastSyncDate) { + const archivedAccounts = qAccounts.filter((account) => !account.Active); + const mappedArchivedAccounts = archivedAccounts.map(toAccount); + await nango.batchDelete(mappedArchivedAccounts, 'Account'); + } } } diff --git a/integrations/quickbooks/syncs/bill-payments.ts b/integrations/quickbooks/syncs/bill-payments.ts index 2588bf28f..7fe0729da 100644 --- a/integrations/quickbooks/syncs/bill-payments.ts +++ b/integrations/quickbooks/syncs/bill-payments.ts @@ -1,4 +1,4 @@ -import type { NangoSync, BillPayment } from '../../models'; +import type { NangoSync, BillPayment, DeleteResponse } from '../../models'; import type { QuickBooksBillPayment } from '../types'; import { paginate } from '../helpers/paginate.js'; import { toBillPayment } from '../mappers/to-bill-payment.js'; @@ -9,8 +9,23 @@ export default async function fetchData(nango: NangoSync): Promise { model: 'BillPayment' }; - for await (const qbillPayments of paginate(nango, config)) { - const billPayments = qbillPayments.map(toBillPayment); - await nango.batchSave(billPayments, 'BillPayment'); + for await (const qBillPayments of paginate(nango, config)) { + const activeBillPayments = qBillPayments.filter((payment) => !payment.PrivateNote?.includes('Voided') && payment.status !== 'Deleted'); + const deletedBillPayments = qBillPayments.filter((payment) => payment.PrivateNote?.includes('Voided') || payment.status === 'Deleted'); + + if (activeBillPayments.length > 0) { + const mappedActiveBillPayments = activeBillPayments.map(toBillPayment); + await nango.batchSave(mappedActiveBillPayments, 'BillPayment'); + await nango.log(`Successfully saved ${activeBillPayments.length} bill payments`); + } + + // Handle deleted or voided bill payments if this isn't the first sync + if (nango.lastSyncDate && deletedBillPayments.length > 0) { + const mappedDeletedBillPayments = deletedBillPayments.map((payment) => ({ + id: payment.Id + })); + await nango.batchDelete(mappedDeletedBillPayments, 'BillPayment'); + await nango.log(`Successfully processed ${deletedBillPayments.length} bill payments`); + } } } diff --git a/integrations/quickbooks/syncs/bills.ts b/integrations/quickbooks/syncs/bills.ts index ce02b0a7e..13dc3de16 100644 --- a/integrations/quickbooks/syncs/bills.ts +++ b/integrations/quickbooks/syncs/bills.ts @@ -1,4 +1,4 @@ -import type { NangoSync, Bill } from '../../models'; +import type { NangoSync, Bill, DeleteResponse } from '../../models'; import type { QuickBooksBill } from '../types'; import { paginate } from '../helpers/paginate.js'; import { toBill } from '../mappers/to-bill.js'; @@ -9,8 +9,24 @@ export default async function fetchData(nango: NangoSync): Promise { model: 'Bill' }; - for await (const bills of paginate(nango, config)) { - const mappedBills = bills.map(toBill); - await nango.batchSave(mappedBills, 'Bill'); + for await (const qBills of paginate(nango, config)) { + // Split active and deleted bills + const activeBills = qBills.filter((bill) => bill.status !== 'Deleted'); + const deletedBills = qBills.filter((bill) => bill.status === 'Deleted'); + + if (activeBills.length > 0) { + const mappedActiveBills = activeBills.map(toBill); + await nango.batchSave(mappedActiveBills, 'Bill'); + await nango.log(`Successfully saved ${activeBills.length} active bills`); + } + + // Handle deleted bills if this isn't the first sync + if (nango.lastSyncDate && deletedBills.length > 0) { + const mappedDeletedBills = deletedBills.map((bill) => ({ + id: bill.Id + })); + await nango.batchDelete(mappedDeletedBills, 'Bill'); + await nango.log(`Successfully processed ${deletedBills.length} deleted bills`); + } } } diff --git a/integrations/quickbooks/syncs/credit-memos.ts b/integrations/quickbooks/syncs/credit-memos.ts index baf96a534..f1443c6dc 100644 --- a/integrations/quickbooks/syncs/credit-memos.ts +++ b/integrations/quickbooks/syncs/credit-memos.ts @@ -1,4 +1,4 @@ -import type { NangoSync, CreditMemo } from '../../models'; +import type { NangoSync, CreditMemo, DeleteResponse } from '../../models'; import type { QuickBooksCreditMemo } from '../types'; import { paginate } from '../helpers/paginate.js'; import { toCreditMemo } from '../mappers/to-credit-memo.js'; @@ -10,7 +10,21 @@ export default async function fetchData(nango: NangoSync): Promise { }; for await (const qCreditMemos of paginate(nango, config)) { - const creditMemos = qCreditMemos.map(toCreditMemo); - await nango.batchSave(creditMemos, 'CreditMemo'); + const activeCreditMemos = qCreditMemos.filter((memo) => memo.status !== 'Deleted'); + const deletedCreditMemos = qCreditMemos.filter((memo) => memo.status === 'Deleted'); + + if (activeCreditMemos.length > 0) { + const mappedActiveCreditMemos = activeCreditMemos.map(toCreditMemo); + await nango.batchSave(mappedActiveCreditMemos, 'CreditMemo'); + await nango.log(`Successfully saved ${activeCreditMemos.length} active credit memos`); + } + + if (deletedCreditMemos.length > 0 && nango.lastSyncDate) { + const mappedDeletedCreditMemos = deletedCreditMemos.map((memo) => ({ + id: memo.Id + })); + await nango.batchDelete(mappedDeletedCreditMemos, 'CreditMemo'); + await nango.log(`Successfully processed ${deletedCreditMemos.length} deleted credit memos`); + } } } diff --git a/integrations/quickbooks/syncs/deposits.ts b/integrations/quickbooks/syncs/deposits.ts index f43612410..dd45ebbfa 100644 --- a/integrations/quickbooks/syncs/deposits.ts +++ b/integrations/quickbooks/syncs/deposits.ts @@ -1,4 +1,4 @@ -import type { NangoSync, Deposit } from '../../models'; +import type { NangoSync, Deposit, DeleteResponse } from '../../models'; import type { QuickBooksDeposit } from '../types'; import { paginate } from '../helpers/paginate.js'; import { toDeposit } from '../mappers/to-deposit.js'; @@ -10,7 +10,21 @@ export default async function fetchData(nango: NangoSync): Promise { }; for await (const qDeposits of paginate(nango, config)) { - const deposits = qDeposits.map(toDeposit); - await nango.batchSave(deposits, 'Deposit'); + const activeDeposits = qDeposits.filter((d) => d.status !== 'Deleted'); + const deletedDeposits = qDeposits.filter((d) => d.status === 'Deleted'); + + if (activeDeposits.length > 0) { + const mappedActiveDeposits = activeDeposits.map(toDeposit); + await nango.batchSave(mappedActiveDeposits, 'Deposit'); + await nango.log(`Successfully saved ${activeDeposits.length} active deposits`); + } + + if (nango.lastSyncDate && deletedDeposits.length > 0) { + const mappedDeletedDeposits = deletedDeposits.map((deposit) => ({ + id: deposit.Id + })); + await nango.batchDelete(mappedDeletedDeposits, 'Deposit'); + await nango.log(`Successfully processed ${deletedDeposits.length} deleted deposits`); + } } } diff --git a/integrations/quickbooks/syncs/invoices.ts b/integrations/quickbooks/syncs/invoices.ts index 51deef098..17baaf26f 100644 --- a/integrations/quickbooks/syncs/invoices.ts +++ b/integrations/quickbooks/syncs/invoices.ts @@ -26,13 +26,13 @@ export default async function fetchData(nango: NangoSync): Promise { } // Filter and process invoices that are not voided (i.e., active invoices) - const activeInvoices = allPayments.filter((invoice) => !invoice.PrivateNote?.includes('Voided')); + const activeInvoices = allPayments.filter((invoice) => invoice.status !== 'Deleted' && !invoice.PrivateNote?.includes('Voided')); const mappedActiveInvoices = activeInvoices.map(toInvoice); await nango.batchSave(mappedActiveInvoices, 'Invoice'); // Handle voided invoices only if it's an incremental refresh if (nango.lastSyncDate) { - const voidedPayments = allPayments.filter((invoice) => invoice.PrivateNote?.includes('Voided')); + const voidedPayments = allPayments.filter((invoice) => invoice.status === 'Deleted' || invoice.PrivateNote?.includes('Voided')); const mappedVoidedPayments = voidedPayments.map(toInvoice); await nango.batchDelete(mappedVoidedPayments, 'Invoice'); } diff --git a/integrations/quickbooks/syncs/items.ts b/integrations/quickbooks/syncs/items.ts index 8915a7937..8e85d1e56 100644 --- a/integrations/quickbooks/syncs/items.ts +++ b/integrations/quickbooks/syncs/items.ts @@ -19,22 +19,18 @@ export default async function fetchData(nango: NangoSync): Promise { additionalFilter: 'Active IN (true, false)' }; - let allItems: QuickBooksItem[] = []; - // Fetch all items with pagination - for await (const items of paginate(nango, config)) { - allItems = [...allItems, ...items]; - } - - // Filter and process active items - const activeItems = allItems.filter((item) => item.Active); - const mappedActiveItems = activeItems.map(toItem); - await nango.batchSave(mappedActiveItems, 'Item'); + for await (const qItems of paginate(nango, config)) { + // Filter and process active items + const activeItems = qItems.filter((item) => item.Active); + const mappedActiveItems = activeItems.map(toItem); + await nango.batchSave(mappedActiveItems, 'Item'); - // Handle archived items only if it's an incremental refresh - if (nango.lastSyncDate) { - const archivedItems = allItems.filter((item) => !item.Active); - const mappedArchivedItems = archivedItems.map(toItem); - await nango.batchDelete(mappedArchivedItems, 'Item'); + // Handle archived items only if it's an incremental refresh + if (nango.lastSyncDate) { + const archivedItems = qItems.filter((item) => !item.Active); + const mappedArchivedItems = archivedItems.map(toItem); + await nango.batchDelete(mappedArchivedItems, 'Item'); + } } } diff --git a/integrations/quickbooks/syncs/journal-entries.ts b/integrations/quickbooks/syncs/journal-entries.ts index 0b171a4f1..788466ca0 100644 --- a/integrations/quickbooks/syncs/journal-entries.ts +++ b/integrations/quickbooks/syncs/journal-entries.ts @@ -1,5 +1,5 @@ import type { QuickBooksJournalEntry } from '../types'; -import type { NangoSync } from '../../models'; +import type { NangoSync, DeleteResponse, JournalEntry } from '../../models'; import { paginate } from '../helpers/paginate.js'; import type { PaginationParams } from '../helpers/paginate'; import { toJournalEntry } from '../mappers/to-journal-entry.js'; @@ -16,9 +16,23 @@ export default async function fetchData(nango: NangoSync): Promise { const config: PaginationParams = { model: 'JournalEntry' }; - for await (const journalEntries of paginate(nango, config)) { - const entries = toJournalEntry(journalEntries); - await nango.batchSave(entries, 'JournalEntry'); - await nango.log(`Successfully saved ${journalEntries.length} entries`); + + for await (const qJournalEntries of paginate(nango, config)) { + const activeJournalEntries = qJournalEntries.filter((entry) => entry.status !== 'Deleted'); + const deletedJournalEntries = qJournalEntries.filter((entry) => entry.status === 'Deleted'); + + // Process and save active journal entries + if (activeJournalEntries.length > 0) { + const mappedActiveJournalEntries = toJournalEntry(activeJournalEntries); + await nango.batchSave(mappedActiveJournalEntries, 'JournalEntry'); + } + + // Process deletions if this is not the first sync + if (nango.lastSyncDate && deletedJournalEntries.length > 0) { + const mappedDeletedJournalEntries = deletedJournalEntries.map((entry) => ({ + id: entry.Id + })); + await nango.batchDelete(mappedDeletedJournalEntries, 'JournalEntry'); + } } } diff --git a/integrations/quickbooks/syncs/payments.ts b/integrations/quickbooks/syncs/payments.ts index 892ae6a6d..6aad2fb02 100644 --- a/integrations/quickbooks/syncs/payments.ts +++ b/integrations/quickbooks/syncs/payments.ts @@ -1,4 +1,4 @@ -import type { NangoSync, Payment } from '../../models'; +import type { NangoSync, Payment, DeleteResponse } from '../../models'; import type { QuickBooksPayment } from '../types'; import { paginate } from '../helpers/paginate.js'; import { toPayment } from '../mappers/to-payment.js'; @@ -17,23 +17,22 @@ export default async function fetchData(nango: NangoSync): Promise { const config: PaginationParams = { model: 'Payment' }; + for await (const qPayments of paginate(nango, config)) { + const activePayments = qPayments.filter((payment) => !payment.PrivateNote?.includes('Voided') && payment.status !== 'Deleted'); + const deletedPayments = qPayments.filter((payment) => payment.PrivateNote?.includes('Voided') || payment.status === 'Deleted'); - let allPayments: QuickBooksPayment[] = []; + // Process and save active payments + if (activePayments.length > 0) { + const mappedActivePayments = activePayments.map(toPayment); + await nango.batchSave(mappedActivePayments, 'Payment'); + } - // Fetch all payments with pagination - for await (const payments of paginate(nango, config)) { - allPayments = [...allPayments, ...payments]; - } - - // Filter and process payments that are not voided (i.e., active payments) - const activePayments = allPayments.filter((payment) => !payment.PrivateNote?.includes('Voided')); - const mappedActivePayments = activePayments.map(toPayment); - await nango.batchSave(mappedActivePayments, 'Payment'); - - // Handle voided payments only if it's an incremental refresh - if (nango.lastSyncDate) { - const voidedPayments = allPayments.filter((payment) => payment.PrivateNote?.includes('Voided')); - const mappedVoidedPayments = voidedPayments.map(toPayment); - await nango.batchDelete(mappedVoidedPayments, 'Payment'); + // Process deletions if this is not the first sync + if (nango.lastSyncDate && deletedPayments.length > 0) { + const mappedDeletedPayments = deletedPayments.map((payment) => ({ + id: payment.Id + })); + await nango.batchDelete(mappedDeletedPayments, 'Payment'); + } } } diff --git a/integrations/quickbooks/syncs/purchases.ts b/integrations/quickbooks/syncs/purchases.ts index 327413b76..a5a9bd5c4 100644 --- a/integrations/quickbooks/syncs/purchases.ts +++ b/integrations/quickbooks/syncs/purchases.ts @@ -1,4 +1,4 @@ -import type { NangoSync, Purchase } from '../../models'; +import type { NangoSync, Purchase, DeleteResponse } from '../../models'; import type { QuickBooksPurchase } from '../types'; import { paginate } from '../helpers/paginate.js'; import { toPurchase } from '../mappers/to-purchase.js'; @@ -10,7 +10,21 @@ export default async function fetchData(nango: NangoSync): Promise { }; for await (const qPurchases of paginate(nango, config)) { - const purchases = qPurchases.map(toPurchase); - await nango.batchSave(purchases, 'Purchase'); + const activePurchases = qPurchases.filter((purchase) => purchase.status !== 'Deleted'); + const deletedPurchases = qPurchases.filter((purchase) => purchase.status === 'Deleted'); + + // Process and save active purchases + if (activePurchases.length > 0) { + const mappedActivePurchases = activePurchases.map(toPurchase); + await nango.batchSave(mappedActivePurchases, 'Purchase'); + } + + // Process deletions if this is not the first sync + if (nango.lastSyncDate && deletedPurchases.length > 0) { + const mappedDeletedPurchases = deletedPurchases.map((purchase) => ({ + id: purchase.Id + })); + await nango.batchDelete(mappedDeletedPurchases, 'Purchase'); + } } } diff --git a/integrations/quickbooks/syncs/transfers.ts b/integrations/quickbooks/syncs/transfers.ts index 85c26a3a1..402dd1ee5 100644 --- a/integrations/quickbooks/syncs/transfers.ts +++ b/integrations/quickbooks/syncs/transfers.ts @@ -1,4 +1,4 @@ -import type { NangoSync, Transfer } from '../../models'; +import type { NangoSync, Transfer, DeleteResponse } from '../../models'; import type { QuickBooksTransfer } from '../types'; import { paginate } from '../helpers/paginate.js'; import { toTransfer } from '../mappers/to-transfer.js'; @@ -10,7 +10,21 @@ export default async function fetchData(nango: NangoSync): Promise { }; for await (const qTransfers of paginate(nango, config)) { - const transfers = qTransfers.map(toTransfer); - await nango.batchSave(transfers, 'Transfer'); + const activeTransfers = qTransfers.filter((transfer) => transfer.status !== 'Deleted'); + const deletedTransfers = qTransfers.filter((transfer) => transfer.status === 'Deleted'); + + // Process and save active transfers + if (activeTransfers.length > 0) { + const mappedActiveTransfers = activeTransfers.map(toTransfer); + await nango.batchSave(mappedActiveTransfers, 'Transfer'); + } + + // Process deletions if this is not the first sync + if (nango.lastSyncDate && deletedTransfers.length > 0) { + const mappedDeletedTransfers = deletedTransfers.map((transfer) => ({ + id: transfer.Id + })); + await nango.batchDelete(mappedDeletedTransfers, 'Transfer'); + } } } diff --git a/integrations/quickbooks/types.ts b/integrations/quickbooks/types.ts index 1a0c9f4dd..b6bd3bfbb 100644 --- a/integrations/quickbooks/types.ts +++ b/integrations/quickbooks/types.ts @@ -190,6 +190,7 @@ export interface QuickBooksPayment { PaymentRefNum?: string; TaxExemptionRef?: ReferenceType; MetaData: MetaData; + status?: string; } interface QuickBooksItemGroupLine { @@ -288,6 +289,7 @@ export interface QuickBooksInvoice { PrivateNote?: string; ProjectRef?: ReferenceType; Deposit?: number; + status?: string; } export interface QuickBooksCreditMemo { @@ -312,6 +314,7 @@ export interface QuickBooksCreditMemo { PrintStatus: string; EmailStatus: string; Balance: number; + status?: string; } export interface QuickBooksLedger { @@ -338,6 +341,7 @@ export interface QuickBooksJournalEntry { PrivateNote: string; Line: QuickBooksJournalLine[]; TxnTaxDetail: Record; + status?: string; } export interface QuickBooksJournalLine { @@ -373,6 +377,7 @@ export interface QuickBooksBill { MetaData: MetaData; CurrencyRef: ReferenceType; SalesTermRef: ReferenceType; + status?: string; } export interface QuickBooksBillLine { @@ -414,6 +419,7 @@ export interface QuickBooksBillPayment { }[]; Id: string; MetaData: MetaData; + status?: string; } export interface QuickBooksPurchase { @@ -433,6 +439,7 @@ export interface QuickBooksPurchase { Line: QuickBooksPurchaseLine[]; CurrencyRef: ReferenceType; EntityRef: ReferenceType; + status?: string; } export interface QuickBooksPurchaseLine { @@ -460,6 +467,7 @@ export interface QuickBooksTransfer { Id: string; FromAccountRef: ReferenceType; MetaData: MetaData; + status?: string; } export interface QuickBooksDeposit { @@ -474,6 +482,7 @@ export interface QuickBooksDeposit { Line: QuickBooksDepositLine[]; Id: string; MetaData: MetaData; + status?: string; } export interface QuickBooksDepositLine { @@ -484,3 +493,8 @@ export interface QuickBooksDepositLine { AccountRef: ReferenceType; }; } + +export interface CDCConfig { + entity: string; + lastSyncDate: Date; +} diff --git a/integrations/ramp/mocks/nango/get/proxy/developer/v1/users/users/594661ff18ed6e6ed0516643751bbdc787466c35.json b/integrations/ramp/mocks/nango/get/proxy/developer/v1/users/users/594661ff18ed6e6ed0516643751bbdc787466c35.json index 3161039d4..07482a0e9 100644 --- a/integrations/ramp/mocks/nango/get/proxy/developer/v1/users/users/594661ff18ed6e6ed0516643751bbdc787466c35.json +++ b/integrations/ramp/mocks/nango/get/proxy/developer/v1/users/users/594661ff18ed6e6ed0516643751bbdc787466c35.json @@ -1,338 +1,338 @@ { - "method": "get", - "endpoint": "developer/v1/users", - "requestIdentityHash": "594661ff18ed6e6ed0516643751bbdc787466c35", - "requestIdentity": { "method": "get", "endpoint": "developer/v1/users", - "params": [], - "headers": [] - }, - "response": { - "data": [ - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "0193bab1-c269-7257-9749-5749b3ce6729", - "employee_id": null, - "manager_id": null, - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "IT_ADMIN", - "last_name": "Doe", - "email": "john.doe@example.com", - "department_id": "a9c97ee1-dd6c-4d68-afe6-878e658075b6", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "INVITE_PENDING", - "is_manager": false, - "custom_fields": [], - "first_name": "John" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "0193ba20-8915-7c44-99d3-733b1ae090e2", - "employee_id": null, - "manager_id": null, - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_ADMIN", - "last_name": "Maina", - "email": "fred.maina@some-email.com", - "department_id": "4e754520-dfba-4415-a4ae-77a811e34e5d", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": true, - "custom_fields": [], - "first_name": "Francis" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "9718494b-3c1a-4ac2-975d-173b0ba65fd5", - "employee_id": null, - "manager_id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Clark", - "email": "rick+cardholder_7@some-email.com", - "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", - "location_id": "234a200e-4f2b-4052-b2d7-344c7010e5b1", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Patrick" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "f5e67731-09fc-4f03-bff3-19b54235945f", - "employee_id": null, - "manager_id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Rickson", - "email": "rick+cardholder_6@some-email.com", - "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", - "location_id": "8fbe10cc-74a6-455d-bd11-cdd6c5ccf302", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Peter" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "331da470-7c12-4331-a964-c0fff0dcd642", - "employee_id": null, - "manager_id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Martinez", - "email": "rick+cardholder_5@some-email.com", - "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Gloria" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "e512f5cb-36ab-4581-8b99-8fdc5bab4dd4", - "employee_id": null, - "manager_id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Garcia", - "email": "rick+cardholder_4@some-email.com", - "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", - "location_id": "234a200e-4f2b-4052-b2d7-344c7010e5b1", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Andrew" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", - "employee_id": null, - "manager_id": null, - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Test", - "email": "rick+manager_1@some-email.com", - "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", - "location_id": "234a200e-4f2b-4052-b2d7-344c7010e5b1", - "status": "USER_ACTIVE", - "is_manager": true, - "custom_fields": [], - "first_name": "Megan" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "34e615b9-f8a2-4999-a8e8-eafcfe2af321", - "employee_id": null, - "manager_id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Moore", - "email": "rick+cardholder_3@some-email.com", - "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Scott" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "e858f28d-dd2b-4364-80d4-c19e354d290e", - "employee_id": null, - "manager_id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Lee", - "email": "rick+cardholder_2@some-email.com", - "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Anna" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "0854299b-b9c3-463c-ac20-6af7cf737153", - "employee_id": null, - "manager_id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Miller", - "email": "rick+cardholder_1@some-email.com", - "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Kevin" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "6b841c7c-4b8a-4638-b19f-264d8b0da4f2", - "employee_id": null, - "manager_id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Davis", - "email": "rick+cardholder_0@some-email.com", - "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Linda" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", - "employee_id": null, - "manager_id": null, - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_USER", - "last_name": "Brown", - "email": "rick+manager_0@some-email.com", - "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": true, - "custom_fields": [], - "first_name": "Mary" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "5835f363-e6d1-4c7c-a730-39a55f5e0272", - "employee_id": null, - "manager_id": "7867a713-8069-4618-9a08-aaf69a79d75a", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_BOOKKEEPER", - "last_name": "Gu", - "email": "rick+bookkeeper_1@some-email.com", - "department_id": "4e754520-dfba-4415-a4ae-77a811e34e5d", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "Robert" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "442c06da-7a49-4340-b4c1-3bab2df600d9", - "employee_id": null, - "manager_id": "7867a713-8069-4618-9a08-aaf69a79d75a", - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_BOOKKEEPER", - "last_name": "Williams", - "email": "rick+bookkeeper_0@some-email.com", - "department_id": "4e754520-dfba-4415-a4ae-77a811e34e5d", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": false, - "custom_fields": [], - "first_name": "John" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "a1265acc-90c1-40dd-a1b7-8d0588ec8a09", - "employee_id": null, - "manager_id": null, - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_ADMIN", - "last_name": "Jones", - "email": "rick+console@some-email.com", - "department_id": "a9c97ee1-dd6c-4d68-afe6-878e658075b6", - "location_id": "8fbe10cc-74a6-455d-bd11-cdd6c5ccf302", - "status": "USER_ACTIVE", - "is_manager": true, - "custom_fields": [], - "first_name": "Julie" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "0dbb840b-8cc7-4428-b0d1-508ad1c4a2d3", - "employee_id": null, - "manager_id": null, - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_ADMIN", - "last_name": "Johnson", - "email": "rick+admin_0@some-email.com", - "department_id": "a9c97ee1-dd6c-4d68-afe6-878e658075b6", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": true, - "custom_fields": [], - "first_name": "Elizabeth" - }, - { - "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", - "id": "7867a713-8069-4618-9a08-aaf69a79d75a", - "employee_id": null, - "manager_id": null, - "phone": null, - "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", - "role": "BUSINESS_OWNER", - "last_name": "Smith", - "email": "rick+owner@some-email.com", - "department_id": "a9c97ee1-dd6c-4d68-afe6-878e658075b6", - "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", - "status": "USER_ACTIVE", - "is_manager": true, - "custom_fields": [], - "first_name": "James" - } - ], - "page": { - "next": null + "requestIdentityHash": "594661ff18ed6e6ed0516643751bbdc787466c35", + "requestIdentity": { + "method": "get", + "endpoint": "developer/v1/users", + "params": [], + "headers": [] + }, + "response": { + "data": [ + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "0193bab1-c269-7257-9749-5749b3ce6729", + "employee_id": null, + "manager_id": null, + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "IT_ADMIN", + "last_name": "Doe", + "email": "john.doe@example.com", + "department_id": "a9c97ee1-dd6c-4d68-afe6-878e658075b6", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "INVITE_PENDING", + "is_manager": false, + "custom_fields": [], + "first_name": "John" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "0193ba20-8915-7c44-99d3-733b1ae090e2", + "employee_id": null, + "manager_id": null, + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_ADMIN", + "last_name": "Maina", + "email": "fred.maina@some-email.com", + "department_id": "4e754520-dfba-4415-a4ae-77a811e34e5d", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": true, + "custom_fields": [], + "first_name": "Francis" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "9718494b-3c1a-4ac2-975d-173b0ba65fd5", + "employee_id": null, + "manager_id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Clark", + "email": "rick+cardholder_7@some-email.com", + "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", + "location_id": "234a200e-4f2b-4052-b2d7-344c7010e5b1", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "Patrick" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "f5e67731-09fc-4f03-bff3-19b54235945f", + "employee_id": null, + "manager_id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Rickson", + "email": "rick+cardholder_6@some-email.com", + "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", + "location_id": "8fbe10cc-74a6-455d-bd11-cdd6c5ccf302", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "Peter" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "331da470-7c12-4331-a964-c0fff0dcd642", + "employee_id": null, + "manager_id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Martinez", + "email": "rick+cardholder_5@some-email.com", + "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "Gloria" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "e512f5cb-36ab-4581-8b99-8fdc5bab4dd4", + "employee_id": null, + "manager_id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Garcia", + "email": "rick+cardholder_4@some-email.com", + "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", + "location_id": "234a200e-4f2b-4052-b2d7-344c7010e5b1", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "test" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", + "employee_id": null, + "manager_id": null, + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Test", + "email": "rick+manager_1@some-email.com", + "department_id": "2e9b49af-2994-4de6-ba5b-70c4aa99eda0", + "location_id": "234a200e-4f2b-4052-b2d7-344c7010e5b1", + "status": "USER_ACTIVE", + "is_manager": true, + "custom_fields": [], + "first_name": "Megan" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "34e615b9-f8a2-4999-a8e8-eafcfe2af321", + "employee_id": null, + "manager_id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Moore", + "email": "rick+cardholder_3@some-email.com", + "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "Scott" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "e858f28d-dd2b-4364-80d4-c19e354d290e", + "employee_id": null, + "manager_id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Lee", + "email": "rick+cardholder_2@some-email.com", + "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "Anna" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "0854299b-b9c3-463c-ac20-6af7cf737153", + "employee_id": null, + "manager_id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Miller", + "email": "rick+cardholder_1@some-email.com", + "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "Kevin" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "6b841c7c-4b8a-4638-b19f-264d8b0da4f2", + "employee_id": null, + "manager_id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Davis", + "email": "rick+cardholder_0@some-email.com", + "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "Linda" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", + "employee_id": null, + "manager_id": null, + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_USER", + "last_name": "Brown", + "email": "rick+manager_0@some-email.com", + "department_id": "12e604bd-dd6d-4d1b-9ab5-eb5e0fffebf5", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": true, + "custom_fields": [], + "first_name": "Mary" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "5835f363-e6d1-4c7c-a730-39a55f5e0272", + "employee_id": null, + "manager_id": "7867a713-8069-4618-9a08-aaf69a79d75a", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_BOOKKEEPER", + "last_name": "Gu", + "email": "rick+bookkeeper_1@some-email.com", + "department_id": "4e754520-dfba-4415-a4ae-77a811e34e5d", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "Robert" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "442c06da-7a49-4340-b4c1-3bab2df600d9", + "employee_id": null, + "manager_id": "7867a713-8069-4618-9a08-aaf69a79d75a", + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_BOOKKEEPER", + "last_name": "Williams", + "email": "rick+bookkeeper_0@some-email.com", + "department_id": "4e754520-dfba-4415-a4ae-77a811e34e5d", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": false, + "custom_fields": [], + "first_name": "John" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "a1265acc-90c1-40dd-a1b7-8d0588ec8a09", + "employee_id": null, + "manager_id": null, + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_ADMIN", + "last_name": "Jones", + "email": "rick+console@some-email.com", + "department_id": "a9c97ee1-dd6c-4d68-afe6-878e658075b6", + "location_id": "8fbe10cc-74a6-455d-bd11-cdd6c5ccf302", + "status": "USER_ACTIVE", + "is_manager": true, + "custom_fields": [], + "first_name": "Julie" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "0dbb840b-8cc7-4428-b0d1-508ad1c4a2d3", + "employee_id": null, + "manager_id": null, + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_ADMIN", + "last_name": "Johnson", + "email": "rick+admin_0@some-email.com", + "department_id": "a9c97ee1-dd6c-4d68-afe6-878e658075b6", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": true, + "custom_fields": [], + "first_name": "Elizabeth" + }, + { + "entity_id": "7f83ff3d-c046-4eca-bb21-77777ff393cb", + "id": "7867a713-8069-4618-9a08-aaf69a79d75a", + "employee_id": null, + "manager_id": null, + "phone": null, + "business_id": "eeaeb3d0-6b8d-4d4a-81d3-aa739b5d6bcc", + "role": "BUSINESS_OWNER", + "last_name": "Smith", + "email": "rick+owner@some-email.com", + "department_id": "a9c97ee1-dd6c-4d68-afe6-878e658075b6", + "location_id": "c07c63b0-6b1e-4012-ab6d-3a9e24b5cff5", + "status": "USER_ACTIVE", + "is_manager": true, + "custom_fields": [], + "first_name": "James" + } + ], + "page": { + "next": null + } + }, + "status": 200, + "headers": { + "date": "Fri, 13 Dec 2024 12:51:54 GMT", + "content-type": "application/json", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "8f161695da7e7d9a-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "set-cookie": "__cf_bm=GfV4TvwqZplEoto.x.PZow8SpST5AhXp5kgr4KRumWM-1734094314-1.0.1.1-Xz7IoX.q8Ohhr.LI0lmvVwT9odlgUUpo2YwbHJ.m6mc5kcsg1ThvKbxjwjWhJnx8_JuXJHMl1AJ1515qxaqP_g; path=/; expires=Fri, 13-Dec-24 13:21:54 GMT; domain=.ramp.com; HttpOnly; Secure; SameSite=None", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "alt-svc": "h3=\":443\"; ma=86400", + "content-security-policy": "upgrade-insecure-requests; frame-ancestors 'self' https://demo.ramp.com https://cohere-voice.ramp.com; report-uri https://ramp.report-uri.com/r/d/csp/enforce", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "referrer-policy": "strict-origin-when-cross-origin", + "rndr-id": "e352430a-5eb9-41fe", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ramp-body-source": "others", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2397", + "x-ratelimit-reset": "1734094372", + "x-render-origin-server": "cloudflare", + "x-trace-id": "ramp-2a203f62101d43ec8597c5c0a9871d81", + "x-xss-protection": "1; mode=block", + "server": "cloudflare" } - }, - "status": 200, - "headers": { - "date": "Fri, 13 Dec 2024 12:51:54 GMT", - "content-type": "application/json", - "transfer-encoding": "chunked", - "connection": "keep-alive", - "cf-ray": "8f161695da7e7d9a-TLV", - "cf-cache-status": "DYNAMIC", - "access-control-allow-origin": "*", - "set-cookie": "__cf_bm=GfV4TvwqZplEoto.x.PZow8SpST5AhXp5kgr4KRumWM-1734094314-1.0.1.1-Xz7IoX.q8Ohhr.LI0lmvVwT9odlgUUpo2YwbHJ.m6mc5kcsg1ThvKbxjwjWhJnx8_JuXJHMl1AJ1515qxaqP_g; path=/; expires=Fri, 13-Dec-24 13:21:54 GMT; domain=.ramp.com; HttpOnly; Secure; SameSite=None", - "strict-transport-security": "max-age=31536000; includeSubDomains; preload", - "vary": "Accept-Encoding", - "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", - "alt-svc": "h3=\":443\"; ma=86400", - "content-security-policy": "upgrade-insecure-requests; frame-ancestors 'self' https://demo.ramp.com https://cohere-voice.ramp.com; report-uri https://ramp.report-uri.com/r/d/csp/enforce", - "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", - "referrer-policy": "strict-origin-when-cross-origin", - "rndr-id": "e352430a-5eb9-41fe", - "x-content-type-options": "nosniff", - "x-dns-prefetch-control": "off", - "x-download-options": "noopen", - "x-frame-options": "SAMEORIGIN", - "x-ramp-body-source": "others", - "x-ratelimit-limit": "2400", - "x-ratelimit-remaining": "2397", - "x-ratelimit-reset": "1734094372", - "x-render-origin-server": "cloudflare", - "x-trace-id": "ramp-2a203f62101d43ec8597c5c0a9871d81", - "x-xss-protection": "1; mode=block", - "server": "cloudflare" - } -} \ No newline at end of file +} diff --git a/integrations/ramp/mocks/users/User/batchSave.json b/integrations/ramp/mocks/users/User/batchSave.json index 07eafab2b..4832e1302 100644 --- a/integrations/ramp/mocks/users/User/batchSave.json +++ b/integrations/ramp/mocks/users/User/batchSave.json @@ -1,104 +1,104 @@ [ - { - "id": "0193bab1-c269-7257-9749-5749b3ce6729", - "firstName": "John", - "lastName": "Doe", - "email": "john.doe@example.com" - }, - { - "id": "0193ba20-8915-7c44-99d3-733b1ae090e2", - "firstName": "Francis", - "lastName": "Maina", - "email": "fred.maina@some-email.com" - }, - { - "id": "9718494b-3c1a-4ac2-975d-173b0ba65fd5", - "firstName": "Patrick", - "lastName": "Clark", - "email": "rick+cardholder_7@some-email.com" - }, - { - "id": "f5e67731-09fc-4f03-bff3-19b54235945f", - "firstName": "Peter", - "lastName": "Rickson", - "email": "rick+cardholder_6@some-email.com" - }, - { - "id": "331da470-7c12-4331-a964-c0fff0dcd642", - "firstName": "Gloria", - "lastName": "Martinez", - "email": "rick+cardholder_5@some-email.com" - }, - { - "id": "e512f5cb-36ab-4581-8b99-8fdc5bab4dd4", - "firstName": "Andrew", - "lastName": "Garcia", - "email": "rick+cardholder_4@some-email.com" - }, - { - "id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", - "firstName": "Megan", - "lastName": "Test", - "email": "rick+manager_1@some-email.com" - }, - { - "id": "34e615b9-f8a2-4999-a8e8-eafcfe2af321", - "firstName": "Scott", - "lastName": "Moore", - "email": "rick+cardholder_3@some-email.com" - }, - { - "id": "e858f28d-dd2b-4364-80d4-c19e354d290e", - "firstName": "Anna", - "lastName": "Lee", - "email": "rick+cardholder_2@some-email.com" - }, - { - "id": "0854299b-b9c3-463c-ac20-6af7cf737153", - "firstName": "Kevin", - "lastName": "Miller", - "email": "rick+cardholder_1@some-email.com" - }, - { - "id": "6b841c7c-4b8a-4638-b19f-264d8b0da4f2", - "firstName": "Linda", - "lastName": "Davis", - "email": "rick+cardholder_0@some-email.com" - }, - { - "id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", - "firstName": "Mary", - "lastName": "Brown", - "email": "rick+manager_0@some-email.com" - }, - { - "id": "5835f363-e6d1-4c7c-a730-39a55f5e0272", - "firstName": "Robert", - "lastName": "Gu", - "email": "rick+bookkeeper_1@some-email.com" - }, - { - "id": "442c06da-7a49-4340-b4c1-3bab2df600d9", - "firstName": "John", - "lastName": "Williams", - "email": "rick+bookkeeper_0@some-email.com" - }, - { - "id": "a1265acc-90c1-40dd-a1b7-8d0588ec8a09", - "firstName": "Julie", - "lastName": "Jones", - "email": "rick+console@some-email.com" - }, - { - "id": "0dbb840b-8cc7-4428-b0d1-508ad1c4a2d3", - "firstName": "Elizabeth", - "lastName": "Johnson", - "email": "rick+admin_0@some-email.com" - }, - { - "id": "7867a713-8069-4618-9a08-aaf69a79d75a", - "firstName": "James", - "lastName": "Smith", - "email": "rick+owner@some-email.com" - } -] \ No newline at end of file + { + "id": "0193bab1-c269-7257-9749-5749b3ce6729", + "firstName": "John", + "lastName": "Doe", + "email": "john.doe@example.com" + }, + { + "id": "0193ba20-8915-7c44-99d3-733b1ae090e2", + "firstName": "Francis", + "lastName": "Maina", + "email": "fred.maina@some-email.com" + }, + { + "id": "9718494b-3c1a-4ac2-975d-173b0ba65fd5", + "firstName": "Patrick", + "lastName": "Clark", + "email": "rick+cardholder_7@some-email.com" + }, + { + "id": "f5e67731-09fc-4f03-bff3-19b54235945f", + "firstName": "Peter", + "lastName": "Rickson", + "email": "rick+cardholder_6@some-email.com" + }, + { + "id": "331da470-7c12-4331-a964-c0fff0dcd642", + "firstName": "Gloria", + "lastName": "Martinez", + "email": "rick+cardholder_5@some-email.com" + }, + { + "id": "e512f5cb-36ab-4581-8b99-8fdc5bab4dd4", + "firstName": "test", + "lastName": "Garcia", + "email": "rick+cardholder_4@some-email.com" + }, + { + "id": "d3aa5c83-07c8-4908-abcb-c810bb688d46", + "firstName": "Megan", + "lastName": "Test", + "email": "rick+manager_1@some-email.com" + }, + { + "id": "34e615b9-f8a2-4999-a8e8-eafcfe2af321", + "firstName": "Scott", + "lastName": "Moore", + "email": "rick+cardholder_3@some-email.com" + }, + { + "id": "e858f28d-dd2b-4364-80d4-c19e354d290e", + "firstName": "Anna", + "lastName": "Lee", + "email": "rick+cardholder_2@some-email.com" + }, + { + "id": "0854299b-b9c3-463c-ac20-6af7cf737153", + "firstName": "Kevin", + "lastName": "Miller", + "email": "rick+cardholder_1@some-email.com" + }, + { + "id": "6b841c7c-4b8a-4638-b19f-264d8b0da4f2", + "firstName": "Linda", + "lastName": "Davis", + "email": "rick+cardholder_0@some-email.com" + }, + { + "id": "6cc84a20-a9ec-4720-a25a-dc1862605e0f", + "firstName": "Mary", + "lastName": "Brown", + "email": "rick+manager_0@some-email.com" + }, + { + "id": "5835f363-e6d1-4c7c-a730-39a55f5e0272", + "firstName": "Robert", + "lastName": "Gu", + "email": "rick+bookkeeper_1@some-email.com" + }, + { + "id": "442c06da-7a49-4340-b4c1-3bab2df600d9", + "firstName": "John", + "lastName": "Williams", + "email": "rick+bookkeeper_0@some-email.com" + }, + { + "id": "a1265acc-90c1-40dd-a1b7-8d0588ec8a09", + "firstName": "Julie", + "lastName": "Jones", + "email": "rick+console@some-email.com" + }, + { + "id": "0dbb840b-8cc7-4428-b0d1-508ad1c4a2d3", + "firstName": "Elizabeth", + "lastName": "Johnson", + "email": "rick+admin_0@some-email.com" + }, + { + "id": "7867a713-8069-4618-9a08-aaf69a79d75a", + "firstName": "James", + "lastName": "Smith", + "email": "rick+owner@some-email.com" + } +] diff --git a/integrations/recharge/actions/upsert-customers.md b/integrations/recharge/actions/upsert-customers.md new file mode 100644 index 000000000..ed9219098 --- /dev/null +++ b/integrations/recharge/actions/upsert-customers.md @@ -0,0 +1,99 @@ + +# Upsert Customers + +## General Information + +- **Description:** Upsert a customer in Recharge +- **Version:** 0.0.1 +- **Group:** Customers +- **Scopes:** `read_customers, write_customers, write_payment_methods` +- **Endpoint Type:** Action +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/recharge/actions/upsert-customers.ts) + + +## Endpoint Reference + +### Request Endpoint + +`POST /customers` + +### Request Query Parameters + +_No request parameters_ + +### Request Body + +```json +{ + "email": "", + "external_customer_id?": "", + "first_name": "", + "last_name": "", + "phone?": "", + "tax_exempt?": "" +} +``` + +### Request Response + +```json +{ + "action": "", + "response": { + "accepts_marketing": "", + "analytics_data": { + "utm_params": { + "0": { + "utm_campaign?": "", + "utm_content?": "", + "utm_data_source?": "", + "utm_source?": "", + "utm_medium?": "", + "utm_term?": "", + "utm_timestamp?": "" + } + } + }, + "billing_address1": "", + "billing_address2": "", + "billing_city": "", + "billing_company": "", + "billing_country": "", + "billing_phone": "", + "billing_province": "", + "billing_zip": "", + "created_at": "", + "email": "", + "first_charge_processed_at": "", + "first_name": "", + "has_card_error_in_dunning": "", + "has_valid_payment_method": "", + "hash": "", + "id": "", + "last_name": "", + "number_active_subscriptions": "", + "number_subscriptions": "", + "phone": "", + "processor_type": "", + "reason_payment_method_not_valid": "", + "shopify_customer_id": "", + "status": "", + "tax_exempt": "", + "updated_at": "", + "apply_credit_to_next_recurring_charge?": "", + "external_customer_id?": "", + "has_payment_method_in_dunning?": "", + "subscriptions_active_count?": "", + "subscriptions_total_count?": "", + "subscription_related_charge_streak?": "" + } +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/recharge/actions/upsert-customers.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/recharge/actions/upsert-customers.md) + + + diff --git a/integrations/recharge/actions/upsert-customers.ts b/integrations/recharge/actions/upsert-customers.ts new file mode 100644 index 000000000..aa8f8e5d7 --- /dev/null +++ b/integrations/recharge/actions/upsert-customers.ts @@ -0,0 +1,92 @@ +import type { NangoAction, UpsertRechargeCustomerOutput, UpsertRechargeCustomerInput, ProxyConfiguration } from '../../models'; +import type { RechargeCustomer } from '../types'; +import { upsertRechargeCustomerInputSchema } from '../schema.zod.js'; + +export default async function runAction(nango: NangoAction, input: UpsertRechargeCustomerInput): Promise { + const parsedInput = upsertRechargeCustomerInputSchema.safeParse(input); + + if (!parsedInput.success) { + for (const error of parsedInput.error.errors) { + await nango.log(`Invalid input provided to upsert a customer: ${error.message} at path ${error.path.join('.')}`, { level: 'error' }); + } + + throw new nango.ActionError({ + message: 'Invalid input provided to upsert a customer' + }); + } + + const { first_name, last_name, email, external_customer_id, phone, tax_exempt } = parsedInput.data; + + // @allowTryCatch + try { + const createConfig: ProxyConfiguration = { + // https://developer.rechargepayments.com/2021-11/customers/customers_create + endpoint: '/customers', + data: { + first_name, + last_name, + email, + phone, + external_customer_id, + tax_exempt + }, + retries: 10 + }; + + const createResponse = await nango.post<{ customer: RechargeCustomer }>(createConfig); + const createCustomer: UpsertRechargeCustomerOutput = { + action: 'create', + response: createResponse.data.customer + }; + + return createCustomer; + } catch (error: any) { + if ( + error.response?.status === 400 && + typeof error.response?.data?.errors?.email === 'string' && + error.response.data.errors.email.includes('email already exists') + ) { + const existingCustomerConfig: ProxyConfiguration = { + // https://developer.rechargepayments.com/2021-11/customers/customers_retrieve + endpoint: '/customers', + retries: 10, + params: { + email + } + }; + const existingCustomerResponse = await nango.get<{ customers: RechargeCustomer[] }>(existingCustomerConfig); + const existingCustomer = existingCustomerResponse.data.customers[0]; + + if (!existingCustomer?.id) { + throw new nango.ActionError({ + message: 'Customer exists but ID could not be retrieved' + }); + } + + const updateConfig: ProxyConfiguration = { + // https://developer.rechargepayments.com/2021-11/customers/customers_update + endpoint: `/customers/${existingCustomer.id}`, + data: { + first_name, + last_name, + phone, + external_customer_id, + tax_exempt + }, + retries: 10 + }; + const updateResponse = await nango.put<{ customer: RechargeCustomer }>(updateConfig); + const updateCustomer: UpsertRechargeCustomerOutput = { + action: 'update', + response: updateResponse.data.customer + }; + + return updateCustomer; + } + const errorMessage = error.response?.data ? JSON.stringify(error.response.data, null, 2) : error.message || 'Unknown error occurred'; + throw new nango.ActionError({ + message: 'Failed to create customers', + details: errorMessage + }); + } +} diff --git a/integrations/recharge/fixtures/upsert-customer.json b/integrations/recharge/fixtures/upsert-customer.json new file mode 100644 index 000000000..505233330 --- /dev/null +++ b/integrations/recharge/fixtures/upsert-customer.json @@ -0,0 +1,7 @@ +{ + "first_name": "Anther Test", + "last_name": "User", + "email": "anothertestuser@example.com", + "phone": "+447196746473", + "tax_exempt": true +} diff --git a/integrations/recharge/mappers/to-customer.ts b/integrations/recharge/mappers/to-customer.ts new file mode 100644 index 000000000..6f38c3490 --- /dev/null +++ b/integrations/recharge/mappers/to-customer.ts @@ -0,0 +1,19 @@ +import type { RechargeCustomer, RechargeSubscription } from '../types'; +import type { Customer } from '../../models'; + +export function toCustomer(customer: RechargeCustomer, subscriptions: RechargeSubscription[]): Customer { + return { + id: customer.id.toString(), + phone_number: customer.phone, + first_name: customer.first_name, + last_name: customer.last_name, + subscriptions: subscriptions.map((subscription) => ({ + id: subscription.id.toString(), + type: `${subscription.order_interval_frequency} ${subscription.order_interval_unit}`, + name: subscription.product_title, + start_date: subscription.created_at, + end_date: subscription.cancelled_at || null, + next_charge_scheduled_at: subscription.next_charge_scheduled_at + })) + }; +} diff --git a/integrations/recharge/mocks/customers/Customer/batchDelete.json b/integrations/recharge/mocks/customers/Customer/batchDelete.json new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/integrations/recharge/mocks/customers/Customer/batchDelete.json @@ -0,0 +1 @@ +[] diff --git a/integrations/recharge/mocks/customers/Customer/batchSave.json b/integrations/recharge/mocks/customers/Customer/batchSave.json new file mode 100644 index 000000000..4e6988512 --- /dev/null +++ b/integrations/recharge/mocks/customers/Customer/batchSave.json @@ -0,0 +1,194 @@ +[ + { + "id": "176871450", + "phone_number": "+447196746473", + "first_name": "Anther Test", + "last_name": "User", + "subscriptions": [] + }, + { + "id": "176403041", + "phone_number": "+447196746473", + "first_name": "Dennis", + "last_name": "Doe", + "subscriptions": [] + }, + { + "id": "176398646", + "phone_number": "+447196746473", + "first_name": "James", + "last_name": "Doe", + "subscriptions": [] + }, + { + "id": "176394386", + "phone_number": "+447196746473", + "first_name": "Black", + "last_name": "Hut", + "subscriptions": [] + }, + { + "id": "176385368", + "phone_number": "+447196746473", + "first_name": "stewie", + "last_name": "Griffin", + "subscriptions": [] + }, + { + "id": "176286578", + "phone_number": "+447196746473", + "first_name": "clevland", + "last_name": "Abdul", + "subscriptions": [ + { + "id": "583904731", + "type": "1 month", + "name": "(Sample) Coconut Bar Soap", + "start_date": "2025-01-23T16:29:15", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583903079", + "type": "15 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:28:15", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + } + ] + }, + { + "id": "176286180", + "phone_number": null, + "first_name": "Chris", + "last_name": "Griffin", + "subscriptions": [] + }, + { + "id": "176286166", + "phone_number": null, + "first_name": "Luis", + "last_name": "Griffin", + "subscriptions": [ + { + "id": "583904109", + "type": "1 month", + "name": "(Sample) Coconut Bar Soap", + "start_date": "2025-01-23T16:28:59", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583903740", + "type": "60 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:28:50", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583903181", + "type": "1 month", + "name": "(Sample) Coconut Bar Soap", + "start_date": "2025-01-23T16:28:36", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583902845", + "type": "15 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:27:24", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + } + ] + }, + { + "id": "176286145", + "phone_number": null, + "first_name": "Peter", + "last_name": "Griffin", + "subscriptions": [] + }, + { + "id": "176284917", + "phone_number": null, + "first_name": "John", + "last_name": "Doe", + "subscriptions": [] + }, + { + "id": "176100062", + "phone_number": "+18036716343", + "first_name": "Niels", + "last_name": "Bohr", + "subscriptions": [ + { + "id": "583900539", + "type": "15 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:19:46", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583899192", + "type": "90 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:16:13", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583898961", + "type": "15 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:15:35", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583898646", + "type": "30 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:14:41", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583898562", + "type": "60 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:14:28", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583898474", + "type": "1 month", + "name": "(Sample) Coconut Bar Soap", + "start_date": "2025-01-23T16:14:13", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583897956", + "type": "15 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T16:12:41", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + }, + { + "id": "583551250", + "type": "15 day", + "name": "AKALO Vitamin B1 Hangover Patches", + "start_date": "2025-01-23T07:40:28", + "end_date": null, + "next_charge_scheduled_at": "2025-07-15T00:00:00" + } + ] + } +] diff --git a/integrations/recharge/mocks/nango/get/proxy/customers/customers/0d5ccd05b2ea7d570a31d3dbeabb24c44a22908a.json b/integrations/recharge/mocks/nango/get/proxy/customers/customers/0d5ccd05b2ea7d570a31d3dbeabb24c44a22908a.json new file mode 100644 index 000000000..58a4a3303 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/customers/customers/0d5ccd05b2ea7d570a31d3dbeabb24c44a22908a.json @@ -0,0 +1,400 @@ +{ + "method": "get", + "endpoint": "customers", + "requestIdentityHash": "0d5ccd05b2ea7d570a31d3dbeabb24c44a22908a", + "requestIdentity": { + "method": "get", + "endpoint": "customers", + "params": [ + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "customers": [ + { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-27T07:14:41", + "email": "anothertestuser@example.com", + "first_charge_processed_at": null, + "first_name": "Anther Test", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "d190babfe56f6278298d1b89342c41", + "id": 176871450, + "last_name": "User", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": null, + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-27T07:14:41" + }, + { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T16:46:32", + "email": "dennisdoe@example.com", + "first_charge_processed_at": null, + "first_name": "Dennis", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "5100ec5822dd618561ad9a18ec2777", + "id": 176403041, + "last_name": "Doe", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156483977392p", + "status": "INACTIVE", + "tax_exempt": true, + "updated_at": "2025-01-23T16:48:08" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T16:44:20", + "email": "jamesdoe@example.com", + "first_charge_processed_at": null, + "first_name": "James", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "6a481d18fcbdac304ada91d25e47c3", + "id": 176398646, + "last_name": "Doe", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156483977392", + "status": "INACTIVE", + "tax_exempt": true, + "updated_at": "2025-01-23T16:45:46" + }, + { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T16:42:10", + "email": "blackhut@example.com", + "first_charge_processed_at": null, + "first_name": "Black", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "f3349088397fafe5bfe2b772ed77f0", + "id": 176394386, + "last_name": "Hut", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156483977392", + "status": "INACTIVE", + "tax_exempt": true, + "updated_at": "2025-01-23T16:48:57" + }, + { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T16:37:38", + "email": "stewiegriffin@example.com", + "first_charge_processed_at": null, + "first_name": "stewie", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "df28980b10fb2e0674ef7972942c85", + "id": 176385368, + "last_name": "Griffin", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": null, + "status": "INACTIVE", + "tax_exempt": true, + "updated_at": "2025-01-23T16:37:53" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T15:09:00", + "email": "karimabdul@example.com", + "first_charge_processed_at": null, + "first_name": "clevland", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "318dbc1f9cd15aacf7cca24ba6b158", + "id": 176286578, + "last_name": "Abdul", + "number_active_subscriptions": 2, + "number_subscriptions": 2, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156175040688", + "status": "ACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T16:48:48" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T15:02:11", + "email": "chrisgriffin@example.com", + "first_charge_processed_at": null, + "first_name": "Chris", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "1b391b4befcdb0cdf3cfb8e0cbaab9", + "id": 176286180, + "last_name": "Griffin", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156148007088", + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T15:03:37" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T15:02:02", + "email": "luisgriffin@example.com", + "first_charge_processed_at": null, + "first_name": "Luis", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "8ae75fa4a2a944f73dd3f883bcd882", + "id": 176286166, + "last_name": "Griffin", + "number_active_subscriptions": 4, + "number_subscriptions": 4, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156147810480", + "status": "ACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T16:47:46" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T15:01:50", + "email": "petergriffin@example.com", + "first_charge_processed_at": null, + "first_name": "Peter", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "ed91c164a7647c4d68b6a0a9888bc7", + "id": 176286145, + "last_name": "Griffin", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156147482800", + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T15:03:31" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T14:52:39", + "email": "sample@example.com", + "first_charge_processed_at": null, + "first_name": "John", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "cb1ebc0d9fd2d8ffa9053ee1a8f892", + "id": 176284917, + "last_name": "Doe", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156118974640", + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T14:53:56" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T07:26:11", + "email": "fake@example.com", + "first_charge_processed_at": null, + "first_name": "Niels", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "35e1a48c4e1fc8068a6150eaca6c71", + "id": 176100062, + "last_name": "Bohr", + "number_active_subscriptions": 8, + "number_subscriptions": 8, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8154447413424", + "status": "ACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T16:30:23" + } + ] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:44 GMT", + "content-type": "application/json", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "909a0c37cd537d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "https://admin.rechargeapps.com", + "set-cookie": "session=eyJfcmNzaSI6eyJkIjpudWxsLCJoIjoiYXBpLnJlY2hhcmdlYXBwcy5jb20iLCJ2IjoyfX0.Z5pAvA.Jd7NXnT-qAE5uGOl8dCiR5e0mUg; Secure; HttpOnly; Path=/; SameSite=Lax", + "strict-transport-security": "max-age=63072000; includeSubdomains", + "vary": "Accept-Encoding, Origin, Cookie", + "access-control-expose-headers": "link, x-request-id, x-recharge-limit, x-recharge-version", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "7400b6d2-31c4-49de", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2366", + "x-ratelimit-reset": "1738162368", + "x-recharge-limit": "1/40", + "x-recharge-version": "2021-01", + "x-render-origin-server": "nginx", + "x-request-id": "161860ba6f3416a870a3e70c65ffaec8", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/customers/customers/38e28fb0c37d9fb10b41121e9ac1483f09c13d18.json b/integrations/recharge/mocks/nango/get/proxy/customers/customers/38e28fb0c37d9fb10b41121e9ac1483f09c13d18.json new file mode 100644 index 000000000..25ff80c74 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/customers/customers/38e28fb0c37d9fb10b41121e9ac1483f09c13d18.json @@ -0,0 +1,397 @@ +{ + "method": "get", + "endpoint": "customers", + "requestIdentityHash": "38e28fb0c37d9fb10b41121e9ac1483f09c13d18", + "requestIdentity": { + "method": "get", + "endpoint": "customers", + "params": [], + "headers": [] + }, + "response": { + "customers": [ + { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-27T07:14:41", + "email": "anothertestuser@example.com", + "first_charge_processed_at": null, + "first_name": "Anther Test", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "d190babfe56f6278298d1b89342c41", + "id": 176871450, + "last_name": "User", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": null, + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-27T07:14:41" + }, + { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T16:46:32", + "email": "dennisdoe@example.com", + "first_charge_processed_at": null, + "first_name": "Dennis", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "5100ec5822dd618561ad9a18ec2777", + "id": 176403041, + "last_name": "Doe", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156483977392p", + "status": "INACTIVE", + "tax_exempt": true, + "updated_at": "2025-01-23T16:48:08" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T16:44:20", + "email": "jamesdoe@example.com", + "first_charge_processed_at": null, + "first_name": "James", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "6a481d18fcbdac304ada91d25e47c3", + "id": 176398646, + "last_name": "Doe", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156483977392", + "status": "INACTIVE", + "tax_exempt": true, + "updated_at": "2025-01-23T16:45:46" + }, + { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T16:42:10", + "email": "blackhut@example.com", + "first_charge_processed_at": null, + "first_name": "Black", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "f3349088397fafe5bfe2b772ed77f0", + "id": 176394386, + "last_name": "Hut", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156483977392", + "status": "INACTIVE", + "tax_exempt": true, + "updated_at": "2025-01-23T16:48:57" + }, + { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T16:37:38", + "email": "stewiegriffin@example.com", + "first_charge_processed_at": null, + "first_name": "stewie", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "df28980b10fb2e0674ef7972942c85", + "id": 176385368, + "last_name": "Griffin", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": null, + "status": "INACTIVE", + "tax_exempt": true, + "updated_at": "2025-01-23T16:37:53" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T15:09:00", + "email": "karimabdul@example.com", + "first_charge_processed_at": null, + "first_name": "clevland", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "318dbc1f9cd15aacf7cca24ba6b158", + "id": 176286578, + "last_name": "Abdul", + "number_active_subscriptions": 2, + "number_subscriptions": 2, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156175040688", + "status": "ACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T16:48:48" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T15:02:11", + "email": "chrisgriffin@example.com", + "first_charge_processed_at": null, + "first_name": "Chris", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "1b391b4befcdb0cdf3cfb8e0cbaab9", + "id": 176286180, + "last_name": "Griffin", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156148007088", + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T15:03:37" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T15:02:02", + "email": "luisgriffin@example.com", + "first_charge_processed_at": null, + "first_name": "Luis", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "8ae75fa4a2a944f73dd3f883bcd882", + "id": 176286166, + "last_name": "Griffin", + "number_active_subscriptions": 4, + "number_subscriptions": 4, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156147810480", + "status": "ACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T16:47:46" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T15:01:50", + "email": "petergriffin@example.com", + "first_charge_processed_at": null, + "first_name": "Peter", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "ed91c164a7647c4d68b6a0a9888bc7", + "id": 176286145, + "last_name": "Griffin", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156147482800", + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T15:03:31" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": null, + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T14:52:39", + "email": "sample@example.com", + "first_charge_processed_at": null, + "first_name": "John", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "cb1ebc0d9fd2d8ffa9053ee1a8f892", + "id": 176284917, + "last_name": "Doe", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": null, + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8156118974640", + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-23T14:53:56" + }, + { + "accepts_marketing": 0, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+18036716343", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-23T07:26:11", + "email": "fake@example.com", + "first_charge_processed_at": null, + "first_name": "Niels", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "35e1a48c4e1fc8068a6150eaca6c71", + "id": 176100062, + "last_name": "Bohr", + "number_active_subscriptions": 8, + "number_subscriptions": 8, + "phone": "+18036716343", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": "8154447413424", + "status": "ACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-31T09:19:23" + } + ] + }, + "status": 200, + "headers": { + "date": "Tue, 04 Feb 2025 12:17:58 GMT", + "content-type": "application/json", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "90ca99c0bad5c22f-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "https://admin.rechargeapps.com", + "set-cookie": "session=eyJfcmNzaSI6eyJkIjpudWxsLCJoIjoiYXBpLnJlY2hhcmdlYXBwcy5jb20iLCJ2IjoyfX0.Z6IFdg.1OW6-CpzFxxSEASG_PKVhwSP9Vc; Secure; HttpOnly; Path=/; SameSite=Lax", + "strict-transport-security": "max-age=63072000; includeSubdomains", + "vary": "Accept-Encoding, Origin, Cookie", + "access-control-expose-headers": "link, x-request-id, x-recharge-limit, x-recharge-version", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "09daf5f0-0aff-4fb8", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2395", + "x-ratelimit-reset": "1738671517", + "x-recharge-limit": "1/40", + "x-recharge-version": "2021-01", + "x-render-origin-server": "nginx", + "x-request-id": "24017f66fd58f5439aa09c05ee8a8a31", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/0c0ec73c4cb382f270c533c99b0d452bf9503556.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/0c0ec73c4cb382f270c533c99b0d452bf9503556.json new file mode 100644 index 000000000..a8a2b9b6e --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/0c0ec73c4cb382f270c533c99b0d452bf9503556.json @@ -0,0 +1,385 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "0c0ec73c4cb382f270c533c99b0d452bf9503556", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176100062], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [ + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:19:46", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583900539, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 30, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:19:46", + "variant_title": "5" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "90", + "created_at": "2025-01-23T16:16:13", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583899192, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "90", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [], + "quantity": 1, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:16:13", + "variant_title": "5" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:15:35", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583898961, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Colour", + "value": "Yellow" + }, + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 3, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:15:35", + "variant_title": "5" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:14:41", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583898646, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "30", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 80, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [], + "quantity": 1, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053910704, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:14:41", + "variant_title": "25" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "60", + "created_at": "2025-01-23T16:14:28", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583898562, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "60", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 36, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [], + "quantity": 1, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053877936, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:14:28", + "variant_title": "10" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "1", + "created_at": "2025-01-23T16:14:13", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583898474, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "1", + "order_interval_unit": "month", + "presentment_currency": "USD", + "price": 0, + "product_title": "(Sample) Coconut Bar Soap", + "properties": [], + "quantity": 1, + "recharge_product_id": 4331400, + "shopify_product_id": 7746395766960, + "shopify_variant_id": 44042053943472, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:14:13", + "variant_title": "" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:12:41", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583897956, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Colour", + "value": "Yellow" + }, + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 3, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:12:41", + "variant_title": "5" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T07:40:28", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583551250, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Colour", + "value": "Yellow" + }, + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 3, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T07:40:28", + "variant_title": "5" + } + ] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:53 GMT", + "content-type": "application/json", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "909a0c6e1bcc7d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "https://admin.rechargeapps.com", + "set-cookie": "session=eyJfcmNzaSI6eyJkIjpudWxsLCJoIjoiYXBpLnJlY2hhcmdlYXBwcy5jb20iLCJ2IjoyfX0.Z5pAxQ.sems4gIZfCCISAo40ianuAc-Lx8; Secure; HttpOnly; Path=/; SameSite=Lax", + "strict-transport-security": "max-age=63072000; includeSubdomains", + "vary": "Accept-Encoding, Origin, Cookie", + "access-control-expose-headers": "link, x-request-id, x-recharge-limit, x-recharge-version", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "499e3e5c-8c13-4260", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2392", + "x-ratelimit-reset": "1738162428", + "x-recharge-limit": "1/40", + "x-recharge-version": "2021-01", + "x-render-origin-server": "nginx", + "x-request-id": "0aa51e15f705cd2580f53cbb5ec438a0", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/1269c26df500987fbc4cdd019d6519fa7a1d06c5.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/1269c26df500987fbc4cdd019d6519fa7a1d06c5.json new file mode 100644 index 000000000..05468ebb2 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/1269c26df500987fbc4cdd019d6519fa7a1d06c5.json @@ -0,0 +1,45 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "1269c26df500987fbc4cdd019d6519fa7a1d06c5", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176284917], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:52 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "24", + "connection": "keep-alive", + "cf-ray": "909a0c6a6fb47d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"14-84UXmzt9GryKISCn7zEuKHQXJ7g\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "3b4e4f65-b972-41dc", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2393", + "x-ratelimit-reset": "1738162428", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/2d47a8acc2e0de1feca4fb70c1cb60e4e5448b18.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/2d47a8acc2e0de1feca4fb70c1cb60e4e5448b18.json new file mode 100644 index 000000000..c316ed679 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/2d47a8acc2e0de1feca4fb70c1cb60e4e5448b18.json @@ -0,0 +1,130 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "2d47a8acc2e0de1feca4fb70c1cb60e4e5448b18", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176286578], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [ + { + "address_id": 193348320, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "1", + "created_at": "2025-01-23T16:29:15", + "customer_id": 176286578, + "email": "karimabdul@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583904731, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "1", + "order_interval_unit": "month", + "presentment_currency": "USD", + "price": 0, + "product_title": "(Sample) Coconut Bar Soap", + "properties": [], + "quantity": 1, + "recharge_product_id": 4331400, + "shopify_product_id": 7746395766960, + "shopify_variant_id": 44042053943472, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:29:15", + "variant_title": "" + }, + { + "address_id": 193348320, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:28:15", + "customer_id": 176286578, + "email": "karimabdul@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583903079, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 30, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:28:15", + "variant_title": "5" + } + ] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:50 GMT", + "content-type": "application/json", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "909a0c58cb897d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "https://admin.rechargeapps.com", + "set-cookie": "session=eyJfcmNzaSI6eyJkIjpudWxsLCJoIjoiYXBpLnJlY2hhcmdlYXBwcy5jb20iLCJ2IjoyfX0.Z5pAwQ.UhF9IUc-0h3yQm8SS7reW50CH20; Secure; HttpOnly; Path=/; SameSite=Lax", + "strict-transport-security": "max-age=63072000; includeSubdomains", + "vary": "Accept-Encoding, Origin, Cookie", + "access-control-expose-headers": "link, x-request-id, x-recharge-limit, x-recharge-version", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "7ec6f633-403d-4e83", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2398", + "x-ratelimit-reset": "1738162428", + "x-recharge-limit": "2/40", + "x-recharge-version": "2021-01", + "x-render-origin-server": "nginx", + "x-request-id": "cda306df4680cf29261cd749d54a6eb6", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/4a68db0af16c52384a28f76b1ac5bc8ac1d3e8b5.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/4a68db0af16c52384a28f76b1ac5bc8ac1d3e8b5.json new file mode 100644 index 000000000..db7bdf54b --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/4a68db0af16c52384a28f76b1ac5bc8ac1d3e8b5.json @@ -0,0 +1,381 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "4a68db0af16c52384a28f76b1ac5bc8ac1d3e8b5", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [], + "headers": [] + }, + "response": { + "subscriptions": [ + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:19:46", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583900539, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 30, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:19:46", + "variant_title": "5" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "90", + "created_at": "2025-01-23T16:16:13", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583899192, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "90", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [], + "quantity": 1, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:16:13", + "variant_title": "5" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:15:35", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583898961, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Colour", + "value": "Yellow" + }, + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 3, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:15:35", + "variant_title": "5" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:14:41", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583898646, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "30", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 80, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [], + "quantity": 1, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053910704, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:14:41", + "variant_title": "25" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "60", + "created_at": "2025-01-23T16:14:28", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583898562, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "60", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 36, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [], + "quantity": 1, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053877936, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:14:28", + "variant_title": "10" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "1", + "created_at": "2025-01-23T16:14:13", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583898474, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "1", + "order_interval_unit": "month", + "presentment_currency": "USD", + "price": 0, + "product_title": "(Sample) Coconut Bar Soap", + "properties": [], + "quantity": 1, + "recharge_product_id": 4331400, + "shopify_product_id": 7746395766960, + "shopify_variant_id": 44042053943472, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:14:13", + "variant_title": "" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:12:41", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583897956, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Colour", + "value": "Yellow" + }, + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 3, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:12:41", + "variant_title": "5" + }, + { + "address_id": 193055417, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T07:40:28", + "customer_id": 176100062, + "email": "fake@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583551250, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Colour", + "value": "Yellow" + }, + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 3, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T07:40:28", + "variant_title": "5" + } + ] + }, + "status": 200, + "headers": { + "date": "Tue, 04 Feb 2025 12:18:07 GMT", + "content-type": "application/json", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "90ca99f92b82c22f-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "https://admin.rechargeapps.com", + "set-cookie": "session=eyJfcmNzaSI6eyJkIjpudWxsLCJoIjoiYXBpLnJlY2hhcmdlYXBwcy5jb20iLCJ2IjoyfX0.Z6IFfw.-6UQSSc5cluqV1C6tA4mDuCCxBM; Secure; HttpOnly; Path=/; SameSite=Lax", + "strict-transport-security": "max-age=63072000; includeSubdomains", + "vary": "Accept-Encoding, Origin, Cookie", + "access-control-expose-headers": "link, x-request-id, x-recharge-limit, x-recharge-version", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "e93609ff-3bd6-4c77", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2384", + "x-ratelimit-reset": "1738671517", + "x-recharge-limit": "1/40", + "x-recharge-version": "2021-01", + "x-render-origin-server": "nginx", + "x-request-id": "1870a49e36ea4b8c5e6bb56807c87d53", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/4e4759424e99880c92d5879227151c33483bee46.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/4e4759424e99880c92d5879227151c33483bee46.json new file mode 100644 index 000000000..09854cc94 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/4e4759424e99880c92d5879227151c33483bee46.json @@ -0,0 +1,45 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "4e4759424e99880c92d5879227151c33483bee46", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176871450], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:45 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "24", + "connection": "keep-alive", + "cf-ray": "909a0c3b08a17d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"14-84UXmzt9GryKISCn7zEuKHQXJ7g\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "9466d433-d1f8-4ca3", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2365", + "x-ratelimit-reset": "1738162368", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/52fe8637bc375ddb4f1a005e35d924acdac4851f.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/52fe8637bc375ddb4f1a005e35d924acdac4851f.json new file mode 100644 index 000000000..47ea84634 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/52fe8637bc375ddb4f1a005e35d924acdac4851f.json @@ -0,0 +1,45 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "52fe8637bc375ddb4f1a005e35d924acdac4851f", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176385368], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:49 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "24", + "connection": "keep-alive", + "cf-ray": "909a0c51bb4f7d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"14-84UXmzt9GryKISCn7zEuKHQXJ7g\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "1c010ebd-10b2-4fb5", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2399", + "x-ratelimit-reset": "1738162428", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/84049085f67ac88b4037bf2725a0ee8d14f7c6f3.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/84049085f67ac88b4037bf2725a0ee8d14f7c6f3.json new file mode 100644 index 000000000..230473476 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/84049085f67ac88b4037bf2725a0ee8d14f7c6f3.json @@ -0,0 +1,45 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "84049085f67ac88b4037bf2725a0ee8d14f7c6f3", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176286180], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:50 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "24", + "connection": "keep-alive", + "cf-ray": "909a0c5d58a87d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"14-84UXmzt9GryKISCn7zEuKHQXJ7g\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "0eb7b0fb-9eb2-4483", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2396", + "x-ratelimit-reset": "1738162428", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/8a263996314443885401cee7ffe39e257071f62c.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/8a263996314443885401cee7ffe39e257071f62c.json new file mode 100644 index 000000000..6d35f4e41 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/8a263996314443885401cee7ffe39e257071f62c.json @@ -0,0 +1,206 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "8a263996314443885401cee7ffe39e257071f62c", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176286166], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [ + { + "address_id": 193348393, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "1", + "created_at": "2025-01-23T16:28:59", + "customer_id": 176286166, + "email": "luisgriffin@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583904109, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "1", + "order_interval_unit": "month", + "presentment_currency": "USD", + "price": 0, + "product_title": "(Sample) Coconut Bar Soap", + "properties": [], + "quantity": 1, + "recharge_product_id": 4331400, + "shopify_product_id": 7746395766960, + "shopify_variant_id": 44042053943472, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:28:59", + "variant_title": "" + }, + { + "address_id": 193348393, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "60", + "created_at": "2025-01-23T16:28:50", + "customer_id": 176286166, + "email": "luisgriffin@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583903740, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "60", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 36, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [], + "quantity": 1, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053877936, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:28:50", + "variant_title": "10" + }, + { + "address_id": 193348393, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "1", + "created_at": "2025-01-23T16:28:36", + "customer_id": 176286166, + "email": "luisgriffin@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583903181, + "is_prepaid": false, + "is_skippable": true, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "1", + "order_interval_unit": "month", + "presentment_currency": "USD", + "price": 0, + "product_title": "(Sample) Coconut Bar Soap", + "properties": [], + "quantity": 1, + "recharge_product_id": 4331400, + "shopify_product_id": 7746395766960, + "shopify_variant_id": 44042053943472, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:28:36", + "variant_title": "" + }, + { + "address_id": 193348393, + "analytics_data": { + "utm_params": [] + }, + "cancellation_reason": null, + "cancellation_reason_comments": null, + "cancelled_at": null, + "charge_interval_frequency": "30", + "created_at": "2025-01-23T16:27:24", + "customer_id": 176286166, + "email": "luisgriffin@example.com", + "expire_after_specific_number_of_charges": null, + "has_queued_charges": 1, + "id": 583902845, + "is_prepaid": true, + "is_skippable": false, + "is_swappable": false, + "max_retries_reached": 0, + "next_charge_scheduled_at": "2025-07-15T00:00:00", + "order_day_of_month": null, + "order_day_of_week": null, + "order_interval_frequency": "15", + "order_interval_unit": "day", + "presentment_currency": "USD", + "price": 20, + "product_title": "AKALO Vitamin B1 Hangover Patches", + "properties": [ + { + "name": "Bottle Material", + "value": "Glass" + } + ], + "quantity": 30, + "recharge_product_id": 4260231, + "shopify_product_id": 7746395734192, + "shopify_variant_id": 44042053845168, + "sku": null, + "sku_override": false, + "status": "ACTIVE", + "updated_at": "2025-01-23T16:27:24", + "variant_title": "5" + } + ] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:51 GMT", + "content-type": "application/json", + "transfer-encoding": "chunked", + "connection": "keep-alive", + "cf-ray": "909a0c618d4f7d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "https://admin.rechargeapps.com", + "set-cookie": "session=eyJfcmNzaSI6eyJkIjpudWxsLCJoIjoiYXBpLnJlY2hhcmdlYXBwcy5jb20iLCJ2IjoyfX0.Z5pAww.vWOON1YG5ZCvPsokVtq93KnAs4E; Secure; HttpOnly; Path=/; SameSite=Lax", + "strict-transport-security": "max-age=63072000; includeSubdomains", + "vary": "Accept-Encoding, Origin, Cookie", + "access-control-expose-headers": "link, x-request-id, x-recharge-limit, x-recharge-version", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "f3822f79-6b58-4ed0", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2395", + "x-ratelimit-reset": "1738162428", + "x-recharge-limit": "1/40", + "x-recharge-version": "2021-01", + "x-render-origin-server": "nginx", + "x-request-id": "13b9167d0409ef394ee31a5e79a54547", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/9768eae98e0165c07d74dc2a0511c955bedb194f.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/9768eae98e0165c07d74dc2a0511c955bedb194f.json new file mode 100644 index 000000000..fa07f958b --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/9768eae98e0165c07d74dc2a0511c955bedb194f.json @@ -0,0 +1,45 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "9768eae98e0165c07d74dc2a0511c955bedb194f", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176398646], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:47 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "24", + "connection": "keep-alive", + "cf-ray": "909a0c43ba937d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"14-84UXmzt9GryKISCn7zEuKHQXJ7g\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "2a9c8217-d1dc-441b", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2363", + "x-ratelimit-reset": "1738162368", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/9fe0191a85b90ed9910477ddd04b14b8e75083cc.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/9fe0191a85b90ed9910477ddd04b14b8e75083cc.json new file mode 100644 index 000000000..8f8e035f7 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/9fe0191a85b90ed9910477ddd04b14b8e75083cc.json @@ -0,0 +1,45 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "9fe0191a85b90ed9910477ddd04b14b8e75083cc", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176403041], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:46 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "24", + "connection": "keep-alive", + "cf-ray": "909a0c3fadfe7d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"14-84UXmzt9GryKISCn7zEuKHQXJ7g\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "c55b5f13-2030-48fe", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2364", + "x-ratelimit-reset": "1738162368", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/b3b36e44870b598783f8aaa798b60239b8fc8a67.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/b3b36e44870b598783f8aaa798b60239b8fc8a67.json new file mode 100644 index 000000000..4bd118a0d --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/b3b36e44870b598783f8aaa798b60239b8fc8a67.json @@ -0,0 +1,45 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "b3b36e44870b598783f8aaa798b60239b8fc8a67", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176394386], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:48 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "24", + "connection": "keep-alive", + "cf-ray": "909a0c4deeed7d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"14-84UXmzt9GryKISCn7zEuKHQXJ7g\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "3307fb2d-8b20-4b4e", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2362", + "x-ratelimit-reset": "1738162368", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/f308c440309bf92b7f0bd161bd55cdb61409a7fd.json b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/f308c440309bf92b7f0bd161bd55cdb61409a7fd.json new file mode 100644 index 000000000..e55a10d87 --- /dev/null +++ b/integrations/recharge/mocks/nango/get/proxy/subscriptions/customers/f308c440309bf92b7f0bd161bd55cdb61409a7fd.json @@ -0,0 +1,45 @@ +{ + "method": "get", + "endpoint": "subscriptions", + "requestIdentityHash": "f308c440309bf92b7f0bd161bd55cdb61409a7fd", + "requestIdentity": { + "method": "get", + "endpoint": "subscriptions", + "params": [ + ["customer_id", 176286145], + ["limit", 100], + ["sort_by", "created_at-desc"] + ], + "headers": [] + }, + "response": { + "subscriptions": [] + }, + "status": 200, + "headers": { + "date": "Wed, 29 Jan 2025 14:52:52 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "24", + "connection": "keep-alive", + "cf-ray": "909a0c663b057d98-TLV", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"14-84UXmzt9GryKISCn7zEuKHQXJ7g\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "10c34b1f-4a42-4f09", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2394", + "x-ratelimit-reset": "1738162428", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/nango/post/proxy/customers/upsert-customers/7c02ce0a879ede3358482895f2b3d896810fe4f9.json b/integrations/recharge/mocks/nango/post/proxy/customers/upsert-customers/7c02ce0a879ede3358482895f2b3d896810fe4f9.json new file mode 100644 index 000000000..fe644fed1 --- /dev/null +++ b/integrations/recharge/mocks/nango/post/proxy/customers/upsert-customers/7c02ce0a879ede3358482895f2b3d896810fe4f9.json @@ -0,0 +1,73 @@ +{ + "method": "post", + "endpoint": "customers", + "requestIdentityHash": "41ba86743ce7f3000d6e61fc811886b4188df8eb", + "requestIdentity": { + "method": "post", + "endpoint": "customers", + "params": [], + "headers": [["Content-Length", "127"]], + "data": "{\"first_name\":\"Anther Test\",\"last_name\":\"User\",\"email\":\"anothertestuser@example.com\",\"phone\":\"+447196746473\",\"tax_exempt\":true}" + }, + "response": { + "customer": { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-27T07:14:41", + "email": "anothertestuser@example.com", + "first_charge_processed_at": null, + "first_name": "Anther Test", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "d190babfe56f6278298d1b89342c41", + "id": 176871450, + "last_name": "User", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": null, + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-27T07:14:41" + } + }, + "status": 200, + "headers": { + "date": "Mon, 27 Jan 2025 12:14:41 GMT", + "content-type": "application/json; charset=utf-8", + "content-length": "410", + "connection": "keep-alive", + "cf-ray": "9088a9ef5f05b199-NBO", + "cf-cache-status": "DYNAMIC", + "access-control-allow-origin": "*", + "etag": "W/\"317-8GAZlqzaVbJOvKjaV5URVlzcYek\"", + "strict-transport-security": "max-age=5184000; includeSubDomains", + "vary": "Accept-Encoding", + "access-control-expose-headers": "Authorization, Etag, Content-Type, Content-Length, X-Nango-Signature, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset", + "content-security-policy-report-only": "default-src 'self' https://app.nango.dev https://api.nango.dev;child-src 'self';connect-src 'self' https://*.google-analytics.com https://*.sentry.io https://app.nango.dev https://api.nango.dev wss://api.nango.dev/ https://*.posthog.com https://bluegrass.nango.dev;font-src 'self' https://*.googleapis.com https://*.gstatic.com;frame-src 'self' https://accounts.google.com https://app.nango.dev https://api.nango.dev https://connect.nango.dev https://www.youtube.com;img-src 'self' data: https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://*.posthog.com https://img.logo.dev https://*.ytimg.com;manifest-src 'self';media-src 'self';object-src 'self';script-src 'self' 'unsafe-eval' 'unsafe-inline' https://app.nango.dev https://api.nango.dev https://*.google-analytics.com https://*.googleapis.com https://apis.google.com https://*.posthog.com https://www.youtube.com https://shoegaze.nango.dev;style-src blob: 'self' 'unsafe-inline' https://*.googleapis.com https://app.nango.dev https://api.nango.dev;worker-src blob: 'self' https://app.nango.dev https://api.nango.dev https://*.googleapis.com https://*.posthog.com;base-uri 'self';form-action 'self';frame-ancestors 'self';script-src-attr 'none';upgrade-insecure-requests", + "rndr-id": "42b30aac-bd8a-42fb", + "x-content-type-options": "nosniff", + "x-dns-prefetch-control": "off", + "x-download-options": "noopen", + "x-frame-options": "SAMEORIGIN", + "x-ratelimit-limit": "2400", + "x-ratelimit-remaining": "2383", + "x-ratelimit-reset": "1737980084", + "x-render-origin-server": "Render", + "x-xss-protection": "0", + "server": "cloudflare", + "alt-svc": "h3=\":443\"; ma=86400" + } +} diff --git a/integrations/recharge/mocks/upsert-customers/input.json b/integrations/recharge/mocks/upsert-customers/input.json new file mode 100644 index 000000000..505233330 --- /dev/null +++ b/integrations/recharge/mocks/upsert-customers/input.json @@ -0,0 +1,7 @@ +{ + "first_name": "Anther Test", + "last_name": "User", + "email": "anothertestuser@example.com", + "phone": "+447196746473", + "tax_exempt": true +} diff --git a/integrations/recharge/mocks/upsert-customers/output.json b/integrations/recharge/mocks/upsert-customers/output.json new file mode 100644 index 000000000..2ccc3b32f --- /dev/null +++ b/integrations/recharge/mocks/upsert-customers/output.json @@ -0,0 +1,35 @@ +{ + "action": "create", + "response": { + "accepts_marketing": null, + "analytics_data": { + "utm_params": [] + }, + "billing_address1": null, + "billing_address2": null, + "billing_city": null, + "billing_company": null, + "billing_country": null, + "billing_phone": "+447196746473", + "billing_province": null, + "billing_zip": null, + "created_at": "2025-01-27T07:14:41", + "email": "anothertestuser@example.com", + "first_charge_processed_at": null, + "first_name": "Anther Test", + "has_card_error_in_dunning": false, + "has_valid_payment_method": false, + "hash": "d190babfe56f6278298d1b89342c41", + "id": 176871450, + "last_name": "User", + "number_active_subscriptions": 0, + "number_subscriptions": 0, + "phone": "+447196746473", + "processor_type": null, + "reason_payment_method_not_valid": "NOT_VALIDATED", + "shopify_customer_id": null, + "status": "INACTIVE", + "tax_exempt": false, + "updated_at": "2025-01-27T07:14:41" + } +} diff --git a/integrations/recharge/nango.yaml b/integrations/recharge/nango.yaml new file mode 100644 index 000000000..de4210fff --- /dev/null +++ b/integrations/recharge/nango.yaml @@ -0,0 +1,92 @@ +integrations: + recharge: + actions: + upsert-customers: + endpoint: + method: POST + path: /customers + group: Customers + description: Upsert a customer in Recharge + input: UpsertRechargeCustomerInput + output: UpsertRechargeCustomerOutput + scopes: read_customers, write_customers, write_payment_methods + syncs: + customers: + sync_type: incremental + endpoint: + method: GET + path: /customers + group: Customers + description: Incrementally fetch all Recharge customers and their subscription details. + runs: every 1 hour + output: Customer + scopes: read_customers, read_subscriptions + version: 1.0.1 + +models: + Customer: + id: string + phone_number: string | null + first_name: string | null + last_name: string | null + subscriptions: + - id: string + type: string + name: string + start_date: string + end_date: string | null + next_charge_scheduled_at: string | null + UpsertRechargeCustomerInput: + email: string + external_customer_id?: ExternalCustomerId | undefined + first_name: string + last_name: string + phone?: string | undefined + tax_exempt?: boolean | undefined + UpsertRechargeCustomerOutput: + action: update | create + response: + accepts_marketing: number | null + analytics_data: + utm_params: + - utm_campaign?: string | undefined + utm_content?: string | undefined + utm_data_source?: string | undefined + utm_source?: string | undefined + utm_medium?: string | undefined + utm_term?: string | undefined + utm_timestamp?: string | undefined + billing_address1: string | null + billing_address2: string | null + billing_city: string | null + billing_company: string | null + billing_country: string | null + billing_phone: string | null + billing_province: string | null + billing_zip: string | null + created_at: string + email: string + first_charge_processed_at: string | null + first_name: string + has_card_error_in_dunning: boolean + has_valid_payment_method: boolean + hash: string + id: number + last_name: string + number_active_subscriptions: number + number_subscriptions: number + phone: string | null + processor_type: string | null + reason_payment_method_not_valid: string | null + shopify_customer_id: string | null + status: string + tax_exempt: boolean + updated_at: string + apply_credit_to_next_recurring_charge?: boolean | undefined + external_customer_id?: ExternalCustomerId | undefined + has_payment_method_in_dunning?: boolean | undefined + subscriptions_active_count?: number | undefined + subscriptions_total_count?: number | undefined + subscription_related_charge_streak?: number | undefined + ExternalCustomerId: + ecommerce: string diff --git a/integrations/recharge/schema.zod.ts b/integrations/recharge/schema.zod.ts new file mode 100644 index 000000000..403edc43c --- /dev/null +++ b/integrations/recharge/schema.zod.ts @@ -0,0 +1,82 @@ +// Generated by ts-to-zod +import { z } from 'zod'; + +export const customerSchema = z.object({ + id: z.string(), + phone_number: z.string().nullable(), + first_name: z.string().nullable(), + last_name: z.string().nullable(), + subscriptions: z.array( + z.object({ + type: z.string(), + name: z.string(), + start_date: z.string(), + end_date: z.string().nullable() + }) + ) +}); + +export const externalCustomerIdSchema = z.object({ + ecommerce: z.string() +}); + +export const upsertRechargeCustomerInputSchema = z.object({ + email: z.string(), + external_customer_id: z.union([externalCustomerIdSchema, z.undefined()]).optional(), + first_name: z.string(), + last_name: z.string(), + phone: z.union([z.string(), z.undefined()]).optional(), + tax_exempt: z.union([z.boolean(), z.undefined()]).optional() +}); + +export const upsertRechargeCustomerOutputSchema = z.object({ + action: z.union([z.literal('update'), z.literal('create')]), + response: z.object({ + accepts_marketing: z.number().nullable(), + analytics_data: z.object({ + utm_params: z.array( + z.object({ + utm_campaign: z.union([z.string(), z.undefined()]).optional(), + utm_content: z.union([z.string(), z.undefined()]).optional(), + utm_data_source: z.union([z.string(), z.undefined()]).optional(), + utm_source: z.union([z.string(), z.undefined()]).optional(), + utm_medium: z.union([z.string(), z.undefined()]).optional(), + utm_term: z.union([z.string(), z.undefined()]).optional(), + utm_timestamp: z.union([z.string(), z.undefined()]).optional() + }) + ) + }), + billing_address1: z.string().nullable(), + billing_address2: z.string().nullable(), + billing_city: z.string().nullable(), + billing_company: z.string().nullable(), + billing_country: z.string().nullable(), + billing_phone: z.string().nullable(), + billing_province: z.string().nullable(), + billing_zip: z.string().nullable(), + created_at: z.string(), + email: z.string(), + first_charge_processed_at: z.string().nullable(), + first_name: z.string(), + has_card_error_in_dunning: z.boolean(), + has_valid_payment_method: z.boolean(), + hash: z.string(), + id: z.number(), + last_name: z.string(), + number_active_subscriptions: z.number(), + number_subscriptions: z.number(), + phone: z.string().nullable(), + processor_type: z.string().nullable(), + reason_payment_method_not_valid: z.string().nullable(), + shopify_customer_id: z.string().nullable(), + status: z.string(), + tax_exempt: z.boolean(), + updated_at: z.string(), + apply_credit_to_next_recurring_charge: z.union([z.boolean(), z.undefined()]).optional(), + external_customer_id: z.union([externalCustomerIdSchema, z.undefined()]).optional(), + has_payment_method_in_dunning: z.union([z.boolean(), z.undefined()]).optional(), + subscriptions_active_count: z.union([z.number(), z.undefined()]).optional(), + subscriptions_total_count: z.union([z.number(), z.undefined()]).optional(), + subscription_related_charge_streak: z.union([z.number(), z.undefined()]).optional() + }) +}); diff --git a/integrations/recharge/syncs/customers.md b/integrations/recharge/syncs/customers.md new file mode 100644 index 000000000..15985499c --- /dev/null +++ b/integrations/recharge/syncs/customers.md @@ -0,0 +1,58 @@ + +# Customers + +## General Information + +- **Description:** Incrementally fetch all Recharge customers and their subscription details. +- **Version:** 1.0.1 +- **Group:** Customers +- **Scopes:** `read_customers, read_subscriptions` +- **Endpoint Type:** Sync +- **Code:** [github.com](https://github.com/NangoHQ/integration-templates/tree/main/integrations/recharge/syncs/customers.ts) + + +## Endpoint Reference + +### Request Endpoint + +`GET /customers` + +### Request Query Parameters + +- **modified_after:** `(optional, string)` A timestamp (e.g., `2023-05-31T11:46:13.390Z`) used to fetch records modified after this date and time. If not provided, all records are returned. The modified_after parameter is less precise than cursor, as multiple records may share the same modification timestamp. +- **limit:** `(optional, integer)` The maximum number of records to return per page. Defaults to 100. +- **cursor:** `(optional, string)` A marker used to fetch records modified after a specific point in time.If not provided, all records are returned.Each record includes a cursor value found in _nango_metadata.cursor.Save the cursor from the last record retrieved to track your sync progress.Use the cursor parameter together with the limit parameter to paginate through records.The cursor is more precise than modified_after, as it can differentiate between records with the same modification timestamp. +- **filter:** `(optional, added | updated | deleted)` Filter to only show results that have been added or updated or deleted. + +### Request Body + +_No request body_ + +### Request Response + +```json +{ + "id": "", + "phone_number": "", + "first_name": "", + "last_name": "", + "subscriptions": { + "0": { + "id": "", + "type": "", + "name": "", + "start_date": "", + "end_date": "", + "next_charge_scheduled_at": "" + } + } +} +``` + +## Changelog + +- [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/recharge/syncs/customers.ts) +- [Documentation History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/recharge/syncs/customers.md) + + + diff --git a/integrations/recharge/syncs/customers.ts b/integrations/recharge/syncs/customers.ts new file mode 100644 index 000000000..8ed69eca0 --- /dev/null +++ b/integrations/recharge/syncs/customers.ts @@ -0,0 +1,62 @@ +import type { NangoSync, Customer, ProxyConfiguration } from '../../models'; +import { toCustomer } from '../mappers/to-customer.js'; +import type { RechargeCustomer, RechargeSubscription } from '../types'; + +export default async function fetchData(nango: NangoSync): Promise { + const customerConfig: ProxyConfiguration = { + // https://developer.rechargepayments.com/2021-11/customers/customers_list + endpoint: '/customers', + paginate: { + type: 'link', + limit_name_in_request: 'limit', + link_rel_in_response_header: 'next', + response_path: 'customers', + limit: 100 + }, + params: { + ...(nango.lastSyncDate && { updated_at_min: nango.lastSyncDate.toISOString() }), + sort_by: 'created_at-desc' + }, + retries: 10 + }; + + const mappedCustomers: Customer[] = []; + + for await (const customers of nango.paginate(customerConfig)) { + for (const customer of customers) { + const subscriptions = await fetchSubscriptions(customer.id, nango); + const mappedForCustomer = toCustomer(customer, subscriptions); + mappedCustomers.push(mappedForCustomer); + } + } + if (mappedCustomers.length > 0) { + await nango.batchSave(mappedCustomers, 'Customer'); + } +} + +async function fetchSubscriptions(customerId: number, nango: NangoSync): Promise { + const subscriptionConfig: ProxyConfiguration = { + // https://developer.rechargepayments.com/2021-11/subscriptions/subscriptions_list + endpoint: '/subscriptions', + paginate: { + type: 'link', + limit_name_in_request: 'limit', + link_rel_in_response_header: 'next', + response_path: 'subscriptions', + limit: 100 + }, + params: { + customer_id: customerId, + sort_by: 'created_at-desc', + ...(nango.lastSyncDate && { updated_at_min: nango.lastSyncDate.toISOString() }) + }, + retries: 10 + }; + + const subscriptions: RechargeSubscription[] = []; + for await (const batch of nango.paginate(subscriptionConfig)) { + subscriptions.push(...batch); + } + + return subscriptions; +} diff --git a/integrations/recharge/tests/recharge-upsert-customers.test.ts b/integrations/recharge/tests/recharge-upsert-customers.test.ts new file mode 100644 index 000000000..05474d836 --- /dev/null +++ b/integrations/recharge/tests/recharge-upsert-customers.test.ts @@ -0,0 +1,19 @@ +import { vi, expect, it, describe } from 'vitest'; + +import runAction from '../actions/upsert-customers.js'; + +describe('recharge upsert-customers tests', () => { + const nangoMock = new global.vitest.NangoActionMock({ + dirname: __dirname, + name: 'upsert-customers', + Model: 'UpsertRechargeCustomerOutput' + }); + + 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); + }); +}); diff --git a/integrations/recharge/types.ts b/integrations/recharge/types.ts new file mode 100644 index 000000000..099bb7d77 --- /dev/null +++ b/integrations/recharge/types.ts @@ -0,0 +1,104 @@ +export interface RechargeCustomer { + accepts_marketing: number | null; + analytics_data: { + utm_params: { + utm_campaign?: string; + utm_content?: string; + utm_data_source?: string; + utm_source?: string; + utm_medium?: string; + utm_term?: string; + utm_timestamp?: string; + }[]; + }; + billing_address1: string | null; + billing_address2: string | null; + billing_city: string | null; + billing_company: string | null; + billing_country: string | null; + billing_phone: string | null; + billing_province: string | null; + billing_zip: string | null; + created_at: string; + email: string; + first_charge_processed_at: string | null; + first_name: string; + has_card_error_in_dunning: boolean; + has_valid_payment_method: boolean; + hash: string; + id: number; + last_name: string; + number_active_subscriptions: number; + number_subscriptions: number; + phone: string | null; + processor_type: string | null; + reason_payment_method_not_valid: string | null; + shopify_customer_id: string; + status: string; + tax_exempt: boolean; + updated_at: string; + apply_credit_to_next_recurring_charge?: boolean; + external_customer_id?: { + ecommerce: string; + }; + has_payment_method_in_dunning?: boolean; + subscriptions_active_count?: number; + subscriptions_total_count?: number; + subscription_related_charge_streak?: number; +} + +export interface RechargeSubscription { + address_id: number; + analytics_data: { + utm_params: { + utm_campaign?: string; + utm_content?: string; + utm_data_source?: string; + utm_source?: string; + utm_medium?: string; + utm_term?: string; + utm_timestamp?: string; + }[]; + }; + cancellation_reason: string | null; + cancellation_reason_comments: string | null; + cancelled_at: string | null; + charge_interval_frequency: string; + created_at: string; + customer_id: number; + email: string; + expire_after_specific_number_of_charges: number | null; + has_queued_charges: number; + id: number; + is_prepaid: boolean; + is_skippable: boolean; + is_swappable: boolean; + max_retries_reached: number; + next_charge_scheduled_at: string | null; + order_day_of_month: number | null; + order_day_of_week: number | null; + order_interval_frequency: string; + order_interval_unit: 'day' | 'week' | 'month'; + presentment_currency: string; + price: number; + product_title: string; + properties: { + name: string; + value: string; + }[]; + quantity: number; + recharge_product_id: number; + shopify_product_id: number; + shopify_variant_id: number; + sku: string | null; + sku_override: boolean; + status: 'active' | 'cancelled' | 'expired'; + updated_at: string; + variant_title: string; + external_product_id?: { + ecommerce: string; + }; + external_variant_id?: { + ecommerce: string; + }; +} diff --git a/integrations/salesforce/syncs/articles.md b/integrations/salesforce/syncs/articles.md index dde509696..81fc9f3d5 100644 --- a/integrations/salesforce/syncs/articles.md +++ b/integrations/salesforce/syncs/articles.md @@ -40,6 +40,16 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "customFields": [ + "" + ] +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/salesforce/syncs/articles.ts) diff --git a/integrations/sharepoint-online/syncs/shared-sites-selection.md b/integrations/sharepoint-online/syncs/shared-sites-selection.md index 36e7bb0cf..4a1f194a1 100644 --- a/integrations/sharepoint-online/syncs/shared-sites-selection.md +++ b/integrations/sharepoint-online/syncs/shared-sites-selection.md @@ -49,6 +49,24 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "sharedSites": [ + "" + ], + "pickedFiles": [ + { + "siteId": "", + "fileIds": [ + "" + ] + } + ] +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/sharepoint-online/syncs/shared-sites-selection.ts) diff --git a/integrations/sharepoint-online/syncs/user-files-selection.md b/integrations/sharepoint-online/syncs/user-files-selection.md index 0736e711d..8203c2386 100644 --- a/integrations/sharepoint-online/syncs/user-files-selection.md +++ b/integrations/sharepoint-online/syncs/user-files-selection.md @@ -48,6 +48,24 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "sharedSites": [ + "" + ], + "pickedFiles": [ + { + "siteId": "", + "fileIds": [ + "" + ] + } + ] +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/sharepoint-online/syncs/user-files-selection.ts) diff --git a/integrations/zoom/syncs/recording-files.md b/integrations/zoom/syncs/recording-files.md index bcbf5b4c2..586c581ab 100644 --- a/integrations/zoom/syncs/recording-files.md +++ b/integrations/zoom/syncs/recording-files.md @@ -52,6 +52,14 @@ _No request body_ } ``` +### Expected Metadata + +```json +{ + "backfillPeriodDays": "" +} +``` + ## Changelog - [Script History](https://github.com/NangoHQ/integration-templates/commits/main/integrations/zoom/syncs/recording-files.ts) diff --git a/package-lock.json b/package-lock.json index b43ea4172..6a5dc6406 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "devDependencies": { "@nangohq/eslint-config": "1.0.6", "@nangohq/eslint-plugin-custom-integrations-linting": "0.0.4", - "@nangohq/shared": "^0.48.1", + "@nangohq/shared": "^0.49.0", "@nangohq/types": "^0.48.1", "@types/ejs": "^3.1.2", "@types/js-yaml": "4.0.9", @@ -24,7 +24,7 @@ "js-yaml": "4.1.0", "lint-staged": "15.2.9", "lodash-es": "^4.17.21", - "nango": "^0.48.3", + "nango": "^0.52.5", "onchange": "7.1.0", "parse-link-header": "^2.0.0", "soap": "1.1.2", @@ -2177,9 +2177,9 @@ "dev": true }, "node_modules/@nangohq/nango-yaml": { - "version": "0.48.2", - "resolved": "https://registry.npmjs.org/@nangohq/nango-yaml/-/nango-yaml-0.48.2.tgz", - "integrity": "sha512-it3QYEUrP7A9D9VUv43gOPV01GpSSi7eoeHH7yo6Sn1LaR7hCs99bpnv8PQakuhfv+QDJhegBwdhMeuoorxiAA==", + "version": "0.48.4", + "resolved": "https://registry.npmjs.org/@nangohq/nango-yaml/-/nango-yaml-0.48.4.tgz", + "integrity": "sha512-Vg4/84Xa6StlgiI/ufebwReJBZR8x2dk4wU2vD+dI6+xLJndMBlat2VBtAA+Busu3cugKQKLxtbipexQGLZvmg==", "dev": true, "dependencies": { "js-yaml": "^4.1.0", @@ -2191,14 +2191,52 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", "dev": true, + "license": "MIT", "engines": { "node": ">=12.13" } }, "node_modules/@nangohq/node": { - "version": "0.48.3", - "resolved": "https://registry.npmjs.org/@nangohq/node/-/node-0.48.3.tgz", - "integrity": "sha512-Gekg7Z/51+XBfz6i8MR5fhoHqcce6ixl3t5FLWumx70vxSKoaT1TXF6zbIoVCNkHbIMRBbPgdw95q4Urw2C/Qg==", + "version": "0.48.4", + "resolved": "https://registry.npmjs.org/@nangohq/node/-/node-0.48.4.tgz", + "integrity": "sha512-77yocmC4BxEDEEWczuCRDANbXa0otttX/P8F34Qr9A/aq0q0GdIqmHpUZQy7wHCWu4s5FWvFxngBKS+J2jEsjw==", + "dev": true, + "license": "SEE LICENSE IN LICENSE FILE IN GIT REPOSITORY", + "dependencies": { + "axios": "^1.7.9" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@nangohq/providers": { + "version": "0.52.5", + "resolved": "https://registry.npmjs.org/@nangohq/providers/-/providers-0.52.5.tgz", + "integrity": "sha512-0a6SN8l23DcZntLtF4vqsfPcE3u79hF+5QPpIsBrn+N/NyHj7cqCrJuJ5y1ovzPvTzP9CMIYlR/7vNfuHs5DHg==", + "dev": true, + "dependencies": { + "js-yaml": "4.1.0" + } + }, + "node_modules/@nangohq/runner-sdk": { + "version": "0.52.5", + "resolved": "https://registry.npmjs.org/@nangohq/runner-sdk/-/runner-sdk-0.52.5.tgz", + "integrity": "sha512-XRmL6ZPe9VghROF1lMEdiEFEdif5g1pFz0Gbl90uYI0KMxButU0ubG8W6Kb5xjxMbv9vF1YZeWIMH48wp4p51w==", + "dev": true, + "dependencies": { + "@nangohq/node": "0.52.5", + "@nangohq/providers": "0.52.5", + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "axios": "1.7.9", + "lodash-es": "4.17.21", + "parse-link-header": "2.0.0" + } + }, + "node_modules/@nangohq/runner-sdk/node_modules/@nangohq/node": { + "version": "0.52.5", + "resolved": "https://registry.npmjs.org/@nangohq/node/-/node-0.52.5.tgz", + "integrity": "sha512-8/4u4Jh8fDg0i5ASVewFA3mdKRGYp35Q2Hp5Rqv69G57CdOHp7anR+WgLBtilv0qoEAmhJR45zTlwS0CHWkryw==", "dev": true, "dependencies": { "axios": "^1.7.9" @@ -2208,21 +2246,23 @@ } }, "node_modules/@nangohq/shared": { - "version": "0.48.3", - "resolved": "https://registry.npmjs.org/@nangohq/shared/-/shared-0.48.3.tgz", - "integrity": "sha512-lCUjHurF5lckbxOW49TBf7gyM7Xk7IGaAbmHz9qkeHtkTYV4HOztg4O243QrgkNBSF9TIW5t9oTFkGHowaCRIA==", + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/@nangohq/shared/-/shared-0.49.0.tgz", + "integrity": "sha512-hP1ZL2kfwEQmEFJ1HuUF8HpJDSaWPUv2osQPsYinhuHq7AjfiOjM+Bq2Lrd4DvI/r2rt3zLcK0UePl9FcYjKZg==", "bundleDependencies": [ "@nangohq/utils", "@nangohq/database" ], "dev": true, + "license": "SEE LICENSE IN LICENSE FILE IN GIT REPOSITORY", "dependencies": { "@aws-sdk/client-s3": "3.637.0", "@datadog/datadog-api-client": "1.26.0", "@hapi/boom": "^10.0.1", "@nangohq/database": "file:vendor/nangohq-database-1.0.0.tgz", - "@nangohq/nango-yaml": "0.48.2", - "@nangohq/node": "^0.48.3", + "@nangohq/nango-yaml": "0.48.4", + "@nangohq/node": "^0.48.4", + "@nangohq/providers": "0.48.4", "@nangohq/utils": "file:vendor/nangohq-utils-1.0.0.tgz", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", @@ -2506,7 +2546,7 @@ "license": "BSD-3-Clause" }, "node_modules/@nangohq/shared/node_modules/@nangohq/database/node_modules/@nangohq/utils/node_modules/@types/node": { - "version": "22.10.6", + "version": "22.10.10", "dev": true, "inBundle": true, "license": "MIT", @@ -3859,6 +3899,15 @@ "node": ">=0.4" } }, + "node_modules/@nangohq/shared/node_modules/@nangohq/providers": { + "version": "0.48.4", + "resolved": "https://registry.npmjs.org/@nangohq/providers/-/providers-0.48.4.tgz", + "integrity": "sha512-C67B7zbYK3qz8iBYUpNkOe36Ifatx5X5VN9KzDE0xlMIMhkFUma4jiNTwRuNPR+VLNpIlLNV46rYgChDr6tn3w==", + "dev": true, + "dependencies": { + "js-yaml": "4.1.0" + } + }, "node_modules/@nangohq/shared/node_modules/@nangohq/utils": { "version": "1.0.0", "bundleDependencies": [ @@ -4095,7 +4144,7 @@ "license": "BSD-3-Clause" }, "node_modules/@nangohq/shared/node_modules/@nangohq/utils/node_modules/@types/node": { - "version": "22.10.6", + "version": "22.10.10", "dev": true, "inBundle": true, "license": "MIT", @@ -8313,9 +8362,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "dependencies": { "path-key": "^3.1.0", @@ -11823,16 +11872,17 @@ } }, "node_modules/nango": { - "version": "0.48.3", - "resolved": "https://registry.npmjs.org/nango/-/nango-0.48.3.tgz", - "integrity": "sha512-JhOFxtS6OGYaz7wdD3iPoV7I8K+SOVxfs9oKRMQVJh950ny3/P8gdn4WgWFPjMypharstjQvXvy1VymXEk6n9g==", + "version": "0.52.5", + "resolved": "https://registry.npmjs.org/nango/-/nango-0.52.5.tgz", + "integrity": "sha512-2T7Hfc5M2ziGl+qnRz9BSsWBIwO00k0PGJfHZf7AqUqnpOKTPd2GeWYx/f/5GLD0z4CRyVc9bWuk4nfmMqSu+A==", "dev": true, "dependencies": { "@babel/parser": "^7.22.5", "@babel/traverse": "^7.22.5", "@babel/types": "^7.22.5", - "@nangohq/nango-yaml": "0.48.2", - "@nangohq/shared": "^0.48.3", + "@nangohq/nango-yaml": "0.52.5", + "@nangohq/node": "0.52.5", + "@nangohq/runner-sdk": "0.52.5", "@swc/core": "^1.5.25", "ajv": "^8.17.1", "ajv-errors": "^3.0.0", @@ -11865,11 +11915,43 @@ "node": ">=18.0" } }, + "node_modules/nango/node_modules/@nangohq/nango-yaml": { + "version": "0.52.5", + "resolved": "https://registry.npmjs.org/@nangohq/nango-yaml/-/nango-yaml-0.52.5.tgz", + "integrity": "sha512-A3plJBYFS8mG0w7DorZ25J238WP43kBE6ZXUDm+SbSDEfKR1LEk2hv/G2kBNZhFyY45pU3gy/BacgbVI6/W6aA==", + "dev": true, + "dependencies": { + "js-yaml": "^4.1.0", + "ms": "3.0.0-canary.1" + } + }, + "node_modules/nango/node_modules/@nangohq/node": { + "version": "0.52.5", + "resolved": "https://registry.npmjs.org/@nangohq/node/-/node-0.52.5.tgz", + "integrity": "sha512-8/4u4Jh8fDg0i5ASVewFA3mdKRGYp35Q2Hp5Rqv69G57CdOHp7anR+WgLBtilv0qoEAmhJR45zTlwS0CHWkryw==", + "dev": true, + "dependencies": { + "axios": "^1.7.9" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/nango/node_modules/ms": { + "version": "3.0.0-canary.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-3.0.0-canary.1.tgz", + "integrity": "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g==", + "dev": true, + "engines": { + "node": ">=12.13" + } + }, "node_modules/nango/node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11883,14 +11965,15 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "dev": true, "funding": [ { @@ -12505,11 +12588,10 @@ "license": "ISC" }, "node_modules/path-to-regexp": { - "version": "0.1.11", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.11.tgz", - "integrity": "sha512-c0t+KCuUkO/YDLPG4WWzEwx3J5F/GHXsD1h/SNZfySqAIKe/BaP95x8fWtOfRJokpS5yYHRJjMtYlXD8jxnpbw==", - "dev": true, - "license": "MIT" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "dev": true }, "node_modules/path-type": { "version": "4.0.0", @@ -15479,9 +15561,9 @@ } }, "node_modules/vite": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.4.tgz", - "integrity": "sha512-RHFCkULitycHVTtelJ6jQLd+KSAAzOgEYorV32R2q++M6COBjKJR6BxqClwp5sf0XaBDjVMuJ9wnNfyAJwjMkA==", + "version": "5.4.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz", + "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "dependencies": { "esbuild": "^0.21.3", diff --git a/package.json b/package.json index 3c67302ba..b01d05326 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "devDependencies": { "@nangohq/eslint-config": "1.0.6", "@nangohq/eslint-plugin-custom-integrations-linting": "0.0.4", - "@nangohq/shared": "^0.48.1", + "@nangohq/shared": "^0.49.0", "@nangohq/types": "^0.48.1", "@types/ejs": "^3.1.2", "@types/js-yaml": "4.0.9", @@ -38,7 +38,7 @@ "js-yaml": "4.1.0", "lint-staged": "15.2.9", "lodash-es": "^4.17.21", - "nango": "^0.48.3", + "nango": "^0.52.5", "onchange": "7.1.0", "parse-link-header": "^2.0.0", "soap": "1.1.2", diff --git a/scripts/generate-readmes.ts b/scripts/generate-readmes.ts index 5f91b7ac2..a9c71a82f 100644 --- a/scripts/generate-readmes.ts +++ b/scripts/generate-readmes.ts @@ -66,8 +66,11 @@ function updateReadme(markdown: string, scriptName: string, scriptPath: string, requestParams(endpointType), requestBody(scriptConfig, endpointType, models), requestResponse(scriptConfig, models), + expectedMetadata(scriptConfig, endpointType, models), changelog(scriptPath) - ].join('\n'); + ] + .filter((lines) => lines !== undefined) + .join('\n'); return `${generatedLines}\n${divider}\n${custom.trim()}\n`; } @@ -153,6 +156,18 @@ function requestResponse(scriptConfig: any, models: any) { return out.join('\n'); } +function expectedMetadata(scriptConfig: any, endpointType: string, models: any) { + if (endpointType === 'sync' && scriptConfig.input) { + const out = ['### Expected Metadata']; + + const expanded = expandModels(scriptConfig.input, models); + const expandedLines = JSON.stringify(expanded, null, 2).split('\n'); + out.push(``, `\`\`\`json`, ...expandedLines, `\`\`\``, ``); + + return out.join('\n'); + } +} + function changelog(scriptPath: string) { return [ '## Changelog',