etl-integrations 0.1.81

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +34 -0
  4. data/.ruby-version +1 -0
  5. data/.vscode/settings.json +5 -0
  6. data/CHANGELOG.md +38 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +105 -0
  10. data/Rakefile +12 -0
  11. data/lib/multiwoven/integrations/config.rb +13 -0
  12. data/lib/multiwoven/integrations/core/base_connector.rb +70 -0
  13. data/lib/multiwoven/integrations/core/constants.rb +46 -0
  14. data/lib/multiwoven/integrations/core/destination_connector.rb +14 -0
  15. data/lib/multiwoven/integrations/core/fullrefresher.rb +19 -0
  16. data/lib/multiwoven/integrations/core/http_client.rb +34 -0
  17. data/lib/multiwoven/integrations/core/query_builder.rb +27 -0
  18. data/lib/multiwoven/integrations/core/rate_limiter.rb +19 -0
  19. data/lib/multiwoven/integrations/core/source_connector.rb +38 -0
  20. data/lib/multiwoven/integrations/core/utils.rb +104 -0
  21. data/lib/multiwoven/integrations/destination/airtable/client.rb +153 -0
  22. data/lib/multiwoven/integrations/destination/airtable/config/catalog.json +6 -0
  23. data/lib/multiwoven/integrations/destination/airtable/config/meta.json +15 -0
  24. data/lib/multiwoven/integrations/destination/airtable/config/spec.json +22 -0
  25. data/lib/multiwoven/integrations/destination/airtable/icon.svg +6 -0
  26. data/lib/multiwoven/integrations/destination/airtable/schema_helper.rb +141 -0
  27. data/lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb +124 -0
  28. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json +42 -0
  29. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json +15 -0
  30. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/spec.json +27 -0
  31. data/lib/multiwoven/integrations/destination/facebook_custom_audience/icon.svg +23 -0
  32. data/lib/multiwoven/integrations/destination/google_sheets/client.rb +231 -0
  33. data/lib/multiwoven/integrations/destination/google_sheets/config/catalog.json +6 -0
  34. data/lib/multiwoven/integrations/destination/google_sheets/config/meta.json +15 -0
  35. data/lib/multiwoven/integrations/destination/google_sheets/config/spec.json +74 -0
  36. data/lib/multiwoven/integrations/destination/google_sheets/icon.svg +1 -0
  37. data/lib/multiwoven/integrations/destination/hubspot/client.rb +107 -0
  38. data/lib/multiwoven/integrations/destination/hubspot/config/catalog.json +351 -0
  39. data/lib/multiwoven/integrations/destination/hubspot/config/meta.json +15 -0
  40. data/lib/multiwoven/integrations/destination/hubspot/config/spec.json +17 -0
  41. data/lib/multiwoven/integrations/destination/hubspot/icon.svg +5 -0
  42. data/lib/multiwoven/integrations/destination/klaviyo/client.rb +116 -0
  43. data/lib/multiwoven/integrations/destination/klaviyo/config/catalog.json +103 -0
  44. data/lib/multiwoven/integrations/destination/klaviyo/config/meta.json +15 -0
  45. data/lib/multiwoven/integrations/destination/klaviyo/config/spec.json +22 -0
  46. data/lib/multiwoven/integrations/destination/klaviyo/icon.svg +6 -0
  47. data/lib/multiwoven/integrations/destination/postgresql/client.rb +123 -0
  48. data/lib/multiwoven/integrations/destination/postgresql/config/meta.json +15 -0
  49. data/lib/multiwoven/integrations/destination/postgresql/config/spec.json +68 -0
  50. data/lib/multiwoven/integrations/destination/postgresql/icon.svg +20 -0
  51. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/client.rb +114 -0
  52. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/catalog.json +6 -0
  53. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/meta.json +16 -0
  54. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/spec.json +49 -0
  55. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/icon.svg +16 -0
  56. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/schema_helper.rb +132 -0
  57. data/lib/multiwoven/integrations/destination/salesforce_crm/client.rb +117 -0
  58. data/lib/multiwoven/integrations/destination/salesforce_crm/config/catalog.json +320 -0
  59. data/lib/multiwoven/integrations/destination/salesforce_crm/config/meta.json +15 -0
  60. data/lib/multiwoven/integrations/destination/salesforce_crm/config/spec.json +43 -0
  61. data/lib/multiwoven/integrations/destination/salesforce_crm/icon.svg +16 -0
  62. data/lib/multiwoven/integrations/destination/sftp/client.rb +133 -0
  63. data/lib/multiwoven/integrations/destination/sftp/config/catalog.json +16 -0
  64. data/lib/multiwoven/integrations/destination/sftp/config/meta.json +16 -0
  65. data/lib/multiwoven/integrations/destination/sftp/config/spec.json +50 -0
  66. data/lib/multiwoven/integrations/destination/sftp/icon.svg +1 -0
  67. data/lib/multiwoven/integrations/destination/slack/client.rb +114 -0
  68. data/lib/multiwoven/integrations/destination/slack/config/catalog.json +22 -0
  69. data/lib/multiwoven/integrations/destination/slack/config/meta.json +15 -0
  70. data/lib/multiwoven/integrations/destination/slack/config/spec.json +22 -0
  71. data/lib/multiwoven/integrations/destination/slack/icon.svg +26 -0
  72. data/lib/multiwoven/integrations/destination/stripe/client.rb +82 -0
  73. data/lib/multiwoven/integrations/destination/stripe/config/catalog.json +128 -0
  74. data/lib/multiwoven/integrations/destination/stripe/config/meta.json +15 -0
  75. data/lib/multiwoven/integrations/destination/stripe/config/spec.json +17 -0
  76. data/lib/multiwoven/integrations/destination/stripe/icon.svg +10 -0
  77. data/lib/multiwoven/integrations/destination/tally/client.rb +151 -0
  78. data/lib/multiwoven/integrations/destination/tally/config/catalog.json +62 -0
  79. data/lib/multiwoven/integrations/destination/tally/config/meta.json +15 -0
  80. data/lib/multiwoven/integrations/destination/tally/config/spec.json +45 -0
  81. data/lib/multiwoven/integrations/destination/tally/icon.svg +7 -0
  82. data/lib/multiwoven/integrations/protocol/protocol.json +189 -0
  83. data/lib/multiwoven/integrations/protocol/protocol.rb +216 -0
  84. data/lib/multiwoven/integrations/rollout.rb +32 -0
  85. data/lib/multiwoven/integrations/service.rb +79 -0
  86. data/lib/multiwoven/integrations/source/bigquery/client.rb +98 -0
  87. data/lib/multiwoven/integrations/source/bigquery/config/meta.json +15 -0
  88. data/lib/multiwoven/integrations/source/bigquery/config/spec.json +82 -0
  89. data/lib/multiwoven/integrations/source/bigquery/icon.svg +1 -0
  90. data/lib/multiwoven/integrations/source/databricks/client.rb +98 -0
  91. data/lib/multiwoven/integrations/source/databricks/config/meta.json +16 -0
  92. data/lib/multiwoven/integrations/source/databricks/config/spec.json +56 -0
  93. data/lib/multiwoven/integrations/source/databricks/icon.svg +19 -0
  94. data/lib/multiwoven/integrations/source/postgresql/client.rb +109 -0
  95. data/lib/multiwoven/integrations/source/postgresql/config/meta.json +15 -0
  96. data/lib/multiwoven/integrations/source/postgresql/config/spec.json +69 -0
  97. data/lib/multiwoven/integrations/source/postgresql/icon.svg +20 -0
  98. data/lib/multiwoven/integrations/source/redshift/client.rb +109 -0
  99. data/lib/multiwoven/integrations/source/redshift/config/meta.json +15 -0
  100. data/lib/multiwoven/integrations/source/redshift/config/spec.json +71 -0
  101. data/lib/multiwoven/integrations/source/redshift/icon.svg +15 -0
  102. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/client.rb +123 -0
  103. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/catalog.json +6 -0
  104. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/meta.json +17 -0
  105. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/spec.json +50 -0
  106. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/icon.svg +16 -0
  107. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/schema_helper.rb +130 -0
  108. data/lib/multiwoven/integrations/source/snowflake/client.rb +92 -0
  109. data/lib/multiwoven/integrations/source/snowflake/config/meta.json +15 -0
  110. data/lib/multiwoven/integrations/source/snowflake/config/spec.json +82 -0
  111. data/lib/multiwoven/integrations/source/snowflake/icon.svg +10 -0
  112. data/lib/multiwoven/integrations/source/zoho_books/client.rb +155 -0
  113. data/lib/multiwoven/integrations/source/zoho_books/config/meta.json +16 -0
  114. data/lib/multiwoven/integrations/source/zoho_books/config/spec.json +43 -0
  115. data/lib/multiwoven/integrations/source/zoho_books/icon.svg +16 -0
  116. data/lib/multiwoven/integrations.rb +71 -0
  117. data/multiwoven-integrations-0.1.68.gem +0 -0
  118. data/sig/multiwoven/integrations.rbs +6 -0
  119. metadata +515 -0
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven
4
+ module Integrations
5
+ module Source
6
+ module SalesforceConsumerGoodsCloud
7
+ module SchemaHelper
8
+ include Core::Constants
9
+
10
+ module_function
11
+
12
+ def salesforce_field_to_json_schema_type(sf_field) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
13
+ case sf_field["type"]
14
+ when "string", "Email", "Phone", "Text", "TextArea", "TextEncrypted", "URL", "Picklist (Single)"
15
+ if sf_field["nillable"]
16
+ { "type": %w[string null] }
17
+ else
18
+ { "type": "string" }
19
+ end
20
+ when "double", "Currency", "Percent"
21
+ if sf_field["nillable"]
22
+ { "type": %w[number null] }
23
+ else
24
+ { "type": "number" }
25
+ end
26
+ when "boolean", "Checkbox"
27
+ if sf_field["nillable"]
28
+ { "type": %w[boolean null] }
29
+ else
30
+ { "type": "boolean" }
31
+ end
32
+ when "int", "AutoNumber"
33
+ if sf_field["nillable"]
34
+ { "type": %w[integer null] }
35
+ else
36
+ { "type": "integer" }
37
+ end
38
+ when "date"
39
+ if sf_field["nillable"]
40
+ { "type": %w[string null], "format": "date" }
41
+ else
42
+ { "type": "string", "format": "date" }
43
+ end
44
+ when "datetime", "DateTime"
45
+ if sf_field["nillable"]
46
+ { "type": %w[string null], "format": "date-time" }
47
+ else
48
+ { "type": "string", "format": "date-time" }
49
+ end
50
+ when "time"
51
+ if sf_field["nillable"]
52
+ { "type": %w[string null], "format": "time" }
53
+ else
54
+ { "type": "string", "format": "time" }
55
+ end
56
+ when "textarea", "Text Area (Long)", "Text Area (Rich)"
57
+ if sf_field["nillable"]
58
+ { "type": %w[string null] }
59
+ else
60
+ { "type": "string" }
61
+ end
62
+ when "picklist", "multipicklist", "Picklist (Multi-select)"
63
+ if sf_field[:picklistValues] && sf_field["nillable"]
64
+ enum_values = sf_field[:picklistValues].map { |val| val["value"] }
65
+ { "type": %w[string null], "items": { "type": "string" }, "enum": enum_values }
66
+ elsif sf_field[:picklistValues]
67
+ enum_values = sf_field[:picklistValues].map { |val| val["value"] }
68
+ { "type": "string", "items": { "type": "string" }, "enum": enum_values }
69
+ else
70
+ { "type": "string", "items": { "type": "string" } }
71
+ end
72
+ when "reference", "Reference (Lookup & Master-Detail)"
73
+ if sf_field["nillable"]
74
+ { "type": %w[string null] }
75
+ else
76
+ { "type": "string" }
77
+ end
78
+ when "location", "Geolocation"
79
+ if sf_field["nillable"]
80
+ { "type": %w[object null], "properties": { "latitude": { "type": "number" }, "longitude": { "type": "number" } } }
81
+ else
82
+ { "type": "object", "properties": { "latitude": { "type": "number" }, "longitude": { "type": "number" } } }
83
+ end
84
+ else
85
+ if sf_field["nillable"]
86
+ { "type": %w[string null] }
87
+ else
88
+ { "type": "string" }
89
+ end
90
+ end
91
+ end
92
+
93
+ def create_json_schema_for_object(metadata)
94
+ fields_schema = metadata["fields"].map do |field|
95
+ {
96
+ "#{field[:name]}": salesforce_field_to_json_schema_type(field)
97
+ }
98
+ end.reduce(:merge)
99
+
100
+ json_schema = {
101
+ "$schema": "http://json-schema.org/draft-07/schema#",
102
+ "title": metadata["name"],
103
+ "type": "object",
104
+ "additionalProperties": true,
105
+ "properties": fields_schema
106
+ }
107
+
108
+ required = metadata["fields"].map do |field|
109
+ field["name"] if field["nillable"] == false
110
+ end.compact
111
+ primary_key = metadata["fields"].map do |field|
112
+ field["name"] if field["nillable"] == false && field["unique"] == true
113
+ end.compact
114
+
115
+ {
116
+ "name": metadata["name"],
117
+ "action": "create",
118
+ "json_schema": json_schema,
119
+ "required": required,
120
+ "supported_sync_modes": %w[incremental],
121
+ "source_defined_primary_key": [primary_key],
122
+ "source_defined_cursor": false,
123
+ "default_cursor_field": nil
124
+ }
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven::Integrations::Source
4
+ module Snowflake
5
+ include Multiwoven::Integrations::Core
6
+ class Client < SourceConnector
7
+ def check_connection(connection_config)
8
+ connection_config = connection_config.with_indifferent_access
9
+ create_connection(connection_config)
10
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
11
+ rescue Sequel::DatabaseConnectionError => e
12
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: e.message).to_multiwoven_message
13
+ end
14
+
15
+ def discover(connection_config)
16
+ connection_config = connection_config.with_indifferent_access
17
+ query = "SELECT table_name, column_name, data_type, is_nullable
18
+ FROM information_schema.columns
19
+ WHERE table_schema = \'#{connection_config[:schema]}\' AND table_catalog = \'#{connection_config[:database]}\'
20
+ ORDER BY table_name, ordinal_position;"
21
+
22
+ db = create_connection(connection_config)
23
+
24
+ records = []
25
+ db.fetch(query.gsub("\n", "")) do |row|
26
+ records << row
27
+ end
28
+ catalog = Catalog.new(streams: create_streams(records))
29
+ catalog.to_multiwoven_message
30
+ rescue StandardError => e
31
+ handle_exception(
32
+ "SNOWFLAKE:DISCOVER:EXCEPTION",
33
+ "error",
34
+ e
35
+ )
36
+ end
37
+
38
+ def read(sync_config)
39
+ connection_config = sync_config.source.connection_specification
40
+ connection_config = connection_config.with_indifferent_access
41
+ query = sync_config.model.query
42
+ query = batched_query(query, sync_config.limit, sync_config.offset) unless sync_config.limit.nil? && sync_config.offset.nil?
43
+
44
+ db = create_connection(connection_config)
45
+
46
+ query(db, query)
47
+ rescue StandardError => e
48
+ handle_exception(
49
+ "SNOWFLAKE:READ:EXCEPTION",
50
+ "error",
51
+ e
52
+ )
53
+ end
54
+
55
+ private
56
+
57
+ def query(connection, query)
58
+ records = []
59
+ connection.fetch(query) do |row|
60
+ records << RecordMessage.new(data: row, emitted_at: Time.now.to_i).to_multiwoven_message
61
+ end
62
+ records
63
+ end
64
+
65
+ def create_connection(connection_config)
66
+ raise "Unsupported Auth type" if connection_config[:credentials][:auth_type] != "username/password"
67
+
68
+ Sequel.odbc(drvconnect: generate_drvconnect(connection_config))
69
+ end
70
+
71
+ def generate_drvconnect(connection_config)
72
+ c = connection_config[:credentials]
73
+ "driver=#{SNOWFLAKE_DRIVER_PATH};server=#{connection_config[:host]};uid=#{c[:username]};pwd=#{c[:password]};schema=#{connection_config[:schema]};database=#{connection_config[:database]};warehouse=#{connection_config[:warehouse]};"
74
+ end
75
+
76
+ def create_streams(records)
77
+ group_by_table(records).map do |r|
78
+ Multiwoven::Integrations::Protocol::Stream.new(name: r[:tablename], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
79
+ end
80
+ end
81
+
82
+ def group_by_table(records)
83
+ records.group_by { |entry| entry[:table_name] }.map do |table_name, columns|
84
+ {
85
+ tablename: table_name,
86
+ columns: columns.map { |column| { column_name: column[:column_name], type: column[:data_type], optional: column[:is_nullable] == "YES" } }
87
+ }
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,15 @@
1
+ {
2
+ "data": {
3
+ "name": "Snowflake",
4
+ "title": "Snowflake",
5
+ "connector_type": "source",
6
+ "category": "Data Warehouse",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "source-snowflake",
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,82 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/sources/snowflake",
3
+ "stream_type": "dynamic",
4
+ "connector_query_type": "raw_sql",
5
+ "connection_specification": {
6
+ "$schema": "http://json-schema.org/draft-07/schema#",
7
+ "title": "Snowflake",
8
+ "type": "object",
9
+ "required": ["host", "role", "warehouse", "database"],
10
+ "properties": {
11
+ "credentials": {
12
+ "title": "",
13
+ "type": "object",
14
+ "required": ["auth_type", "username", "password"],
15
+ "properties": {
16
+ "auth_type": {
17
+ "type": "string",
18
+ "default": "username/password",
19
+ "order": 0,
20
+ "readOnly": true
21
+ },
22
+ "username": {
23
+ "description": "The username you created to allow multiwoven to access the database.",
24
+ "examples": ["MULTIWOVEN_USER"],
25
+ "type": "string",
26
+ "title": "Username",
27
+ "order": 1
28
+ },
29
+ "password": {
30
+ "description": "The password associated with the username.",
31
+ "type": "string",
32
+ "multiwoven_secret": true,
33
+ "title": "Password",
34
+ "order": 2
35
+ }
36
+ },
37
+ "order": 0
38
+ },
39
+ "host": {
40
+ "description": "The host domain of the snowflake instance (must include the account, region, cloud environment, and end with snowflakecomputing.com).",
41
+ "examples": ["accountname.us-east-2.aws.snowflakecomputing.com"],
42
+ "type": "string",
43
+ "title": "Account Name",
44
+ "order": 1
45
+ },
46
+ "role": {
47
+ "description": "The role you created for multiwoven to access Snowflake.",
48
+ "examples": ["MULTIWOVEN_ROLE"],
49
+ "type": "string",
50
+ "title": "Role",
51
+ "order": 2
52
+ },
53
+ "warehouse": {
54
+ "description": "The warehouse you created for multiwoven to access data.",
55
+ "examples": ["MULTIWOVEN_WAREHOUSE"],
56
+ "type": "string",
57
+ "title": "Warehouse",
58
+ "order": 3
59
+ },
60
+ "database": {
61
+ "description": "The database you created for multiwoven to access data.",
62
+ "examples": ["MULTIWOVEN_DATABASE"],
63
+ "type": "string",
64
+ "title": "Database",
65
+ "order": 4
66
+ },
67
+ "schema": {
68
+ "description": "The source Snowflake schema tables. Leave empty to access tables from multiple schemas.",
69
+ "examples": ["MULTIWOVEN_SCHEMA"],
70
+ "type": "string",
71
+ "title": "Schema",
72
+ "order": 5
73
+ },
74
+ "jdbc_url_params": {
75
+ "description": "Additional properties to pass to the JDBC URL string when connecting to the database formatted as 'key=value' pairs separated by the symbol '&'. (example: key1=value1&key2=value2&key3=value3).",
76
+ "title": "JDBC URL Params",
77
+ "type": "string",
78
+ "order": 6
79
+ }
80
+ }
81
+ }
82
+ }
@@ -0,0 +1,10 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg viewBox="0 0 44 44" stroke="#249edc" fill="#249edc" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <path d="M37.2637465,33.128906 L28.0879655,27.828125 C26.7989025,27.085938 25.1504655,27.527344 24.4043715,28.816406 C24.1153085,29.324219 24.0020275,29.882812 24.0567155,30.425781 L24.0567155,40.785156 C24.0567155,42.265625 25.2598395,43.46875 26.7442155,43.46875 C28.2246835,43.46875 29.4278085,42.265625 29.4278085,40.785156 L29.4278085,34.828125 L34.5684335,37.796875 C35.8574965,38.542969 37.5098395,38.097656 38.2520275,36.808594 C38.9981215,35.519531 38.5567155,33.871094 37.2637465,33.128906" id="Path"></path>
4
+ <path d="M14.4434335,21.769531 C14.4590585,20.8125 13.9551525,19.921875 13.1270275,19.441406 L3.95124649,14.144531 C3.55280849,13.914062 3.09577749,13.792969 2.63874649,13.792969 C1.69733949,13.792969 0.822339495,14.296875 0.353589495,15.109375 C-0.372972505,16.367188 0.060621495,17.980469 1.31843349,18.707031 L6.60749649,21.757812 L1.31843349,24.8125 C0.709058495,25.164062 0.271558495,25.730469 0.091871495,26.410156 C-0.091722505,27.089844 0.00202749496,27.800781 0.353589495,28.410156 C0.822339495,29.222656 1.69733949,29.726562 2.63483949,29.726562 C3.09577749,29.726562 3.55280849,29.605469 3.95124649,29.375 L13.1270275,24.078125 C13.9473395,23.601562 14.4512465,22.71875 14.4434335,21.769531" id="Path2"></path>
5
+ <path d="M6.03327749,10.390625 L15.2090585,15.6875 C16.2793715,16.308594 17.5996835,16.105469 18.4434335,15.28125 C18.9785895,14.789062 19.3106215,14.085938 19.3106215,13.304688 L19.3106215,2.6875 C19.3106215,1.203125 18.1074965,0 16.6270275,0 C15.1426525,0 13.9395275,1.203125 13.9395275,2.6875 L13.9395275,8.730469 L8.72858949,5.722656 C7.43952749,4.976562 5.79108949,5.417969 5.04499649,6.707031 C4.29890249,7.996094 4.74421549,9.644531 6.03327749,10.390625" id="Path3"></path>
6
+ <path d="M26.6660895,22.199219 C26.6660895,22.402344 26.5489025,22.683594 26.4043715,22.832031 L22.7676525,26.46875 C22.6231215,26.613281 22.3379655,26.730469 22.1348395,26.730469 L21.2090585,26.730469 C21.0059335,26.730469 20.7207775,26.613281 20.5762465,26.46875 L16.9356215,22.832031 C16.7910895,22.683594 16.6739025,22.402344 16.6739025,22.199219 L16.6739025,21.273438 C16.6739025,21.066406 16.7910895,20.785156 16.9356215,20.640625 L20.5762465,17 C20.7207775,16.855469 21.0059335,16.738281 21.2090585,16.738281 L22.1348395,16.738281 C22.3379655,16.738281 22.6231215,16.855469 22.7676525,17 L26.4043715,20.640625 C26.5489025,20.785156 26.6660895,21.066406 26.6660895,21.273438 L26.6660895,22.199219 Z M23.4199965,21.753906 L23.4199965,21.714844 C23.4199965,21.566406 23.3340585,21.359375 23.2285895,21.25 L22.1543715,20.179688 C22.0489025,20.070312 21.8418715,19.984375 21.6895275,19.984375 L21.6504655,19.984375 C21.5020275,19.984375 21.2949965,20.070312 21.1856215,20.179688 L20.1153085,21.25 C20.0098395,21.355469 19.9239025,21.5625 19.9239025,21.714844 L19.9239025,21.753906 C19.9239025,21.90625 20.0098395,22.113281 20.1153085,22.21875 L21.1856215,23.292969 C21.2949965,23.398438 21.5020275,23.484375 21.6504655,23.484375 L21.6895275,23.484375 C21.8418715,23.484375 22.0489025,23.398438 22.1543715,23.292969 L23.2285895,22.21875 C23.3340585,22.113281 23.4199965,21.90625 23.4199965,21.753906 Z" id="Combined-Shape"></path>
7
+ <path d="M28.0879655,15.6875 L37.2637465,10.390625 C38.5528085,9.648438 38.9981215,7.996094 38.2520275,6.707031 C37.5059335,5.417969 35.8574965,4.976562 34.5684335,5.722656 L29.4278085,8.691406 L29.4278085,2.6875 C29.4278085,1.203125 28.2246835,-5.68434189e-14 26.7442155,-5.68434189e-14 C25.2598395,-5.68434189e-14 24.0567155,1.203125 24.0567155,2.6875 L24.0567155,13.09375 C24.0059335,13.632812 24.1114025,14.195312 24.4043715,14.703125 C25.1504655,15.992188 26.7989025,16.433594 28.0879655,15.6875" id="Path4"></path>
8
+ <path d="M17.0489025,27.515625 C16.4395275,27.398438 15.7871835,27.496094 15.2090585,27.828125 L6.03327749,33.128906 C4.74421549,33.871094 4.29890249,35.519531 5.04499649,36.808594 C5.79108949,38.101562 7.43952749,38.542969 8.72858949,37.796875 L13.9395275,34.789062 L13.9395275,40.785156 C13.9395275,42.265625 15.1426525,43.46875 16.6270275,43.46875 C18.1074965,43.46875 19.3106215,42.265625 19.3106215,40.785156 L19.3106215,30.167969 C19.3106215,28.828125 18.3301525,27.71875 17.0489025,27.515625" id="Path5"></path>
9
+ <path d="M42.9981215,15.078125 C42.2559335,13.785156 40.6035895,13.34375 39.3145275,14.089844 L30.1387465,19.386719 C29.2598395,19.894531 28.7754655,20.824219 28.7910895,21.769531 C28.7832775,22.710938 29.2676525,23.628906 30.1387465,24.128906 L39.3145275,29.429688 C40.6035895,30.171875 42.2520275,29.730469 42.9981215,28.441406 C43.7442155,27.152344 43.2989025,25.503906 42.0098395,24.757812 L36.8145275,21.757812 L42.0098395,18.757812 C43.3028085,18.015625 43.7442155,16.367188 42.9981215,15.078125" id="Path6"></path>
10
+ </svg>
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require "zoho_books" # or the correct gem for Zoho Books
4
+ require "stringio"
5
+
6
+ module Multiwoven
7
+ module Integrations
8
+ module Source
9
+ module ZohoBooks
10
+ include Multiwoven::Integrations::Core
11
+
12
+ class Client < DestinationConnector
13
+ prepend Multiwoven::Integrations::Core::RateLimiter
14
+
15
+ def check_connection(connection_config)
16
+ connection_config = connection_config.with_indifferent_access
17
+ puts "connection_config zoho books: #{connection_config}"
18
+ create_connection(connection_config)
19
+ # authenticate_client
20
+ success_status
21
+ rescue StandardError => e
22
+ handle_exception("ZOHO:BOOKS:DISCOVER:EXCEPTION:check_connection", "error", e)
23
+ failure_status(e)
24
+ end
25
+
26
+ def discover(_connection_config = nil)
27
+ catalog = build_catalog(load_catalog)
28
+ catalog.to_multiwoven_message
29
+ rescue StandardError => e
30
+ handle_exception("ZOHO:BOOKS:DISCOVER:EXCEPTION:discover", "error", e)
31
+ end
32
+
33
+ def write(sync_config, records, action = "create")
34
+ @action = sync_config.stream.action || action
35
+ create_connection(sync_config.destination.connection_specification)
36
+ process_records(records, sync_config.stream)
37
+ rescue StandardError => e
38
+ handle_exception("ZOHO:BOOKS:WRITE:EXCEPTION:write", "error", e)
39
+ end
40
+
41
+ # def setting_base_url(url)
42
+ # @base_url = url
43
+ # puts "setting_base_url only for zoho: #{@base_url}"
44
+ # end
45
+
46
+ private
47
+
48
+ def create_connection(config)
49
+ # config = config.with_indifferent_access
50
+ @client_id = config[:client_id]
51
+ @client_secret = config[:client_secret]
52
+ @refresh_token = config[:refresh_token]
53
+ @organization_id = config[:organization_id]
54
+ @data_center = config[:data_center]
55
+ @base_url = config[:base_url]
56
+
57
+ # Zoho token endpoint
58
+ token_url = "https://accounts.zoho.com/oauth/v2/token"
59
+
60
+ # Prepare the request parameters
61
+ uri = URI.parse(token_url)
62
+ params = {
63
+ client_id: @client_id,
64
+ client_secret: @client_secret,
65
+ refresh_token: @refresh_token,
66
+ grant_type: 'refresh_token'
67
+ }
68
+
69
+ # Make the HTTP POST request to get the access token
70
+ response = Net::HTTP.post_form(uri, params)
71
+
72
+ # Parse the response
73
+ result = JSON.parse(response.body)
74
+
75
+ # Check if the response has an error or return the access token
76
+ if result["access_token"]
77
+ @access_token = result["access_token"]
78
+ puts "@access_token = result[VITORY]: #{@access_token}"
79
+ @access_token
80
+ else
81
+ raise "Error fetching access token: #{result['error']}"
82
+ end
83
+ end
84
+
85
+ def query(connection, query)
86
+ connection.exec(query) do |result|
87
+ result.map do |row|
88
+ RecordMessage.new(data: row, emitted_at: Time.now.to_i).to_multiwoven_message
89
+ end
90
+ end
91
+ end
92
+
93
+ def process_records(records, stream)
94
+ write_success = 0
95
+ write_failure = 0
96
+ properties = stream.json_schema.with_indifferent_access[:properties]
97
+ records.each do |record_object|
98
+ record = extract_data(record_object, properties)
99
+ send_data_to_zoho_books(stream.name, record)
100
+ write_success += 1
101
+ rescue StandardError => e
102
+ handle_exception("ZOHO:BOOKS:WRITE:EXCEPTION:process_records", "error", e)
103
+ write_failure += 1
104
+ end
105
+ tracking_message(write_success, write_failure)
106
+ end
107
+
108
+ def send_data_to_zoho_books(stream_name, record = {})
109
+ args = build_args(@action, stream_name, record)
110
+ zoho_books_stream = @client.send(stream_name) # Update with Zoho Books API structure
111
+ zoho_books_data = { record_input: args }
112
+ zoho_books_stream.send(@action, zoho_books_data) # Adjust to the correct API action
113
+ end
114
+
115
+ def build_args(action, stream_name, record)
116
+ case action
117
+ when :upsert
118
+ [stream_name, record[:external_key], record]
119
+ when :destroy
120
+ [stream_name, record[:id]]
121
+ else
122
+ record
123
+ end
124
+ end
125
+
126
+ # def authenticate_client
127
+ # @client.get_invoices # Replace with a Zoho Books API method to test connection
128
+ # end
129
+
130
+ def success_status
131
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
132
+ end
133
+
134
+ def failure_status(error)
135
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: error.message).to_multiwoven_message
136
+ end
137
+
138
+ def load_catalog
139
+ read_json(CATALOG_SPEC_PATH)
140
+ end
141
+
142
+ def tracking_message(success, failure)
143
+ Multiwoven::Integrations::Protocol::TrackingMessage.new(
144
+ success: success, failed: failure
145
+ ).to_multiwoven_message
146
+ end
147
+
148
+ def log_debug(message)
149
+ Multiwoven::Integrations::Service.logger.debug(message)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,16 @@
1
+ {
2
+ "data": {
3
+ "name": "ZohoBooks",
4
+ "title": "Zoho Books",
5
+ "connector_type": "source",
6
+ "category": "Accounting",
7
+ "documentation_url": "https://docs.multiwoven.com/destinations/accounting/zohobooks",
8
+ "github_issue_label": "destination-zohobooks-invoices",
9
+ "icon": "icon.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven", "invoices"]
14
+ }
15
+ }
16
+
@@ -0,0 +1,43 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/destinations/accounting/zohobooks",
3
+ "stream_type": "static",
4
+ "connection_specification": {
5
+ "$schema": "http://json-schema.org/draft-07/schema#",
6
+ "title": "Zoho Books",
7
+ "type": "object",
8
+ "required": ["client_id", "client_secret", "refresh_token", "organization_id", "data_center"],
9
+ "properties": {
10
+ "client_id": {
11
+ "type": "string",
12
+ "title": "Client ID",
13
+ "description": "The OAuth 2.0 client ID for Zoho Books API access.",
14
+ "order": 0
15
+ },
16
+ "client_secret": {
17
+ "type": "string",
18
+ "title": "Client Secret",
19
+ "description": "The OAuth 2.0 client secret for Zoho Books API access.",
20
+ "order": 1
21
+ },
22
+ "refresh_token": {
23
+ "type": "string",
24
+ "title": "Refresh Token",
25
+ "description": "The OAuth 2.0 refresh token for Zoho Books API access.",
26
+ "order": 2
27
+ },
28
+ "organization_id": {
29
+ "type": "string",
30
+ "title": "Organization ID",
31
+ "description": "The unique identifier for your Zoho Books organization.",
32
+ "order": 3
33
+ },
34
+ "data_center": {
35
+ "type": "string",
36
+ "title": "Data Center",
37
+ "description": "The unique identifier for your Zoho Books organization.",
38
+ "order": 4
39
+ }
40
+ }
41
+ }
42
+ }
43
+
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+
3
+ <svg width="800px" height="800px" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
4
+ <style>
5
+ .st1{fill:#fff}
6
+ </style>
7
+ <g id="Icon">
8
+ <circle id="Background" cx="512" cy="512" r="512" style="fill:#c82336"/>
9
+ <g id="ZOHO_logo__x28_monochrome_edit_by_krisu_x29_">
10
+ <path id="O_1_" class="st1" d="M810.3 452.6 829 431v147.8l-18.7 18.7V452.6zm-137.7-2.4h135.2l19-21.9-134.2.1m45.2 62.6c-9.3 0-16.4 3.2-21.7 9.6-5.3 6.4-7.9 15.2-7.9 26.6 0 11.2 2.7 19.9 8 26.2 5.3 6.3 12.4 9.5 21.6 9.5 9.2 0 16.3-3.2 21.5-9.5 5.3-6.4 7.9-15.1 7.9-26.2 0-11.4-2.7-20.2-7.9-26.6-5.2-6.4-12.3-9.6-21.5-9.6zm69.1-37.4v145.5H671.1V453.6h135.8zm-15.2 73.6c0-8-1.4-15.5-4.1-22.3-2.7-6.8-6.7-13-12.1-18.4-5.1-5.2-10.8-9.1-17.2-11.8-6.3-2.6-13.2-3.9-20.5-3.9-7.4 0-14.3 1.3-20.7 3.9-6.4 2.6-12.1 6.6-17.2 11.8-5.3 5.4-9.3 11.5-12 18.3-2.7 6.8-4 14.3-4 22.4 0 8 1.3 15.4 4 22.3s6.7 13.1 12 18.5c4.9 5.1 10.6 9 17 11.6 6.4 2.6 13.3 3.9 20.9 3.9 7.3 0 14.1-1.3 20.5-3.9 6.4-2.6 12.1-6.5 17.2-11.6 5.3-5.5 9.4-11.6 12.1-18.5 2.7-6.8 4.1-14.3 4.1-22.3z"/>
11
+ <path id="H" class="st1" d="m675.2 552-9 25.1L648 448.7l10.1-28.9m-133.4 45.3-.2-1.7L645 447l10.7-30.6-133.4 18.5-9.5 31.8 11.9-1.6zm138.6 115.6-131.7 18.5-17.9-126 13.4 6 .7-1.5-.5-2.1-14.1-6.4-.4-2.5 11.8-1.6.4 1.7 119.7-16.3 18.6 130.2zm-26.4-14.4c0-1-.1-2.1-.3-3.3l-13.1-89.3c-.6-4.1-2-7.2-4.3-9.3-1.8-1.6-4.1-2.5-6.7-2.5-.7 0-1.3.1-2 .2-3.5.5-6.1 2.1-7.7 4.7-1.2 1.9-1.8 4.2-1.8 6.9 0 1 .1 2.1.3 3.2l4.9 34.3-38.3 5.6-4.9-34.3c-.6-4-2-7.1-4.2-9.2-1.8-1.7-4.1-2.6-6.6-2.6-.6 0-1.2 0-1.8.1-3.6.5-6.4 2.1-8 4.7-1.2 1.9-1.8 4.2-1.8 6.9 0 1 .1 2.1.3 3.3L554 575c.6 4.1 2 7.2 4.4 9.2 1.8 1.6 4.1 2.4 6.8 2.4.7 0 1.5-.1 2.2-.2 3.3-.5 5.9-2.1 7.4-4.7 1.1-1.9 1.6-4.1 1.6-6.7 0-1-.1-2.1-.3-3.3l-5.4-35.3 38.3-5.6 5.4 35.3c.6 4.1 2 7.2 4.3 9.2 1.8 1.6 4.1 2.4 6.7 2.4.7 0 1.3-.1 2.1-.2 3.4-.5 6.1-2.1 7.7-4.7 1.2-1.6 1.7-3.9 1.7-6.5z"/>
12
+ <path id="O" class="st1" d="m353.6 543-6.2-47 47.2-111.9 11.7 39.4L357.6 534l-4 9zm101.9-65.8c-3.9-1.7-7.7-2.5-11.4-2.5-4.2 0-8.3 1.1-12.2 3.2-7.4 4.1-13.4 11.5-18 22.4-3 7.1-4.5 13.6-4.5 19.6 0 3 .4 5.9 1.1 8.6 2.3 8.1 7.5 13.8 15.9 17.4 3.9 1.6 7.6 2.5 11.3 2.5 4.2 0 8.2-1.1 12.2-3.2 7.4-4 13.3-11.3 17.8-22 3.1-7.3 4.6-14 4.6-20.1 0-2.9-.4-5.7-1.1-8.4-2.3-8.2-7.4-13.9-15.7-17.5zm62.2-39.6-120.1-55 11.8 39.8 117.8 53.2m-.1 3.6-53.3 121.1L355.6 547v-.1l53.3-121 118.2 53.3zm-31.9 30.6v-2.2c0-6.8-1.2-13.5-3.6-20-2.5-6.9-6.2-12.9-10.9-17.8-4.7-5-10.4-8.9-17.1-11.7-6.7-2.8-13.4-4.2-20.2-4.2h-.3c-6.9 0-13.7 1.6-20.4 4.6-7 3.1-13.1 7.3-18.3 12.8-5.2 5.5-9.4 12.1-12.7 19.8-3.2 7.6-5 15.2-5.3 22.8 0 .9-.1 1.7-.1 2.6 0 6.7 1.2 13.3 3.4 19.7 2.4 6.8 6 12.6 10.8 17.6 4.7 4.9 10.6 8.9 17.5 11.8 6.6 2.8 13.3 4.2 20.1 4.2h.2c6.8 0 13.6-1.5 20.3-4.4 7.1-3.2 13.2-7.5 18.5-13 5.2-5.5 9.5-12.1 12.7-19.7 3.3-7.8 5.1-15.4 5.4-22.9z"/>
13
+ <path id="Z" class="st1" d="m349.5 573.2-19-124.8 8.1-42.3 18.8 126-7.9 41.1zm-2.6 5.2-130.7 20.5-21-128.4 132.2-20.2 19.5 128.1zm-30.1-14.1c0-.7-.1-1.4-.2-2.2-.3-1.8-1-3.4-2-4.6-1-1.2-2.4-2.2-4.2-2.8-1.8-.6-3.9-.9-6.4-.9-2.4 0-5.1.3-8.1.8l-34.9 6c.4-2.7 1.7-6.2 4.1-10.5 2.6-4.8 6.5-10.6 11.6-17.4 1.8-2.4 3.1-4.1 4-5.4.7-.8 1.6-2 2.8-3.6 8-10.5 12.9-19 14.8-25.7 1.1-3.8 1.7-7.6 2-11.5.1-1.1.1-2.1.1-3.1 0-2.8-.2-5.5-.7-8.2-.4-2.4-1-4.5-1.8-6.1-.8-1.6-1.8-2.9-3.1-3.6-1.4-.9-3.4-1.2-6-1.2-2.2 0-4.8.3-7.8.8l-40.4 6.9c-4.9.8-8.5 2.3-10.8 4.4-1.8 1.7-2.8 3.9-2.8 6.5 0 .7.1 1.4.2 2.1.6 3.4 2.4 5.9 5.2 7.2 1.7.8 3.8 1.1 6.2 1.1 1.6 0 3.4-.2 5.4-.5l33.8-5.7c.1.6.1 1.2.1 1.8 0 2.1-.4 4.2-1.1 6.2-1 2.8-3.5 6.8-7.5 11.9-1.1 1.4-2.9 3.5-5.2 6.5-9.1 11-15.6 20.4-19.6 28.3-2.8 5.4-4.7 10.7-5.8 15.9-.6 3-.9 6-.9 8.8 0 2 .2 4 .5 5.8.5 2.7 1.1 4.9 2 6.7.9 1.8 2 3.1 3.5 3.9 1.3.7 3.3 1 5.9 1 3.4 0 7.9-.5 13.7-1.5l36.6-6.3c6.5-1.1 11-2.7 13.6-4.8 2.2-1.9 3.3-4.3 3.2-7zM204.3 422.5l-6.7 33.3-2.3 11.3 132.1-20.2 8.7-45.5-131.8 21.1z"/>
14
+ </g>
15
+ </g>
16
+ </svg>
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "dry-struct"
5
+ require "dry-schema"
6
+ require "dry-types"
7
+ require "odbc"
8
+ require "sequel"
9
+ require "byebug"
10
+ require "net/http"
11
+ require "uri"
12
+ require "active_support/core_ext/hash/indifferent_access"
13
+ require "restforce"
14
+ require "logger"
15
+ require "slack-ruby-client"
16
+ require "git"
17
+ require "ruby-limiter"
18
+ require "hubspot-api-client"
19
+ require "google/apis/sheets_v4"
20
+ require "stringio"
21
+ require "stripe"
22
+ require "net/sftp"
23
+ require "csv"
24
+ require "securerandom"
25
+
26
+ # Service
27
+ require_relative "integrations/config"
28
+ require_relative "integrations/rollout"
29
+ require_relative "integrations/service"
30
+
31
+ # Core
32
+ require_relative "integrations/core/constants"
33
+ require_relative "integrations/core/utils"
34
+ require_relative "integrations/core/rate_limiter"
35
+ require_relative "integrations/core/fullrefresher"
36
+ require_relative "integrations/protocol/protocol"
37
+ require_relative "integrations/core/base_connector"
38
+ require_relative "integrations/core/source_connector"
39
+ require_relative "integrations/core/destination_connector"
40
+ require_relative "integrations/core/http_client"
41
+ require_relative "integrations/core/query_builder"
42
+
43
+ # Source
44
+ require_relative "integrations/source/snowflake/client"
45
+ require_relative "integrations/source/redshift/client"
46
+ require_relative "integrations/source/bigquery/client"
47
+ require_relative "integrations/source/postgresql/client"
48
+ require_relative "integrations/source/databricks/client"
49
+ require_relative "integrations/source/salesforce_consumer_goods_cloud/client"
50
+ require_relative "integrations/source/zoho_books/client"
51
+
52
+ # Destination
53
+ require_relative "integrations/destination/klaviyo/client"
54
+ require_relative "integrations/destination/salesforce_crm/client"
55
+ require_relative "integrations/destination/facebook_custom_audience/client"
56
+ require_relative "integrations/destination/slack/client"
57
+ require_relative "integrations/destination/hubspot/client"
58
+ require_relative "integrations/destination/google_sheets/client"
59
+ require_relative "integrations/destination/airtable/client"
60
+ require_relative "integrations/destination/stripe/client"
61
+ require_relative "integrations/destination/salesforce_consumer_goods_cloud/client"
62
+ require_relative "integrations/destination/sftp/client"
63
+ require_relative "integrations/destination/postgresql/client"
64
+ require_relative "integrations/destination/tally/client"
65
+
66
+ module Multiwoven
67
+ module Integrations
68
+ class Error < StandardError; end
69
+ # Your code goes here...
70
+ end
71
+ end
Binary file
@@ -0,0 +1,6 @@
1
+ module Multiwoven
2
+ module Integrations
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end