multiwoven-integrations 0.1.34 → 0.1.36

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32abb3bef8987a8a76828570507e207089efb5c7c59dd21093774beeef21a265
4
- data.tar.gz: 9920eb296b386ef3add818a1e7b59ef26e7e8c32aee8a21e8295b82deebc4d44
3
+ metadata.gz: 818c8606d1ef1b50bd14765b3ed85f687ef5e16cdd975324bfbf28ed2b371070
4
+ data.tar.gz: 0bd41ef2fe575ff51d5ea5878522509de3aa6ae0b3592074c15ce3de5be97c51
5
5
  SHA512:
6
- metadata.gz: 33846b75d3a91aedf93fa367d0cd2385f7dd06066015a782cd149dd1cedefd055f29c9c007062988b13a18b9eb4efac1afd250a4b47531c1fcfd5d0b0f5619c9
7
- data.tar.gz: 6faa53d0929c0d5ae12da7467e56573e86e1ff3b1898f96762150440a19fff351796198e2fe03f72a12a7008762391ac4714a3f5d5a9703602e6acaf328d27ba
6
+ metadata.gz: dfaa4c53e72fbf3fc64d2d746bf4fd632c19be834af900a93eb88060fc1a84156b1849b645768ca41ebd3037f205a7b4f79430738eddb7f607b526fb9424c08b
7
+ data.tar.gz: dd7dc877f6ad86082975cb9dbdc714bc5e9dc623196178e6c9c11b601deab967fa107d1d218d5dc03e43b6b6c580403d45bc470fd364a109a8a1cb6e8a793520
@@ -18,6 +18,8 @@ module Multiwoven
18
18
  SNOWFLAKE_DRIVER_PATH = ENV["SNOWFLAKE_DRIVER_PATH"] || SNOWFLAKE_MAC_DRIVER_PATH
19
19
  DATABRICKS_DRIVER_PATH = ENV["DATABRICKS_DRIVER_PATH"] || DATABRICKS_MAC_DRIVER_PATH
20
20
 
21
+ JSON_SCHEMA_URL = "https://json-schema.org/draft-07/schema#"
22
+
21
23
  # CONNECTORS
22
24
  KLAVIYO_AUTH_ENDPOINT = "https://a.klaviyo.com/api/lists/"
23
25
  KLAVIYO_AUTH_PAYLOAD = {
@@ -31,14 +33,16 @@ module Multiwoven
31
33
 
32
34
  FACEBOOK_AUDIENCE_GET_ALL_ACCOUNTS = "https://graph.facebook.com/v18.0/me/adaccounts?fields=id,name"
33
35
 
36
+ AIRTABLE_URL_BASE = "https://api.airtable.com/v0/"
37
+ AIRTABLE_BASES_ENDPOINT = "https://api.airtable.com/v0/meta/bases"
38
+ AIRTABLE_GET_BASE_SCHEMA_ENDPOINT = "https://api.airtable.com/v0/meta/bases/{baseId}/tables"
39
+
34
40
  # HTTP
35
41
  HTTP_GET = "GET"
36
42
  HTTP_POST = "POST"
37
43
  HTTP_PUT = "PUT"
38
44
  HTTP_DELETE = "DELETE"
39
45
 
40
- JSON_SCHEMA_URL = "http://json-schema.org/draft-07/schema#"
41
-
42
46
  # google sheets
43
47
  GOOGLE_SHEETS_SCOPE = "https://www.googleapis.com/auth/drive"
44
48
  GOOGLE_SPREADSHEET_ID_REGEX = %r{/d/([-\w]{20,})/}.freeze
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "schema_helper"
4
+ module Multiwoven
5
+ module Integrations
6
+ module Destination
7
+ module Airtable
8
+ include Multiwoven::Integrations::Core
9
+ class Client < DestinationConnector # rubocop:disable Metrics/ClassLength
10
+ MAX_CHUNK_SIZE = 10
11
+ def check_connection(connection_config)
12
+ connection_config = connection_config.with_indifferent_access
13
+ bases = Multiwoven::Integrations::Core::HttpClient.request(
14
+ AIRTABLE_BASES_ENDPOINT,
15
+ HTTP_GET,
16
+ headers: auth_headers(connection_config[:api_key])
17
+ )
18
+ if success?(bases)
19
+ base_id_exists?(bases, connection_config[:base_id])
20
+ success_status
21
+ else
22
+ failure_status(nil)
23
+ end
24
+ rescue StandardError => e
25
+ failure_status(e)
26
+ end
27
+
28
+ def discover(connection_config)
29
+ connection_config = connection_config.with_indifferent_access
30
+ base_id = connection_config[:base_id]
31
+ api_key = connection_config[:api_key]
32
+
33
+ bases = Multiwoven::Integrations::Core::HttpClient.request(
34
+ AIRTABLE_BASES_ENDPOINT,
35
+ HTTP_GET,
36
+ headers: auth_headers(api_key)
37
+ )
38
+
39
+ base = extract_bases(bases).find { |b| b["id"] == base_id }
40
+ base_name = base["name"]
41
+
42
+ schema = Multiwoven::Integrations::Core::HttpClient.request(
43
+ AIRTABLE_GET_BASE_SCHEMA_ENDPOINT.gsub("{baseId}", base_id),
44
+ HTTP_GET,
45
+ headers: auth_headers(api_key)
46
+ )
47
+
48
+ catalog = build_catalog_from_schema(extract_body(schema), base_id, base_name)
49
+ catalog.to_multiwoven_message
50
+ rescue StandardError => e
51
+ handle_exception("AIRTABLE:DISCOVER:EXCEPTION", "error", e)
52
+ end
53
+
54
+ def write(sync_config, records, _action = "create")
55
+ connection_config = sync_config.destination.connection_specification.with_indifferent_access
56
+ api_key = connection_config[:api_key]
57
+ url = sync_config.stream.url
58
+ write_success = 0
59
+ write_failure = 0
60
+ records.each_slice(MAX_CHUNK_SIZE) do |chunk|
61
+ payload = create_payload(chunk)
62
+ response = Multiwoven::Integrations::Core::HttpClient.request(
63
+ url,
64
+ sync_config.stream.request_method,
65
+ payload: payload,
66
+ headers: auth_headers(api_key)
67
+ )
68
+ if success?(response)
69
+ write_success += chunk.size
70
+ else
71
+ write_failure += chunk.size
72
+ end
73
+ rescue StandardError => e
74
+ handle_exception("AIRTABLE:RECORD:WRITE:EXCEPTION", "error", e)
75
+ write_failure += chunk.size
76
+ end
77
+
78
+ tracker = Multiwoven::Integrations::Protocol::TrackingMessage.new(
79
+ success: write_success,
80
+ failed: write_failure
81
+ )
82
+ tracker.to_multiwoven_message
83
+ rescue StandardError => e
84
+ handle_exception("AIRTABLE:WRITE:EXCEPTION", "error", e)
85
+ end
86
+
87
+ private
88
+
89
+ def create_payload(records)
90
+ {
91
+ "records" => records.map do |record|
92
+ {
93
+ "fields" => record
94
+ }
95
+ end
96
+ }
97
+ end
98
+
99
+ def auth_headers(access_token)
100
+ {
101
+ "Accept" => "application/json",
102
+ "Authorization" => "Bearer #{access_token}",
103
+ "Content-Type" => "application/json"
104
+ }
105
+ end
106
+
107
+ def base_id_exists?(bases, base_id)
108
+ return if extract_data(bases).any? { |base| base["id"] == base_id }
109
+
110
+ raise ArgumentError, "base_id not found"
111
+ end
112
+
113
+ def extract_bases(response)
114
+ response_body = extract_body(response)
115
+ response_body["bases"] if response_body
116
+ end
117
+
118
+ def extract_body(response)
119
+ response_body = response.body
120
+ JSON.parse(response_body) if response_body
121
+ end
122
+
123
+ def load_catalog
124
+ read_json(CATALOG_SPEC_PATH)
125
+ end
126
+
127
+ def create_stream(table, base_id, base_name)
128
+ {
129
+ name: "#{base_name}/#{SchemaHelper.clean_name(table["name"])}",
130
+ action: "create",
131
+ method: HTTP_POST,
132
+ url: "#{AIRTABLE_URL_BASE}#{base_id}/#{table["id"]}",
133
+ json_schema: SchemaHelper.get_json_schema(table),
134
+ supported_sync_modes: %w[incremental],
135
+ batch_support: true,
136
+ batch_size: 10
137
+
138
+ }.with_indifferent_access
139
+ end
140
+
141
+ def build_catalog_from_schema(schema, base_id, base_name)
142
+ catalog = build_catalog(load_catalog)
143
+ schema["tables"].each do |table|
144
+ catalog.streams << build_stream(create_stream(table, base_id, base_name))
145
+ end
146
+ catalog
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,6 @@
1
+ {
2
+ "request_rate_limit": 300,
3
+ "request_rate_limit_unit": "minute",
4
+ "request_rate_concurrency": 10,
5
+ "streams": []
6
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "data": {
3
+ "name": "Airtable",
4
+ "title": "airtable",
5
+ "connector_type": "destination",
6
+ "category": "Productivity Tools",
7
+ "documentation_url": "https://docs.multiwoven.com/destinations/productivity-tools/airtable",
8
+ "github_issue_label": "destination-airtable",
9
+ "icon": "icon.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/destination/airtable",
3
+ "stream_type": "dynamic",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "Airtable",
7
+ "type": "object",
8
+ "required": ["api_key", "base_id"],
9
+ "properties": {
10
+ "api_key": {
11
+ "type": "string",
12
+ "title": "Personal access token",
13
+ "order": 0
14
+ },
15
+ "base_id": {
16
+ "type": "string",
17
+ "title": "Airtable base id",
18
+ "order": 1
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="27" shape-rendering="geometricPrecision" viewBox="0 0 200 170">
2
+ <path fill="#FCB400" d="M90.039 12.367L24.079 39.66c-3.667 1.519-3.63 6.729.062 8.192l66.235 26.266a24.575 24.575 0 0018.12 0l66.236-26.266c3.69-1.463 3.729-6.673.06-8.191l-65.958-27.294a24.578 24.578 0 00-18.795 0"></path>
3
+ <path fill="#18BFFF" d="M105.312 88.46v65.617c0 3.12 3.147 5.258 6.048 4.108l73.806-28.648a4.418 4.418 0 002.79-4.108V59.813c0-3.121-3.147-5.258-6.048-4.108l-73.806 28.648a4.42 4.42 0 00-2.79 4.108"></path>
4
+ <path fill="#F82B60" d="M88.078 91.846l-21.904 10.576-2.224 1.075-46.238 22.155c-2.93 1.414-6.672-.722-6.672-3.978V60.088c0-1.178.604-2.195 1.414-2.96a5.024 5.024 0 011.12-.84c1.104-.663 2.68-.84 4.02-.31L87.71 83.76c3.564 1.414 3.844 6.408.368 8.087"></path>
5
+ <path fill="rgba(0, 0, 0, 0.25)" d="M88.078 91.846l-21.904 10.576-53.72-45.295a5.024 5.024 0 011.12-.839c1.104-.663 2.68-.84 4.02-.31L87.71 83.76c3.564 1.414 3.844 6.408.368 8.087"></path>
6
+ </svg>
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven
4
+ module Integrations
5
+ module Destination
6
+ module Airtable
7
+ module SchemaHelper
8
+ include Core::Constants
9
+
10
+ module_function
11
+
12
+ def clean_name(name_str)
13
+ name_str.strip.gsub(" ", "_")
14
+ end
15
+
16
+ def get_json_schema(table)
17
+ fields = table["fields"] || {}
18
+ properties = fields.each_with_object({}) do |field, props|
19
+ name, schema = process_field(field)
20
+ props[name] = schema
21
+ end
22
+
23
+ build_schema(properties)
24
+ end
25
+
26
+ def process_field(field)
27
+ name = clean_name(field.fetch("name", ""))
28
+ original_type = field.fetch("type", "")
29
+ options = field.fetch("options", {})
30
+
31
+ schema = determine_schema(original_type, options)
32
+ [name, schema]
33
+ end
34
+
35
+ def determine_schema(original_type, options)
36
+ if COMPLEX_AIRTABLE_TYPES.keys.include?(original_type)
37
+ complex_type = deep_copy(COMPLEX_AIRTABLE_TYPES[original_type])
38
+ adjust_complex_type(original_type, complex_type, options)
39
+ elsif SIMPLE_AIRTABLE_TYPES.keys.include?(original_type)
40
+ simple_type_schema(original_type, options)
41
+ else
42
+ SCHEMA_TYPES[:STRING]
43
+ end
44
+ end
45
+
46
+ def adjust_complex_type(original_type, complex_type, options)
47
+ exec_type = options.dig("result", "type") || "simpleText"
48
+ if complex_type == SCHEMA_TYPES[:ARRAY_WITH_ANY]
49
+ adjust_array_with_any(original_type, complex_type, exec_type, options)
50
+ else
51
+ complex_type
52
+ end
53
+ end
54
+
55
+ def adjust_array_with_any(original_type, complex_type, exec_type, options)
56
+ if original_type == "formula" && %w[number currency percent duration].include?(exec_type)
57
+ complex_type = SCHEMA_TYPES[:NUMBER]
58
+ elsif original_type == "formula" && ARRAY_FORMULAS.none? { |x| options.fetch("formula", "").start_with?(x) }
59
+ complex_type = SCHEMA_TYPES[:STRING]
60
+ elsif SIMPLE_AIRTABLE_TYPES.keys.include?(exec_type)
61
+ complex_type["items"] = deep_copy(SIMPLE_AIRTABLE_TYPES[exec_type])
62
+ else
63
+ complex_type["items"] = SCHEMA_TYPES[:STRING]
64
+ end
65
+ complex_type
66
+ end
67
+
68
+ def simple_type_schema(original_type, options)
69
+ exec_type = options.dig("result", "type") || original_type
70
+ deep_copy(SIMPLE_AIRTABLE_TYPES[exec_type])
71
+ end
72
+
73
+ def build_schema(properties)
74
+ {
75
+ "$schema" => JSON_SCHEMA_URL,
76
+ "type" => "object",
77
+ "additionalProperties" => true,
78
+ "properties" => properties
79
+ }
80
+ end
81
+
82
+ def deep_copy(object)
83
+ Marshal.load(Marshal.dump(object))
84
+ end
85
+
86
+ SCHEMA_TYPES = {
87
+ STRING: { "type": %w[null string] },
88
+ NUMBER: { "type": %w[null number] },
89
+ BOOLEAN: { "type": %w[null boolean] },
90
+ DATE: { "type": %w[null string], "format": "date" },
91
+ DATETIME: { "type": %w[null string], "format": "date-time" },
92
+ ARRAY_WITH_STRINGS: { "type": %w[null array], "items": { "type": %w[null string] } },
93
+ ARRAY_WITH_ANY: { "type": %w[null array], "items": {} }
94
+ }.freeze.with_indifferent_access
95
+
96
+ SIMPLE_AIRTABLE_TYPES = {
97
+ "multipleAttachments" => SCHEMA_TYPES[:STRING],
98
+ "autoNumber" => SCHEMA_TYPES[:NUMBER],
99
+ "barcode" => SCHEMA_TYPES[:STRING],
100
+ "button" => SCHEMA_TYPES[:STRING],
101
+ "checkbox" => :BOOLEAN,
102
+ "singleCollaborator" => SCHEMA_TYPES[:STRING],
103
+ "count" => SCHEMA_TYPES[:NUMBER],
104
+ "createdBy" => SCHEMA_TYPES[:STRING],
105
+ "createdTime" => SCHEMA_TYPES[:DATETIME],
106
+ "currency" => SCHEMA_TYPES[:NUMBER],
107
+ "email" => SCHEMA_TYPES[:STRING],
108
+ "date" => SCHEMA_TYPES[:DATE],
109
+ "dateTime" => SCHEMA_TYPES[:DATETIME],
110
+ "duration" => SCHEMA_TYPES[:NUMBER],
111
+ "lastModifiedBy" => SCHEMA_TYPES[:STRING],
112
+ "lastModifiedTime" => SCHEMA_TYPES[:DATETIME],
113
+ "multipleRecordLinks" => SCHEMA_TYPES[:ARRAY_WITH_STRINGS],
114
+ "multilineText" => SCHEMA_TYPES[:STRING],
115
+ "multipleCollaborators" => SCHEMA_TYPES[:ARRAY_WITH_STRINGS],
116
+ "multipleSelects" => SCHEMA_TYPES[:ARRAY_WITH_STRINGS],
117
+ "number" => SCHEMA_TYPES[:NUMBER],
118
+ "percent" => SCHEMA_TYPES[:NUMBER],
119
+ "phoneNumber" => SCHEMA_TYPES[:STRING],
120
+ "rating" => SCHEMA_TYPES[:NUMBER],
121
+ "richText" => SCHEMA_TYPES[:STRING],
122
+ "singleLineText" => SCHEMA_TYPES[:STRING],
123
+ "singleSelect" => SCHEMA_TYPES[:STRING],
124
+ "externalSyncSource" => SCHEMA_TYPES[:STRING],
125
+ "url" => SCHEMA_TYPES[:STRING],
126
+ "simpleText" => SCHEMA_TYPES[:STRING]
127
+ }.freeze
128
+
129
+ COMPLEX_AIRTABLE_TYPES = {
130
+ "formula" => SCHEMA_TYPES[:ARRAY_WITH_ANY],
131
+ "lookup" => SCHEMA_TYPES[:ARRAY_WITH_ANY],
132
+ "multipleLookupValues" => SCHEMA_TYPES[:ARRAY_WITH_ANY],
133
+ "rollup" => SCHEMA_TYPES[:ARRAY_WITH_ANY]
134
+ }.freeze.with_indifferent_access
135
+
136
+ ARRAY_FORMULAS = %w[ARRAYCOMPACT ARRAYFLATTEN ARRAYUNIQUE ARRAYSLICE].freeze
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -1,124 +1,103 @@
1
1
  {
2
- "request_rate_limit": 600,
3
- "request_rate_limit_unit": "minute",
4
- "request_rate_concurrency": 10,
5
- "streams": [
6
- {
7
- "name": "profile",
8
- "action": "create",
9
- "url": "https://a.klaviyo.com/api/profiles",
10
- "method": "POST",
11
- "json_schema": {
12
- "$schema": "http://json-schema.org/draft-07/schema#",
13
- "type": "object",
14
- "properties": {
15
- "data": {
16
- "type": "object",
17
- "properties": {
18
- "type": {
19
- "type": "string",
20
- "enum": [
21
- "profile"
22
- ]
23
- },
24
- "attributes": {
25
- "type": "object",
26
- "properties": {
27
- "email": {
28
- "type": "string",
29
- "format": "email"
30
- },
31
- "phone_number": {
32
- "type": "string"
33
- },
34
- "external_id": {
35
- "type": "string",
36
- "format": "uuid"
37
- },
38
- "first_name": {
39
- "type": "string"
40
- },
41
- "last_name": {
42
- "type": "string"
43
- },
44
- "organization": {
45
- "type": "string"
46
- },
47
- "title": {
48
- "type": "string"
49
- },
50
- "image": {
51
- "type": "string",
52
- "format": "uri"
53
- },
54
- "location": {
55
- "type": "object",
56
- "properties": {
57
- "address1": {
58
- "type": "string"
59
- },
60
- "address2": {
61
- "type": "string"
62
- },
63
- "city": {
64
- "type": "string"
65
- },
66
- "country": {
67
- "type": "string"
68
- },
69
- "region": {
70
- "type": "string"
71
- },
72
- "zip": {
73
- "type": "string"
74
- },
75
- "timezone": {
76
- "type": "string"
77
- },
78
- "ip": {
79
- "type": "string",
80
- "format": "ipv4"
81
- }
82
- }
83
-
84
- },
2
+ "request_rate_limit": 600,
3
+ "request_rate_limit_unit": "minute",
4
+ "request_rate_concurrency": 10,
5
+ "streams": [
6
+ {
7
+ "name": "profile",
8
+ "action": "create",
9
+ "url": "https://a.klaviyo.com/api/profiles",
10
+ "method": "POST",
11
+ "json_schema": {
12
+ "$schema": "http://json-schema.org/draft-07/schema#",
13
+ "type": "object",
14
+ "properties": {
15
+ "data": {
16
+ "type": "object",
17
+ "properties": {
18
+ "type": {
19
+ "type": "string",
20
+ "enum": ["profile"]
21
+ },
22
+ "attributes": {
23
+ "type": "object",
24
+ "properties": {
25
+ "email": {
26
+ "type": "string",
27
+ "format": "email"
28
+ },
29
+ "phone_number": {
30
+ "type": "string"
31
+ },
32
+ "external_id": {
33
+ "type": "string",
34
+ "format": "uuid"
35
+ },
36
+ "first_name": {
37
+ "type": "string"
38
+ },
39
+ "last_name": {
40
+ "type": "string"
41
+ },
42
+ "organization": {
43
+ "type": "string"
44
+ },
45
+ "title": {
46
+ "type": "string"
47
+ },
48
+ "image": {
49
+ "type": "string",
50
+ "format": "uri"
51
+ },
52
+ "location": {
53
+ "type": "object",
85
54
  "properties": {
86
- "type": "object",
87
- "additionalProperties": {
55
+ "address1": {
56
+ "type": "string"
57
+ },
58
+ "address2": {
59
+ "type": "string"
60
+ },
61
+ "city": {
62
+ "type": "string"
63
+ },
64
+ "country": {
65
+ "type": "string"
66
+ },
67
+ "region": {
88
68
  "type": "string"
69
+ },
70
+ "zip": {
71
+ "type": "string"
72
+ },
73
+ "timezone": {
74
+ "type": "string"
75
+ },
76
+ "ip": {
77
+ "type": "string",
78
+ "format": "ipv4"
89
79
  }
90
80
  }
91
81
  },
92
- "required": [
93
- "email",
94
- "phone_number"
95
- ]
96
- }
97
- },
98
- "required": [
99
- "type",
100
- "attributes"
101
- ]
102
- }
103
- },
104
- "required": [
105
- "data"
106
- ]
82
+ "properties": {
83
+ "type": "object",
84
+ "additionalProperties": {
85
+ "type": "string"
86
+ }
87
+ }
88
+ },
89
+ "required": ["email", "phone_number"]
90
+ }
91
+ },
92
+ "required": ["type", "attributes"]
93
+ }
107
94
  },
108
- "supported_sync_modes": [
109
- "incremental"
110
- ],
111
- "source_defined_cursor": true,
112
- "default_cursor_field": [
113
- "updated"
114
- ],
115
- "source_defined_primary_key": [
116
- [
117
- "id",
118
- "email"
119
- ]
120
- ]
121
-
122
- }
123
- ]
124
- }
95
+ "required": ["data"]
96
+ },
97
+ "supported_sync_modes": ["incremental"],
98
+ "source_defined_cursor": true,
99
+ "default_cursor_field": ["updated"],
100
+ "source_defined_primary_key": [["id", "email"]]
101
+ }
102
+ ]
103
+ }
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven
4
+ module Integrations
5
+ module Destination
6
+ module Stripe
7
+ include Multiwoven::Integrations::Core
8
+
9
+ API_VERSION = "59.0"
10
+
11
+ class Client < DestinationConnector
12
+ def check_connection(connection_config)
13
+ connection_config = connection_config.with_indifferent_access
14
+ initialize_client(connection_config)
15
+ authenticate_client
16
+ success_status
17
+ rescue StandardError => e
18
+ failure_status(e)
19
+ end
20
+
21
+ def discover(_connection_config = nil)
22
+ catalog = build_catalog(load_catalog)
23
+ catalog.to_multiwoven_message
24
+ rescue StandardError => e
25
+ handle_exception("STRIPE:CRM:DISCOVER:EXCEPTION", "error", e)
26
+ end
27
+
28
+ def write(sync_config, records, action = "create")
29
+ @action = sync_config.stream.action || action
30
+ initialize_client(sync_config.destination.connection_specification)
31
+ process_records(records, sync_config.stream)
32
+ rescue StandardError => e
33
+ handle_exception("STRIPE:CRM:WRITE:EXCEPTION", "error", e)
34
+ end
35
+
36
+ private
37
+
38
+ def initialize_client(config)
39
+ config = config.with_indifferent_access
40
+ ::Stripe.api_key = config[:api_key]
41
+ @client = ::Stripe
42
+ end
43
+
44
+ def process_records(records, stream)
45
+ write_success = 0
46
+ write_failure = 0
47
+ properties = stream.json_schema[:properties]
48
+ records.each do |record_object|
49
+ record = extract_data(record_object, properties)
50
+ klass = @client.const_get(stream.name)
51
+ klass.send(@action, record)
52
+ write_success += 1
53
+ rescue StandardError => e
54
+ handle_exception("STRIPE:CRM:WRITE:EXCEPTION", "error", e)
55
+ write_failure += 1
56
+ end
57
+ tracking_message(write_success, write_failure)
58
+ end
59
+
60
+ def authenticate_client
61
+ @client::Customer.list
62
+ end
63
+
64
+ def load_catalog
65
+ read_json(CATALOG_SPEC_PATH)
66
+ end
67
+
68
+ def tracking_message(success, failure)
69
+ Multiwoven::Integrations::Protocol::TrackingMessage.new(
70
+ success: success, failed: failure
71
+ ).to_multiwoven_message
72
+ end
73
+
74
+ def log_debug(message)
75
+ Multiwoven::Integrations::Service.logger.debug(message)
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,128 @@
1
+ {
2
+ "request_rate_limit": 100000,
3
+ "request_rate_limit_unit": "day",
4
+ "request_rate_concurrency": 10,
5
+ "streams": [
6
+ {
7
+ "name": "Customer",
8
+ "action": "create",
9
+ "json_schema": {
10
+ "type": "object",
11
+ "additionalProperties": true,
12
+ "properties": {
13
+ "email": {
14
+ "type": "string"
15
+ },
16
+ "description": {
17
+ "type": "string"
18
+ },
19
+ "name": {
20
+ "type": "string"
21
+ },
22
+ "payment_method": {
23
+ "type": "string"
24
+ },
25
+ "metadata": {
26
+ "type": "object"
27
+ },
28
+ "phone": {
29
+ "type": "string"
30
+ },
31
+ "address": {
32
+ "type": "object",
33
+ "additionalProperties": true,
34
+ "properties": {
35
+ "city": {
36
+ "type": "string"
37
+ },
38
+ "country": {
39
+ "type": "string"
40
+ },
41
+ "line1": {
42
+ "type": "string"
43
+ },
44
+ "line2": {
45
+ "type": "string"
46
+ },
47
+ "postal_code": {
48
+ "type": "string"
49
+ },
50
+ "state": {
51
+ "type": "string"
52
+ }
53
+ }
54
+ },
55
+ "shipping": {
56
+ "type": "object",
57
+ "additionalProperties": true,
58
+ "properties": {
59
+ "address": {
60
+ "type": "object",
61
+ "additionalProperties": true,
62
+ "properties": {
63
+ "city": {
64
+ "type": "string"
65
+ },
66
+ "country": {
67
+ "type": "string"
68
+ },
69
+ "line1": {
70
+ "type": "string"
71
+ },
72
+ "line2": {
73
+ "type": "string"
74
+ },
75
+ "postal_code": {
76
+ "type": "string"
77
+ },
78
+ "state": {
79
+ "type": "string"
80
+ }
81
+ }
82
+ },
83
+ "name": {
84
+ "type": "string"
85
+ },
86
+ "phone": {
87
+ "type": "string"
88
+ }
89
+ }
90
+ }
91
+ }
92
+ },
93
+ "supported_sync_modes": ["incremental"],
94
+ "source_defined_cursor": true,
95
+ "default_cursor_field": ["updated"],
96
+ "source_defined_primary_key": [["Id"]]
97
+ },
98
+ {
99
+ "name": "Product",
100
+ "action": "create",
101
+ "json_schema": {
102
+ "type": "object",
103
+ "additionalProperties": true,
104
+ "required": ["name"],
105
+ "properties": {
106
+ "name": {
107
+ "type": "string"
108
+ },
109
+ "description": {
110
+ "type": "string"
111
+ },
112
+ "active": {
113
+ "type": "boolean"
114
+ },
115
+ "id": {
116
+ "type": "string"
117
+ },
118
+ "metadata": {
119
+ "type": "object"
120
+ }
121
+ }
122
+ },
123
+ "supported_sync_modes": ["incremental"],
124
+ "source_defined_cursor": true,
125
+ "default_cursor_field": ["updated"]
126
+ }
127
+ ]
128
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "data": {
3
+ "name": "Stripe",
4
+ "title": "Stripe",
5
+ "connector_type": "destination",
6
+ "category": "Payments",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "destination-stripe",
9
+ "icon": "icon.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/destination/payments/stripe",
3
+ "stream_type": "static",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "Stripe",
7
+ "type": "object",
8
+ "required": ["api_key"],
9
+ "properties": {
10
+ "api_key": {
11
+ "type": "string",
12
+ "title": "API Key",
13
+ "order": 0
14
+ }
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 28.87 28.87" id="stripe">
2
+ <g data-name="Layer 2">
3
+ <g data-name="Layer 1">
4
+ <rect width="28.87" height="28.87" fill="#6772e5" rx="6.48" ry="6.48"></rect>
5
+ <path fill="#fff" fill-rule="evenodd"
6
+ d="M13.3 11.2c0-.69.57-1 1.49-1a9.84 9.84 0 0 1 4.37 1.13V7.24a11.6 11.6 0 0 0-4.36-.8c-3.56 0-5.94 1.86-5.94 5 0 4.86 6.68 4.07 6.68 6.17 0 .81-.71 1.07-1.68 1.07A11.06 11.06 0 0 1 9 17.25v4.19a12.19 12.19 0 0 0 4.8 1c3.65 0 6.17-1.8 6.17-5 .03-5.21-6.67-4.27-6.67-6.24z">
7
+ </path>
8
+ </g>
9
+ </g>
10
+ </svg>
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Multiwoven
4
4
  module Integrations
5
- VERSION = "0.1.34"
5
+ VERSION = "0.1.36"
6
6
 
7
7
  ENABLED_SOURCES = %w[
8
8
  Snowflake
@@ -19,6 +19,8 @@ module Multiwoven
19
19
  Slack
20
20
  Hubspot
21
21
  GoogleSheets
22
+ Airtable
23
+ Stripe
22
24
  ].freeze
23
25
  end
24
26
  end
@@ -18,6 +18,7 @@ require "ruby-limiter"
18
18
  require "hubspot-api-client"
19
19
  require "google/apis/sheets_v4"
20
20
  require "stringio"
21
+ require "stripe"
21
22
 
22
23
  # Service
23
24
  require_relative "integrations/config"
@@ -48,6 +49,8 @@ require_relative "integrations/destination/facebook_custom_audience/client"
48
49
  require_relative "integrations/destination/slack/client"
49
50
  require_relative "integrations/destination/hubspot/client"
50
51
  require_relative "integrations/destination/google_sheets/client"
52
+ require_relative "integrations/destination/airtable/client"
53
+ require_relative "integrations/destination/stripe/client"
51
54
 
52
55
  module Multiwoven
53
56
  module Integrations
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: multiwoven-integrations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.34
4
+ version: 0.1.36
5
5
  platform: ruby
6
6
  authors:
7
7
  - Subin T P
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-03-12 00:00:00.000000000 Z
11
+ date: 2024-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -330,6 +330,12 @@ files:
330
330
  - lib/multiwoven/integrations/core/rate_limiter.rb
331
331
  - lib/multiwoven/integrations/core/source_connector.rb
332
332
  - lib/multiwoven/integrations/core/utils.rb
333
+ - lib/multiwoven/integrations/destination/airtable/client.rb
334
+ - lib/multiwoven/integrations/destination/airtable/config/catalog.json
335
+ - lib/multiwoven/integrations/destination/airtable/config/meta.json
336
+ - lib/multiwoven/integrations/destination/airtable/config/spec.json
337
+ - lib/multiwoven/integrations/destination/airtable/icon.svg
338
+ - lib/multiwoven/integrations/destination/airtable/schema_helper.rb
333
339
  - lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb
334
340
  - lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json
335
341
  - lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json
@@ -360,6 +366,11 @@ files:
360
366
  - lib/multiwoven/integrations/destination/slack/config/meta.json
361
367
  - lib/multiwoven/integrations/destination/slack/config/spec.json
362
368
  - lib/multiwoven/integrations/destination/slack/icon.svg
369
+ - lib/multiwoven/integrations/destination/stripe/client.rb
370
+ - lib/multiwoven/integrations/destination/stripe/config/catalog.json
371
+ - lib/multiwoven/integrations/destination/stripe/config/meta.json
372
+ - lib/multiwoven/integrations/destination/stripe/config/spec.json
373
+ - lib/multiwoven/integrations/destination/stripe/icon.svg
363
374
  - lib/multiwoven/integrations/protocol/protocol.json
364
375
  - lib/multiwoven/integrations/protocol/protocol.rb
365
376
  - lib/multiwoven/integrations/rollout.rb