diff --git a/README.md b/README.md index d813591..c7be9ab 100644 --- a/README.md +++ b/README.md @@ -8,21 +8,166 @@ ## Overview -[//]: # (TODO: Add overview mentioning the purpose of the module, supported REST API versions, and other high-level details.) +[HubSpot](https://www.hubspot.com) is an AI-powered customer relationship management (CRM) platform. + +The `ballerinax/hubspot.crm.object.schemas` offers APIs to connect and interact with the [HubSpot Schemas API](https://developers.hubspot.com/docs/guides/api/crm/objects/schemas) endpoints, specifically based on the [HubSpot REST API](https://developers.hubspot.com/docs/reference/api). ## Setup guide -[//]: # (TODO: Add detailed steps to obtain credentials and configure the module.) +To use the `HubSpot CRM Object Schemas` connector, you must have access to the HubSpot API through a HubSpot developer account and an app under it. If you do not have a HubSpot developer account, you can sign up for one [here](https://developers.hubspot.com/get-started). + +### Step 1: Create a HubSpot Developer Account + +App Developer Accounts, allow you to create developer test accounts to test apps. + +> **Note:** These accounts are only for development and testing purposes. Not to be used in production. + +1. Go to "Test Account section" from the left sidebar. + + ![Test accounts](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/main/docs/setup/resources/test_acc_img1.png) + +2. Click "Create developer test account". + + ![Developer Test Accounts](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/main/docs/setup/resources/test_acc_img2.png) + +3. In the next dialogue box, give a name to your test account and click "Create". + + ![Hubspot developer account name](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/main/docs/setup/resources/test_acc_img3.png) + +### Step 2: Create a HubSpot App under your account. + +1. In your developer account, navigate to the "Apps" section. Click on "Create App". + ![Hubspot App Creation](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/main/docs/setup/resources/app_img1.png) + +2. Provide the necessary details, including the app name and description. + +### Step 3: Configure the Authentication Flow. + +1. Move to the Auth Tab. + + ![Auth tab](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/main/docs/setup/resources/auth.png) + +2. In the "Scopes" section, add the following scopes for your app using the "Add new scope" button. + + * `schemas` + * `oath` + +3. Add your Redirect URI in the relevant section. You can use `localhost` addresses for local development purposes. Then Click "Create App". + + ![Redirect URI](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/main/docs/setup/resources/redirect_url.png) + +### Step 4: Get your Client ID and Client Secret + +- Navigate to the "Auth" tab. Make sure to save the provided Client ID and Client Secret. + + ![Credentials](https://raw.githubusercontent.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/main/docs/setup/resources/credentials.png) + +### Step 5: Setup Authentication Flow + +Before proceeding with the Quickstart, ensure you have obtained the Access Token using the following steps: + +1. Create an authorization URL using the following format: + + ``` + https://app.hubspot.com/oauth/authorize?client_id=&scope=&redirect_uri= + ``` + + Replace the ``, `` and `` with the above obtained values. + +2. Paste it in the browser and select your developer test account to install the app when prompted. + +3. A code will be displayed in the browser. Copy that code. + +4. Run the following curl command. Replace the ``, ` and `` with your specific value. Use the code you received in the above step 3 as the ``. + + - Linux/macOS + + ```bash + curl --request POST \ + --url https://api.hubapi.com/oauth/v1/token \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + - Windows + + ```bash + curl --request POST ^ + --url https://api.hubapi.com/oauth/v1/token ^ + --header 'content-type: application/x-www-form-urlencoded' ^ + --data 'grant_type=authorization_code&code=&redirect_uri=&client_id=&client_secret=' + ``` + + This command will return the access token necessary for API calls. + + ```json + { + "token_type": "bearer", + "refresh_token": "", + "access_token": "", + "expires_in": 1800 + } + ``` + +5. Store the access token and refresh token securely for use in your application. ## Quickstart -[//]: # (TODO: Add a quickstart guide to demonstrate a basic functionality of the module, including sample code snippets.) +To use the `HubSpot CRM Object Schemas` connector in your Ballerina application, update the `.bal` file as follows: -## Examples +### Step 1: Import the module + +Import the `hubspot.crm.obj.schemas` module and `oauth2` module. + +```ballerina +import ballerina/oauth2; +import ballerinax/hubspot.crm.obj.schemas as hsschemas; +``` -The `HubSpot CRM Object Schemas` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/module-ballerinax-hubspot.crm.object.schemas/tree/main/examples/), covering the following use cases: +### Step 2: Instantiate a new connector + +1. Create a `Config.toml` file inside the Ballerina package and add the following configurations with the values retrieved in the earlier steps as follows: + + ```toml + clientId = + clientSecret = + refreshToken = + ``` + +2. Instantiate a `OAuth2RefreshTokenGrantConfig` with the obtained credentials and initialize the connector with it. + + ```ballerina + configurable string clientId = ?; + configurable string clientSecret = ?; + configurable string refreshToken = ?; + + OAuth2RefreshTokenGrantConfig auth = { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + }; + + final hsschemas:Client hpClient = check new ({auth}); + ``` + +Now, utilize the available connector operations. A sample use case is shown below. + +#### Get all schemas + +```ballerina +public function main() returns error? { + CollectionResponseObjectSchemaNoPaging response = check hpClient->/.get(); + + io:println(response); +} + +``` +## Examples -[//]: # (TODO: Add examples) +The `HubSpot CRM Object Schemas` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.obj.schemas/tree/main/examples), covering the following use cases: + 1. [Auther and Book assosiation](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/tree/main/examples/book-author) + 2. [Product spec update](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/tree/main/examples/product-update) ## Build from the source diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index b65cec2..58e2d67 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,16 +1,15 @@ [package] distribution = "2201.10.0" org = "ballerinax" -name = "hubspot.crm.object.schemas" +name = "hubspot.crm.obj.schemas" version = "1.0.0" license = ["Apache-2.0"] authors = ["Ballerina"] -keywords = [] -# icon = "icon.png" # TODO: update icon.png +keywords = ["schema", "hubspot", "object"] repository = "https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas" [build-options] observabilityIncluded = true -[platform.java21] +[platform.java17] graalvmCompatible = true diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml new file mode 100644 index 0000000..74b9894 --- /dev/null +++ b/ballerina/Dependencies.toml @@ -0,0 +1,332 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.10.3" + +[[package]] +org = "ballerina" +name = "auth" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "cache" +version = "3.8.0" +dependencies = [ + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "task"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "constraint" +version = "1.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "crypto" +version = "2.7.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "file" +version = "1.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "os"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "http" +version = "2.12.4" +dependencies = [ + {org = "ballerina", name = "auth"}, + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "constraint"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "file"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "jwt"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.decimal"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.regexp"}, + {org = "ballerina", name = "lang.runtime"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "mime"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "observe"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "http", moduleName = "http"}, + {org = "ballerina", packageName = "http", moduleName = "http.httpscerr"} +] + +[[package]] +org = "ballerina" +name = "io" +version = "1.6.3" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "jwt" +version = "2.13.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "lang.string"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "lang.__internal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.array" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"} +] + +[[package]] +org = "ballerina" +name = "lang.decimal" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.int" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.__internal"}, + {org = "ballerina", name = "lang.object"} +] + +[[package]] +org = "ballerina" +name = "lang.object" +version = "0.0.0" + +[[package]] +org = "ballerina" +name = "lang.regexp" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.runtime" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.string" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.regexp"} +] + +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "log" +version = "2.10.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"}, + {org = "ballerina", name = "observe"} +] + +[[package]] +org = "ballerina" +name = "mime" +version = "2.10.1" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.int"}, + {org = "ballerina", name = "log"} +] + +[[package]] +org = "ballerina" +name = "oauth2" +version = "2.12.0" +dependencies = [ + {org = "ballerina", name = "cache"}, + {org = "ballerina", name = "crypto"}, + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "log"}, + {org = "ballerina", name = "time"}, + {org = "ballerina", name = "url"} +] +modules = [ + {org = "ballerina", packageName = "oauth2", moduleName = "oauth2"} +] + +[[package]] +org = "ballerina" +name = "observe" +version = "1.3.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "os" +version = "1.8.0" +dependencies = [ + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "task" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "time"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.array"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + +[[package]] +org = "ballerina" +name = "time" +version = "2.5.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "url" +version = "2.4.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] +modules = [ + {org = "ballerina", packageName = "url", moduleName = "url"} +] + +[[package]] +org = "ballerinai" +name = "observe" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "observe"} +] +modules = [ + {org = "ballerinai", packageName = "observe", moduleName = "observe"} +] + +[[package]] +org = "ballerinax" +name = "hubspot.crm.obj.schemas" +version = "1.0.0" +dependencies = [ + {org = "ballerina", name = "http"}, + {org = "ballerina", name = "io"}, + {org = "ballerina", name = "oauth2"}, + {org = "ballerina", name = "test"}, + {org = "ballerina", name = "url"}, + {org = "ballerinai", name = "observe"} +] +modules = [ + {org = "ballerinax", packageName = "hubspot.crm.obj.schemas", moduleName = "hubspot.crm.obj.schemas"} +] + diff --git a/ballerina/build.gradle b/ballerina/build.gradle index c5d96fa..eeecdb8 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -24,7 +24,7 @@ plugins { description = 'HubSpot CRM Object Schemas - Ballerina' -def packageName = "hubspot.crm.object.schemas" +def packageName = "hubspot.crm.obj.schemas" def packageOrg = "ballerinax" def tomlVersion = stripBallerinaExtensionVersion("${project.version}") def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") diff --git a/ballerina/client.bal b/ballerina/client.bal index 66cdc3f..a428821 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -1,3 +1,6 @@ +// AUTO-GENERATED FILE. DO NOT MODIFY. +// This file is auto-generated by the Ballerina OpenAPI tool. + // Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). // // WSO2 LLC. licenses this file to you under the Apache License, @@ -13,3 +16,131 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. + +import ballerina/http; + +# The CRM uses schemas to define how custom objects should store and represent information in the HubSpot CRM. Schemas define details about an object's type, properties, and associations. The schema can be uniquely identified by its **object type ID**. +public isolated client class Client { + final http:Client clientEp; + final readonly & ApiKeysConfig? apiKeyConfig; + + # Gets invoked to initialize the `connector`. + # + # + config - The configurations to be used when initializing the `connector` + # + serviceUrl - URL of the target service + # + return - An error if connector initialization failed + public isolated function init(ConnectionConfig config, string serviceUrl = "https://api.dfgdfghubapi.com/crm-object-schemas/v3/schemas") returns error? { + http:ClientConfiguration httpClientConfig = {httpVersion: config.httpVersion, timeout: config.timeout, forwarded: config.forwarded, poolConfig: config.poolConfig, compression: config.compression, circuitBreaker: config.circuitBreaker, retryConfig: config.retryConfig, validation: config.validation}; + do { + if config.http1Settings is ClientHttp1Settings { + ClientHttp1Settings settings = check config.http1Settings.ensureType(ClientHttp1Settings); + httpClientConfig.http1Settings = {...settings}; + } + if config.http2Settings is http:ClientHttp2Settings { + httpClientConfig.http2Settings = check config.http2Settings.ensureType(http:ClientHttp2Settings); + } + if config.cache is http:CacheConfig { + httpClientConfig.cache = check config.cache.ensureType(http:CacheConfig); + } + if config.responseLimits is http:ResponseLimitConfigs { + httpClientConfig.responseLimits = check config.responseLimits.ensureType(http:ResponseLimitConfigs); + } + if config.secureSocket is http:ClientSecureSocket { + httpClientConfig.secureSocket = check config.secureSocket.ensureType(http:ClientSecureSocket); + } + if config.proxy is http:ProxyConfig { + httpClientConfig.proxy = check config.proxy.ensureType(http:ProxyConfig); + } + } + if config.auth is ApiKeysConfig { + self.apiKeyConfig = (config.auth).cloneReadOnly(); + } else { + httpClientConfig.auth = config.auth; + self.apiKeyConfig = (); + } + http:Client httpEp = check new (serviceUrl, httpClientConfig); + self.clientEp = httpEp; + return; + } + + resource isolated function delete [string objectType](map headers = {}, *DeleteCrmObjectSchemasV3SchemasObjecttype_archiveQueries queries) returns http:Response|error { + string resourcePath = string `/${getEncodedUri(objectType)}`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + resourcePath = resourcePath + check getPathForQueryParam(queries); + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->delete(resourcePath, headers = httpHeaders); + } + + resource isolated function delete [string objectType]/associations/[string associationIdentifier](map headers = {}) returns http:Response|error { + string resourcePath = string `/${getEncodedUri(objectType)}/associations/${getEncodedUri(associationIdentifier)}`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->delete(resourcePath, headers = httpHeaders); + } + + resource isolated function get .(map headers = {}, *GetCrmObjectSchemasV3Schemas_getallQueries queries) returns CollectionResponseObjectSchemaNoPaging|error { + string resourcePath = string ``; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + resourcePath = resourcePath + check getPathForQueryParam(queries); + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->get(resourcePath, httpHeaders); + } + + resource isolated function get [string objectType](map headers = {}) returns ObjectSchema|error { + string resourcePath = string `/${getEncodedUri(objectType)}`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + map httpHeaders = getMapForHeaders(headerValues); + return self.clientEp->get(resourcePath, httpHeaders); + } + + resource isolated function patch [string objectType](ObjectTypeDefinitionPatch payload, map headers = {}) returns ObjectTypeDefinition|error { + string resourcePath = string `/${getEncodedUri(objectType)}`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->patch(resourcePath, request, httpHeaders); + } + + resource isolated function post .(ObjectSchemaEgg payload, map headers = {}) returns ObjectSchema|error { + string resourcePath = string ``; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } + + resource isolated function post [string objectType]/associations(AssociationDefinitionEgg payload, map headers = {}) returns AssociationDefinition|error { + string resourcePath = string `/${getEncodedUri(objectType)}/associations`; + map headerValues = {...headers}; + if self.apiKeyConfig is ApiKeysConfig { + headerValues["private-app-legacy"] = self.apiKeyConfig?.private\-app\-legacy; + } + map httpHeaders = getMapForHeaders(headerValues); + http:Request request = new; + json jsonBody = payload.toJson(); + request.setPayload(jsonBody, "application/json"); + return self.clientEp->post(resourcePath, request, httpHeaders); + } +} diff --git a/ballerina/tests/mock_service.bal b/ballerina/tests/mock_service.bal new file mode 100644 index 0000000..616c737 --- /dev/null +++ b/ballerina/tests/mock_service.bal @@ -0,0 +1,333 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + + +listener http:Listener httpListener = new (3000); + +http:Service mockService = service object { + + resource function post token() returns json { + return { + "access_token": "newAccessToken123456789", + "expires_in": 3600, + "refresh_token": "newRefreshToken987654321", + "token_type": "Bearer" + }; + } + + resource function get .() returns json { + return { + "results": [ + { + "associations": [ + { + "createdAt": "2025-01-10T06:20:44.626Z", + "fromObjectTypeId": "2-123456", + "name": "my_object_to_contact", + "id": "105", + "toObjectTypeId": "0-1", + "updatedAt": "2025-01-10T06:20:44.627Z" + } + ], + "secondaryDisplayProperties": ["string"], + "createdByUserId": 0, + "objectTypeId": "string", + "description": "string", + "updatedByUserId": 0, + "fullyQualifiedName": "p7878787_my_object", + "labels": { + "plural": "My objects", + "singular": "My object" + }, + "archived": true, + "createdAt": "2025-01-10T06:20:44.627Z", + "requiredProperties": ["my_object_property"], + "searchableProperties": ["my_object_property"], + "primaryDisplayProperty": "my_object_property", + "name": "my_object", + "id": "123456", + "properties": [ + { + "hidden": true, + "displayOrder": 2, + "description": "string", + "showCurrencySymbol": true, + "type": "enumeration", + "hubspotDefined": true, + "createdAt": "2025-01-10T06:20:44.627Z", + "archived": true, + "options": [ + { + "hidden": false, + "displayOrder": 1, + "description": "Choice number one", + "label": "Option A", + "value": "A" + } + ], + "hasUniqueValue": false, + "calculated": true, + "externalOptions": true, + "updatedAt": "2025-01-10T06:20:44.627Z", + "createdUserId": "string", + "modificationMetadata": { + "readOnlyOptions": true, + "readOnlyValue": true, + "readOnlyDefinition": true, + "archivable": true + }, + "sensitiveDataCategories": ["string"], + "label": "My object property", + "formField": true, + "dataSensitivity": "non_sensitive", + "archivedAt": "2025-01-10T06:20:44.627Z", + "groupName": "my_object_information", + "referencedObjectType": "string", + "name": "my_object_property", + "calculationFormula": "string", + "fieldType": "select", + "updatedUserId": "string" + } + ], + "updatedAt": "2025-01-10T06:20:44.627Z" + } + ] + }; + } + + resource function get [string testId]() returns json { + return { + "associations": [ + { + "createdAt": "2025-01-10T06:20:44.631Z", + "fromObjectTypeId": "2-123456", + "name": "my_object_to_contact", + "id": "105", + "toObjectTypeId": "0-1", + "updatedAt": "2025-01-10T06:20:44.631Z" + } + ], + "secondaryDisplayProperties": ["string"], + "createdByUserId": 0, + "objectTypeId": "string", + "description": "string", + "updatedByUserId": 0, + "fullyQualifiedName": "p7878787_my_object", + "labels": { + "plural": "My objects", + "singular": "My object" + }, + "archived": true, + "createdAt": "2025-01-10T06:20:44.631Z", + "requiredProperties": ["my_object_property"], + "searchableProperties": ["my_object_property"], + "primaryDisplayProperty": "my_object_property", + "name": "my_object", + "id": "123456", + "properties": [ + { + "hidden": true, + "displayOrder": 2, + "description": "string", + "showCurrencySymbol": true, + "type": "enumeration", + "hubspotDefined": true, + "createdAt": "2025-01-10T06:20:44.631Z", + "archived": true, + "options": [ + { + "hidden": false, + "displayOrder": 1, + "description": "Choice number one", + "label": "Option A", + "value": "A" + } + ], + "hasUniqueValue": false, + "calculated": true, + "externalOptions": true, + "updatedAt": "2025-01-10T06:20:44.631Z", + "createdUserId": "string", + "modificationMetadata": { + "readOnlyOptions": true, + "readOnlyValue": true, + "readOnlyDefinition": true, + "archivable": true + }, + "sensitiveDataCategories": ["string"], + "label": "My object property", + "formField": true, + "dataSensitivity": "non_sensitive", + "archivedAt": "2025-01-10T06:20:44.631Z", + "groupName": "my_object_information", + "referencedObjectType": "string", + "name": "my_object_property", + "calculationFormula": "string", + "fieldType": "select", + "updatedUserId": "string" + } + ], + "updatedAt": "2025-01-10T06:20:44.631Z" + }; + } + + resource function post .() returns json { + return { + "associations": [ + { + "createdAt": "2025-01-10T06:20:44.635Z", + "fromObjectTypeId": "2-123456", + "name": "my_object_to_contact", + "id": "105", + "toObjectTypeId": "0-1", + "updatedAt": "2025-01-10T06:20:44.635Z" + } + ], + "secondaryDisplayProperties": [ + "string" + ], + "createdByUserId": 0, + "objectTypeId": "string", + "description": "string", + "updatedByUserId": 0, + "fullyQualifiedName": "p7878787_my_object\"", + "labels": { + "plural": "My objects", + "singular": "My object" + }, + "archived": true, + "createdAt": "2025-01-10T06:20:44.635Z", + "requiredProperties": [ + "my_object_property" + ], + "searchableProperties": [ + "my_object_property" + ], + "primaryDisplayProperty": "my_object_property", + "name": "my_object", + "id": "123456", + "properties": [ + { + "hidden": true, + "displayOrder": 2, + "description": "string", + "showCurrencySymbol": true, + "type": "enumeration", + "hubspotDefined": true, + "createdAt": "2025-01-10T06:20:44.635Z", + "archived": true, + "options": [ + { + "hidden": false, + "displayOrder": 1, + "description": "Choice number one", + "label": "Option A", + "value": "A" + } + ], + "hasUniqueValue": false, + "calculated": true, + "externalOptions": true, + "updatedAt": "2025-01-10T06:20:44.635Z", + "createdUserId": "string", + "modificationMetadata": { + "readOnlyOptions": true, + "readOnlyValue": true, + "readOnlyDefinition": true, + "archivable": true + }, + "sensitiveDataCategories": [ + "string" + ], + "label": "My object property", + "formField": true, + "dataSensitivity": "non_sensitive", + "archivedAt": "2025-01-10T06:20:44.635Z", + "groupName": "my_object_information", + "referencedObjectType": "string", + "name": "my_object_property", + "calculationFormula": "string", + "fieldType": "select", + "updatedUserId": "string" + } + ], + "updatedAt": "2025-01-10T06:20:44.635Z" + }; + } + + resource function delete [string objId]() returns http:NoContent { + return http:NO_CONTENT; + } + + resource function patch [string objId]() returns json { + return { + "secondaryDisplayProperties": [ + "string" + ], + "objectTypeId": "string", + "description": "string", + "fullyQualifiedName": "string", + "labels": { + "plural": "My objects", + "singular": "My object" + }, + "archived": true, + "createdAt": "2025-01-10T06:20:44.641Z", + "requiredProperties": [ + "my_object_property" + ], + "searchableProperties": [ + "my_object_property" + ], + "portalId": 12345678, + "primaryDisplayProperty": "my_object_property", + "name": "my_object", + "id": "123456", + "updatedAt": "2025-01-10T06:20:44.641Z" + }; + } + + resource function post [string objId]/associations() returns json { + return { + "createdAt": "2025-01-10T06:20:44.638Z", + "fromObjectTypeId": "2-123456", + "name": "my_object_to_contact", + "id": "105", + "toObjectTypeId": "0-1", + "updatedAt": "2025-01-10T06:20:44.638Z" + }; + } + + resource function delete [string objId]/associations/[string assId]() returns http:NoContent { + return http:NO_CONTENT; + } + +}; + +function init() returns error? { + // if isLiveServer { + // log:printInfo("Skiping mock server initialization as the tests are running on live server"); + // return; + // } + + + check httpListener.attach(mockService, "/"); + check httpListener.'start(); +} + + diff --git a/ballerina/tests/tests.bal b/ballerina/tests/tests.bal new file mode 100644 index 0000000..2059107 --- /dev/null +++ b/ballerina/tests/tests.bal @@ -0,0 +1,169 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; +import ballerina/oauth2; +import ballerina/test; +import ballerina/io; + +configurable boolean isLive = false; +configurable string clientId = "testClientId"; +configurable string clientSecret = "testClientSecret"; +configurable string refreshToken = "testRefreshToken"; + +string serviceUrl = isLive ? "" : "http://127.0.0.1:3000"; + + +OAuth2RefreshTokenGrantConfig auth = { + clientId, + clientSecret, + refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER, + refreshUrl: isLive ? "https://api.hubapi.com/oauth/v1/token" : "http://127.0.0.1:3000/token" +}; + +// Global Client configuration for HTTP communication +ConnectionConfig config = { + httpVersion: http:HTTP_2_0, + timeout: 60, + auth: auth +}; + + + +// HubSpot CRM Client for interacting with HubSpot's Object Schemas API +final Client hpClient = check new Client(config, serviceUrl); + + +@test:Config { + groups: ["live_tests", "mock_tests"] +} +@test:BeforeGroups +function beforeGroups1() { + io:println("I'm the before groups function!"); +} + +// Test: Get Schema - Fetches a list of object schemas +@test:Config { + groups: ["live_tests", "mock_tests"] +} +isolated function testGetSchemas() returns error? { + // Make GET request to fetch schemas + CollectionResponseObjectSchemaNoPaging response = check hpClient->/.get(); + test:assertNotEquals(response.results, ()); +} + +// Test: Create Schema - Creates a new schema +@test:Config { + groups: ["live_tests", "mock_tests"] +} +isolated function testCreateSchema() returns error? { + // Define the payload for creating a new object schema + ObjectSchemaEgg payload = { + secondaryDisplayProperties: ["string"], + requiredProperties: ["my_object_property"], + searchableProperties: ["string"], + primaryDisplayProperty: "my_object_property", + name: "my_object", + description: "string", + associatedObjects: ["CONTACT"], + properties: [], + labels: { + plural: "My objects", + singular: "My object" + } + }; + + // Make POST request to create the schema + ObjectSchema response = check hpClient->/.post(payload); + test:assertNotEquals(response.associations, ()); +} + +// Test: Delete Schema - Deletes a specific schema by its ID +@test:Config { + groups: ["live_tests", "mock_tests"] +} +isolated function testDeleteSchema() returns error? { + // Define the object schema ID to delete + string objId = "testid"; + + // Make DELETE request to delete the schema + http:Response response = check hpClient->/[objId].delete(); + test:assertEquals(response.statusCode, 204); +} + +// Test: Update Schema - Updates an existing schema by ID +@test:Config { + groups: ["live_tests", "mock_tests"] +} + +isolated function testPatchSchema() returns error? { + // Define the payload for updating an object schema + ObjectTypeDefinitionPatch payload = { + secondaryDisplayProperties: ["string"], + requiredProperties: ["my_object_property"], + searchableProperties: ["string"], + primaryDisplayProperty: "my_object_property", + description: "string", + labels: { + plural: "My objects", + singular: "My object" + } + }; + + // Define the object schema ID to patch + string objId = "testid2"; + + // Make PATCH request to update the schema + ObjectTypeDefinition response = check hpClient->/[objId].patch(payload); + test:assertNotEquals(response.updatedAt, ()); + +} + +// Test: Create Schema - Creates a new assosiation +@test:Config { + groups: ["live_tests", "mock_tests"] +} +isolated function testCreateAssosiation() returns error? { + + // Define the object schema ID to patch + string objId = "testid2"; + + // Define the payload for creating a new object schema + AssociationDefinitionEgg payload = { + fromObjectTypeId: "2-123456", + name: "my_object_to_contact", + toObjectTypeId: "contact" + }; + + // Make POST request to create the schema + AssociationDefinition response = check hpClient->/[objId]/associations.post(payload); + test:assertNotEquals(response.id, ()); +} + +// Test: Delete assosiation - Deletes a specific assosiation by its ID +@test:Config { + groups: ["live_tests", "mock_tests"] +} +isolated function testDeleteAssosiation() returns error? { + // Define the object schema ID to delete + string objId = "testid"; + string assId = "testid"; + + // Make DELETE request to delete the schema + http:Response response = check hpClient->/[objId]/associations/[assId].delete(); + test:assertEquals(response.statusCode, 204); +} diff --git a/ballerina/types.bal b/ballerina/types.bal new file mode 100644 index 0000000..bfd0a87 --- /dev/null +++ b/ballerina/types.bal @@ -0,0 +1,351 @@ +// AUTO-GENERATED FILE. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/http; + +# Defines attributes to update on an object type. +public type ObjectTypeDefinitionPatch record { + # The names of secondary properties for this object. These will be displayed as secondary on the HubSpot record page for this object type. + string[] secondaryDisplayProperties?; + # The names of properties that should be **required** when creating an object of this type. + string[] requiredProperties?; + # Names of properties that will be indexed for this object type in by HubSpot's product search. + string[] searchableProperties?; + boolean clearDescription?; + # The name of the primary property for this object. This will be displayed as primary on the HubSpot record page for this object type. + string primaryDisplayProperty?; + string description?; + boolean restorable?; + ObjectTypeDefinitionLabels labels?; +}; + +public type CollectionResponseObjectSchemaNoPaging record { + ObjectSchema[] results; +}; + +# Defines an object type. +public type ObjectTypeDefinition record { + # The names of secondary properties for this object. These will be displayed as secondary on the HubSpot record page for this object type. + string[] secondaryDisplayProperties?; + string objectTypeId?; + string description?; + string fullyQualifiedName?; + ObjectTypeDefinitionLabels labels; + boolean archived?; + # When the object type was created. + string createdAt?; + # The names of properties that should be **required** when creating an object of this type. + string[] requiredProperties; + # Names of properties that will be indexed for this object type in by HubSpot's product search. + string[] searchableProperties?; + # The ID of the account that this object type is specific to. + int:Signed32 portalId?; + # The name of the primary property for this object. This will be displayed as primary on the HubSpot record page for this object type. + string primaryDisplayProperty?; + # A unique name for this object. For internal use only. + string name; + # A unique ID for this object type. Will be defined as {meta-type}-{unique ID}. + string id; + # When the object type was last updated. + string updatedAt?; +}; + +# Represents the Queries record for the operation: get-/crm-object-schemas/v3/schemas_getAll +public type GetCrmObjectSchemasV3Schemas_getallQueries record { + # Whether to return only results that have been archived. + boolean archived = false; +}; + +# Defines a enumeration property option +public type OptionInput record { + # Hidden options won't be shown in HubSpot. + boolean hidden; + # Options are shown in order starting with the lowest positive integer value. Values of -1 will cause the option to be displayed after any positive values. + int:Signed32 displayOrder; + # A description of the option. + string description?; + # A human-readable option label that will be shown in HubSpot. + string label; + # The internal value of the option, which must be used when setting the property value through the API. + string value; +}; + +# Represents the Queries record for the operation: delete-/crm-object-schemas/v3/schemas/{objectType}_archive +public type DeleteCrmObjectSchemasV3SchemasObjecttype_archiveQueries record { + # Whether to return only results that have been archived. + boolean archived = false; +}; + +# Proxy server configurations to be used with the HTTP client endpoint. +public type ProxyConfig record {| + # Host name of the proxy server + string host = ""; + # Proxy server port + int port = 0; + # Proxy server username + string userName = ""; + # Proxy server password + @display {label: "", kind: "password"} + string password = ""; +|}; + +# Defines a property to create. +public type ObjectTypePropertyCreate record { + boolean hidden?; + # Controls how the property options will be sorted in the HubSpot UI. + "DISPLAY_ORDER"|"ALPHABETICAL" optionSortStrategy?; + # The order that this property should be displayed in the HubSpot UI relative to other properties for this object type. Properties are displayed in order starting with the lowest positive integer value. A value of -1 will cause the property to be displayed **after** any positive values. + int:Signed32 displayOrder?; + # A description of the property that will be shown as help text in HubSpot. + string description?; + # Whether the property will display the currency symbol in the HubSpot UI. + boolean showCurrencySymbol?; + # A human-readable property label that will be shown in HubSpot. + string label; + # The data type of the property. + "string"|"number"|"date"|"datetime"|"enumeration"|"bool" 'type; + # Whether the property can be used in a HubSpot form. + boolean formField?; + # The name of the group this property belongs to. + string groupName?; + # Defines the options this property will return, e.g. OWNER would return name of users on the portal. + string referencedObjectType?; + # Controls how text properties are formatted in the HubSpot UI + "unformatted_single_line"|"multi_line"|"email"|"phone_number"|"domain_name"|"ip_address"|"physical_address"|"postal_code" textDisplayHint?; + # The internal property name, which must be used when referencing the property from the API. + string name; + # A list of available options for the property. This field is only required for enumerated properties. + OptionInput[] options?; + # Allow users to search for information entered to this field (limited to 3 properties) + boolean searchableInGlobalSearch?; + # Controls how numeric properties are formatted in the HubSpot UI + "unformatted"|"formatted"|"currency"|"percentage"|"duration"|"probability" numberDisplayHint?; + # Whether or not the property's value must be unique. Once set, this can't be changed. + boolean hasUniqueValue?; + # Controls how the property appears in HubSpot. + string fieldType; +}; + +public type PropertyModificationMetadata record { + # + boolean readOnlyOptions?; + # + boolean readOnlyValue; + # + boolean readOnlyDefinition; + # + boolean archivable; +}; + +# Defines an association between two object types. +public type AssociationDefinitionEgg record { + # ID of the primary object type to link from. + string fromObjectTypeId; + # A unique name for this association. + string name?; + # ID of the target object type to link to. + string toObjectTypeId; +}; + +# Defines an association between two object types. +public type AssociationDefinition record { + # When the association was defined. + string createdAt?; + # ID of the primary object type to link from. + string fromObjectTypeId; + # A unique name for this association. + string name?; + # A unique ID for this association. + string id; + # ID of the target object type to link to. + string toObjectTypeId; + # When the association was last updated. + string updatedAt?; +}; + +# Defines an object schema, including its properties and associations. +public type ObjectSchema record { + # Associations defined for a given object type. + AssociationDefinition[] associations; + # The names of secondary properties for this object. These will be displayed as secondary on the HubSpot record page for this object type. + string[] secondaryDisplayProperties?; + int:Signed32 createdByUserId?; + string objectTypeId?; + string description?; + int:Signed32 updatedByUserId?; + # An assigned unique ID for the object, including portal ID and object name. + string fullyQualifiedName?; + ObjectTypeDefinitionLabels labels; + boolean archived?; + # When the object schema was created. + string createdAt?; + # The names of properties that should be **required** when creating an object of this type. + string[] requiredProperties; + # Names of properties that will be indexed for this object type in by HubSpot's product search. + string[] searchableProperties?; + # The name of the primary property for this object. This will be displayed as primary on the HubSpot record page for this object type. + string primaryDisplayProperty?; + # A unique name for the schema's object type. + string name; + # A unique ID for this schema's object type. Will be defined as {meta-type}-{unique ID}. + string id; + # Properties defined for this object type. + Property[] properties; + # When the object schema was last updated. + string updatedAt?; +}; + +# Provides settings related to HTTP/1.x protocol. +public type ClientHttp1Settings record {| + # Specifies whether to reuse a connection for multiple requests + http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; + # The chunking behaviour of the request + http:Chunking chunking = http:CHUNKING_AUTO; + # Proxy server related options + ProxyConfig proxy?; +|}; + +public type AnydataDefault record {| + *http:DefaultStatusCodeResponse; + anydata body; +|}; + +# OAuth2 Refresh Token Grant Configs +public type OAuth2RefreshTokenGrantConfig record {| + *http:OAuth2RefreshTokenGrantConfig; + # Refresh URL + string refreshUrl = "https://api.hubapi.com/oauth/v1/token"; +|}; + +# Singular and plural labels for the object. Used in CRM display. +public type ObjectTypeDefinitionLabels record { + # The word for multiple objects. (There’s no way to change this later.) + string plural?; + # The word for one object. (There’s no way to change this later.) + string singular?; +}; + +# Defines a new object type, its properties, and associations. +public type ObjectSchemaEgg record { + # The names of secondary properties for this object. These will be displayed as secondary on the HubSpot record page for this object type. + string[] secondaryDisplayProperties?; + # The names of properties that should be **required** when creating an object of this type. + string[] requiredProperties; + # Names of properties that will be indexed for this object type in by HubSpot's product search. + string[] searchableProperties?; + # The name of the primary property for this object. This will be displayed as primary on the HubSpot record page for this object type. + string primaryDisplayProperty?; + # A unique name for this object. For internal use only. + string name; + string description?; + # Associations defined for this object type. + string[] associatedObjects; + # Properties defined for this object type. + ObjectTypePropertyCreate[] properties; + ObjectTypeDefinitionLabels labels; +}; + +# The options available when a property is an enumeration +public type Option record { + # Hidden options will not be displayed in HubSpot. + boolean hidden; + # Options are displayed in order starting with the lowest positive integer value. Values of -1 will cause the option to be displayed after any positive values. + int:Signed32 displayOrder?; + # A description of the option. + string description?; + # A human-readable option label that will be shown in HubSpot. + string label; + # The internal value of the option, which must be used when setting the property value through the API. + string value; +}; + +# Defines a property +public type Property record { + boolean hidden?; + # The order that this property should be displayed in the HubSpot UI relative to other properties for this object type. Properties are displayed in order starting with the lowest positive integer value. A value of -1 will cause the property to be displayed **after** any positive values. + int:Signed32 displayOrder?; + # A description of the property that will be shown as help text in HubSpot. + string description; + # Whether the property will display the currency symbol set in the account settings. + boolean showCurrencySymbol?; + # The property data type. + string 'type; + # This will be true for default object properties built into HubSpot. + boolean hubspotDefined?; + # When the property was created + string createdAt?; + # Whether or not the property is archived. + boolean archived?; + # A list of valid options for the property. This field is required for enumerated properties, but will be empty for other property types. + Option[] options; + # Whether or not the property's value must be unique. Once set, this can't be changed. + boolean hasUniqueValue?; + # For default properties, true indicates that the property is calculated by a HubSpot process. It has no effect for custom properties. + boolean calculated?; + # For default properties, true indicates that the options are stored externally to the property settings. + boolean externalOptions?; + # + string updatedAt?; + # The internal ID of the user who created the property in HubSpot. This field may not exist if the property was created outside of HubSpot. + string createdUserId?; + PropertyModificationMetadata modificationMetadata?; + string[] sensitiveDataCategories?; + # A human-readable property label that will be shown in HubSpot. + string label; + # Whether or not the property can be used in a HubSpot form. + boolean formField?; + "non_sensitive"|"sensitive"|"highly_sensitive" dataSensitivity?; + # When the property was archived. + string archivedAt?; + # The name of the property group the property belongs to. + string groupName; + # If this property is related to other object(s), they'll be listed here. + string referencedObjectType?; + # The internal property name, which must be used when referencing the property via the API. + string name; + string calculationFormula?; + # Controls how the property appears in HubSpot. + string fieldType; + # The internal user ID of the user who updated the property in HubSpot. This field may not exist if the property was updated outside of HubSpot. + string updatedUserId?; +}; + +# Provides API key configurations needed when communicating with a remote HTTP endpoint. +public type ApiKeysConfig record {| + string private\-app\-legacy; +|}; + +# Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. +@display {label: "Connection Config"} +public type ConnectionConfig record {| + # Provides Auth configurations needed when communicating with a remote HTTP endpoint. + http:BearerTokenConfig|OAuth2RefreshTokenGrantConfig|ApiKeysConfig auth; + # The HTTP version understood by the client + http:HttpVersion httpVersion = http:HTTP_2_0; + # Configurations related to HTTP/1.x protocol + ClientHttp1Settings http1Settings?; + # Configurations related to HTTP/2 protocol + http:ClientHttp2Settings http2Settings?; + # The maximum time to wait (in seconds) for a response before closing the connection + decimal timeout = 60; + # The choice of setting `forwarded`/`x-forwarded` header + string forwarded = "disable"; + # Configurations associated with request pooling + http:PoolConfiguration poolConfig?; + # HTTP caching related configurations + http:CacheConfig cache?; + # Specifies the way of handling compression (`accept-encoding`) header + http:Compression compression = http:COMPRESSION_AUTO; + # Configurations associated with the behaviour of the Circuit Breaker + http:CircuitBreakerConfig circuitBreaker?; + # Configurations associated with retrying + http:RetryConfig retryConfig?; + # Configurations associated with inbound response size limits + http:ResponseLimitConfigs responseLimits?; + # SSL/TLS-related options + http:ClientSecureSocket secureSocket?; + # Proxy server related options + http:ProxyConfig proxy?; + # Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default + boolean validation = true; +|}; diff --git a/ballerina/utils.bal b/ballerina/utils.bal new file mode 100644 index 0000000..db366d6 --- /dev/null +++ b/ballerina/utils.bal @@ -0,0 +1,218 @@ +// AUTO-GENERATED FILE. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/url; + +type SimpleBasicType string|boolean|int|float|decimal; + +# Represents encoding mechanism details. +type Encoding record { + # Defines how multiple values are delimited + string style = FORM; + # Specifies whether arrays and objects should generate as separate fields + boolean explode = true; + # Specifies the custom content type + string contentType?; + # Specifies the custom headers + map headers?; +}; + +enum EncodingStyle { + DEEPOBJECT, FORM, SPACEDELIMITED, PIPEDELIMITED +} + +final Encoding & readonly defaultEncoding = {}; + +# Serialize the record according to the deepObject style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + return - Serialized record as a string +isolated function getDeepObjectStyleRequest(string parent, record {} anyRecord) returns string { + string[] recordArray = []; + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(parent + "[" + key + "]" + "=" + getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(parent + "[" + key + "]" + "[]", value, DEEPOBJECT, true)); + } else if value is record {} { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getDeepObjectStyleRequest(nextParent, value)); + } else if value is record {}[] { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getSerializedRecordArray(nextParent, value, DEEPOBJECT)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + return string:'join("", ...recordArray); +} + +# Serialize the record according to the form style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getFormStyleRequest(string parent, record {} anyRecord, boolean explode = true) returns string { + string[] recordArray = []; + if explode { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = explode)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + } else { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, ",", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = false)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push(","); + } + _ = recordArray.pop(); + } + return string:'join("", ...recordArray); +} + +# Serialize arrays. +# +# + arrayName - Name of the field with arrays +# + anyArray - Array to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized array as a string +isolated function getSerializedArray(string arrayName, anydata[] anyArray, string style = "form", boolean explode = true) returns string { + string key = arrayName; + string[] arrayValues = []; + if anyArray.length() > 0 { + if style == FORM && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), ","); + } + } else if style == SPACEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "%20"); + } + } else if style == PIPEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "|"); + } + } else if style == DEEPOBJECT { + foreach anydata i in anyArray { + arrayValues.push(key, "[]", "=", getEncodedUri(i.toString()), "&"); + } + } else { + foreach anydata i in anyArray { + arrayValues.push(key, "=", getEncodedUri(i.toString()), "&"); + } + } + _ = arrayValues.pop(); + } + return string:'join("", ...arrayValues); +} + +# Serialize the array of records according to the form style. +# +# + parent - Parent record name +# + value - Array of records to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getSerializedRecordArray(string parent, record {}[] value, string style = FORM, boolean explode = true) returns string { + string[] serializedArray = []; + if style == DEEPOBJECT { + int arayIndex = 0; + foreach var recordItem in value { + serializedArray.push(getDeepObjectStyleRequest(parent + "[" + arayIndex.toString() + "]", recordItem), "&"); + arayIndex = arayIndex + 1; + } + } else { + if !explode { + serializedArray.push(parent, "="); + } + foreach var recordItem in value { + serializedArray.push(getFormStyleRequest(parent, recordItem, explode), ","); + } + } + _ = serializedArray.pop(); + return string:'join("", ...serializedArray); +} + +# Get Encoded URI for a given value. +# +# + value - Value to be encoded +# + return - Encoded string +isolated function getEncodedUri(anydata value) returns string { + string|error encoded = url:encode(value.toString(), "UTF8"); + if encoded is string { + return encoded; + } else { + return value.toString(); + } +} + +# Generate query path with query parameter. +# +# + queryParam - Query parameter map +# + encodingMap - Details on serialization mechanism +# + return - Returns generated Path or error at failure of client initialization +isolated function getPathForQueryParam(map queryParam, map encodingMap = {}) returns string|error { + string[] param = []; + if queryParam.length() > 0 { + param.push("?"); + foreach var [key, value] in queryParam.entries() { + if value is () { + _ = queryParam.remove(key); + continue; + } + Encoding encodingData = encodingMap.hasKey(key) ? encodingMap.get(key) : defaultEncoding; + if value is SimpleBasicType { + param.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + param.push(getSerializedArray(key, value, encodingData.style, encodingData.explode)); + } else if value is record {} { + if encodingData.style == DEEPOBJECT { + param.push(getDeepObjectStyleRequest(key, value)); + } else { + param.push(getFormStyleRequest(key, value, encodingData.explode)); + } + } else { + param.push(key, "=", value.toString()); + } + param.push("&"); + } + _ = param.pop(); + } + string restOfPath = string:'join("", ...param); + return restOfPath; +} + +# Generate header map for given header values. +# +# + headerParam - Headers map +# + return - Returns generated map or error at failure of client initialization +isolated function getMapForHeaders(map headerParam) returns map { + map headerMap = {}; + foreach var [key, value] in headerParam.entries() { + if value is SimpleBasicType[] { + headerMap[key] = from SimpleBasicType data in value + select data.toString(); + } else { + headerMap[key] = value.toString(); + } + } + return headerMap; +} diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml index a4d323b..32e44cd 100644 --- a/build-config/resources/Ballerina.toml +++ b/build-config/resources/Ballerina.toml @@ -1,16 +1,15 @@ [package] distribution = "2201.10.0" org = "ballerinax" -name = "hubspot.crm.object.schemas" +name = "hubspot.crm.obj.schemas" version = "@toml.version@" license = ["Apache-2.0"] authors = ["Ballerina"] -keywords = [] # TODO: Add keywords -# icon = "icon.png" # TODO: Add icon +keywords = ["schema", "hubspot", "object"] repository = "https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas" [build-options] observabilityIncluded = true -[platform.java21] +[platform.java17] graalvmCompatible = true diff --git a/build.gradle b/build.gradle index 5ff8924..01a1502 100644 --- a/build.gradle +++ b/build.gradle @@ -58,7 +58,7 @@ allprojects { def moduleVersion = project.version.replace("-SNAPSHOT", "") task build { - dependsOn(':hubspot.crm.object.schemas-ballerina:build') + dependsOn(':hubspot.crm.obj.schemas-ballerina:build') } release { diff --git a/docs/setup/resources/app_img1.png b/docs/setup/resources/app_img1.png new file mode 100644 index 0000000..11360c6 Binary files /dev/null and b/docs/setup/resources/app_img1.png differ diff --git a/docs/setup/resources/auth.png b/docs/setup/resources/auth.png new file mode 100644 index 0000000..4fe55bf Binary files /dev/null and b/docs/setup/resources/auth.png differ diff --git a/docs/setup/resources/credentials.png b/docs/setup/resources/credentials.png new file mode 100644 index 0000000..c406519 Binary files /dev/null and b/docs/setup/resources/credentials.png differ diff --git a/docs/setup/resources/redirect_url.png b/docs/setup/resources/redirect_url.png new file mode 100644 index 0000000..0617552 Binary files /dev/null and b/docs/setup/resources/redirect_url.png differ diff --git a/docs/setup/resources/scope_select.png b/docs/setup/resources/scope_select.png new file mode 100644 index 0000000..d1f58f7 Binary files /dev/null and b/docs/setup/resources/scope_select.png differ diff --git a/docs/setup/resources/test_acc_img1.png b/docs/setup/resources/test_acc_img1.png new file mode 100644 index 0000000..b2662e6 Binary files /dev/null and b/docs/setup/resources/test_acc_img1.png differ diff --git a/docs/setup/resources/test_acc_img2.png b/docs/setup/resources/test_acc_img2.png new file mode 100644 index 0000000..badba58 Binary files /dev/null and b/docs/setup/resources/test_acc_img2.png differ diff --git a/docs/setup/resources/test_acc_img3.png b/docs/setup/resources/test_acc_img3.png new file mode 100644 index 0000000..828d7cd Binary files /dev/null and b/docs/setup/resources/test_acc_img3.png differ diff --git a/docs/spec/openapi.json b/docs/spec/openapi.json new file mode 100644 index 0000000..7341fde --- /dev/null +++ b/docs/spec/openapi.json @@ -0,0 +1,1334 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "Schemas", + "description" : "The CRM uses schemas to define how custom objects should store and represent information in the HubSpot CRM. Schemas define details about an object's type, properties, and associations. The schema can be uniquely identified by its **object type ID**.", + "version" : "v3", + "x-hubspot-product-tier-requirements" : { + "marketing" : "ENTERPRISE", + "sales" : "ENTERPRISE", + "service" : "ENTERPRISE", + "cms" : "ENTERPRISE" + }, + "x-hubspot-documentation-banner" : "NONE", + "x-hubspot-api-use-case" : "Create a new object for storing information about cars at a car dealership. Your object definition could include properties to store information as well as the filters available on the custom object index page.", + "x-hubspot-related-documentation" : [ { + "name" : "Custom Object Guide", + "url" : "https://developers.hubspot.com/beta-docs/guides/api/crm/objects/crm-custom-objects" + } ], + "x-hubspot-introduction" : "Use the custom objects schema API to define new types of CRM records in your account. After setting up an object schema, you'll then be able to create custom records under that object both in HubSpot and using the custom object records API." + }, + "servers" : [ { + "url" : "https://api.hubapi.com" + } ], + "tags" : [ { + "name" : "Core" + } ], + "paths" : { + "/crm-object-schemas/v3/schemas" : { + "get" : { + "tags" : [ "Core" ], + "summary" : "Get all schemas", + "description" : "Returns all object schemas that have been defined for your account.", + "operationId" : "get-/crm-object-schemas/v3/schemas_getAll", + "parameters" : [ { + "name" : "archived", + "in" : "query", + "description" : "Whether to return only results that have been archived.", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/CollectionResponseObjectSchemaNoPaging" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "private_apps_legacy" : [ "crm.objects.custom.read" ] + }, { + "private_apps_legacy" : [ "crm.schemas.custom.read" ] + }, { + "oauth2_legacy" : [ "crm.objects.custom.read" ] + }, { + "oauth2_legacy" : [ "crm.schemas.custom.read" ] + } ] + }, + "post" : { + "tags" : [ "Core" ], + "summary" : "Create a new schema", + "description" : "Define a new object schema, along with custom properties and associations. The entire object schema, including its object type ID, properties, and associations will be returned in the response.", + "operationId" : "post-/crm-object-schemas/v3/schemas_create", + "parameters" : [ ], + "requestBody" : { + "description" : "Object schema definition, including properties and associations.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ObjectSchemaEgg" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ObjectSchema" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "private_apps_legacy" : [ "crm.schemas.custom.write" ] + } ] + } + }, + "/crm-object-schemas/v3/schemas/{objectType}" : { + "get" : { + "tags" : [ "Core" ], + "summary" : "Get an existing schema", + "description" : "Returns an existing object schema.", + "operationId" : "get-/crm-object-schemas/v3/schemas/{objectType}_getById", + "parameters" : [ { + "name" : "objectType", + "in" : "path", + "description" : "Fully qualified name or object type ID of your schema.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ObjectSchema" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "private_apps_legacy" : [ "crm.objects.custom.read" ] + }, { + "private_apps_legacy" : [ "crm.schemas.custom.read" ] + }, { + "oauth2_legacy" : [ "crm.objects.custom.read" ] + }, { + "oauth2_legacy" : [ "crm.schemas.custom.read" ] + } ] + }, + "delete" : { + "tags" : [ "Core" ], + "summary" : "Delete a schema", + "description" : "Deletes a schema. Any existing records of this schema must be deleted **first**. Otherwise this call will fail.", + "operationId" : "delete-/crm-object-schemas/v3/schemas/{objectType}_archive", + "parameters" : [ { + "name" : "objectType", + "in" : "path", + "description" : "Fully qualified name or object type ID of your schema.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "archived", + "in" : "query", + "description" : "Whether to return only results that have been archived.", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "boolean", + "default" : false + } + } ], + "responses" : { + "204" : { + "description" : "No content", + "content" : { } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "private_apps_legacy" : [ "crm.schemas.custom.write" ] + } ] + }, + "patch" : { + "tags" : [ "Core" ], + "summary" : "Update a schema", + "description" : "Update the details for an existing object schema.", + "operationId" : "patch-/crm-object-schemas/v3/schemas/{objectType}_update", + "parameters" : [ { + "name" : "objectType", + "in" : "path", + "description" : "Fully qualified name or object type ID of your schema.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Attributes to update in your schema.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ObjectTypeDefinitionPatch" + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ObjectTypeDefinition" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "private_apps_legacy" : [ "crm.schemas.custom.write" ] + } ] + } + }, + "/crm-object-schemas/v3/schemas/{objectType}/associations" : { + "post" : { + "tags" : [ "Core" ], + "summary" : "Create an association", + "description" : "Defines a new association between the primary schema's object type and other object types.", + "operationId" : "post-/crm-object-schemas/v3/schemas/{objectType}/associations_createAssociation", + "parameters" : [ { + "name" : "objectType", + "in" : "path", + "description" : "Fully qualified name or object type ID of your schema.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "requestBody" : { + "description" : "Attributes that define the association.", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssociationDefinitionEgg" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "successful operation", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AssociationDefinition" + } + } + } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "private_apps_legacy" : [ "crm.schemas.custom.write" ] + } ] + } + }, + "/crm-object-schemas/v3/schemas/{objectType}/associations/{associationIdentifier}" : { + "delete" : { + "tags" : [ "Core" ], + "summary" : "Remove an association", + "description" : "Removes an existing association from a schema.", + "operationId" : "delete-/crm-object-schemas/v3/schemas/{objectType}/associations/{associationIdentifier}_archiveAssociation", + "parameters" : [ { + "name" : "objectType", + "in" : "path", + "description" : "Fully qualified name or object type ID of your schema.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + }, { + "name" : "associationIdentifier", + "in" : "path", + "description" : "Unique ID of the association to remove.", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "204" : { + "description" : "No content", + "content" : { } + }, + "default" : { + "$ref" : "#/components/responses/Error" + } + }, + "security" : [ { + "private_apps_legacy" : [ "crm.schemas.custom.write" ] + } ] + } + } + }, + "components" : { + "schemas" : { + "ObjectTypeDefinitionPatch" : { + "type" : "object", + "properties" : { + "secondaryDisplayProperties" : { + "type" : "array", + "description" : "The names of secondary properties for this object. These will be displayed as secondary on the HubSpot record page for this object type.", + "items" : { + "type" : "string" + } + }, + "requiredProperties" : { + "type" : "array", + "description" : "The names of properties that should be **required** when creating an object of this type.", + "example" : [ "my_object_property" ], + "items" : { + "type" : "string" + } + }, + "searchableProperties" : { + "type" : "array", + "description" : "Names of properties that will be indexed for this object type in by HubSpot's product search.", + "example" : [ "my_object_property" ], + "items" : { + "type" : "string" + } + }, + "clearDescription" : { + "type" : "boolean" + }, + "primaryDisplayProperty" : { + "type" : "string", + "description" : "The name of the primary property for this object. This will be displayed as primary on the HubSpot record page for this object type.", + "example" : "my_object_property" + }, + "description" : { + "type" : "string" + }, + "restorable" : { + "type" : "boolean" + }, + "labels" : { + "$ref" : "#/components/schemas/ObjectTypeDefinitionLabels" + } + }, + "description" : "Defines attributes to update on an object type.", + "example" : { + "primaryDisplayProperty" : "my_object_property", + "requiredProperties" : [ "my_object_property" ], + "searchableProperties" : [ "my_object_property" ] + } + }, + "CollectionResponseObjectSchemaNoPaging" : { + "required" : [ "results" ], + "type" : "object", + "properties" : { + "results" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/ObjectSchema" + } + } + } + }, + "ObjectTypeDefinition" : { + "required" : [ "id", "labels", "name", "requiredProperties" ], + "type" : "object", + "properties" : { + "secondaryDisplayProperties" : { + "type" : "array", + "description" : "The names of secondary properties for this object. These will be displayed as secondary on the HubSpot record page for this object type.", + "items" : { + "type" : "string" + } + }, + "objectTypeId" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "fullyQualifiedName" : { + "type" : "string" + }, + "labels" : { + "$ref" : "#/components/schemas/ObjectTypeDefinitionLabels" + }, + "archived" : { + "type" : "boolean" + }, + "createdAt" : { + "type" : "string", + "description" : "When the object type was created.", + "format" : "date-time", + "example" : "2020-02-20T18:07:11.390+00:00" + }, + "requiredProperties" : { + "type" : "array", + "description" : "The names of properties that should be **required** when creating an object of this type.", + "example" : [ "my_object_property" ], + "items" : { + "type" : "string" + } + }, + "searchableProperties" : { + "type" : "array", + "description" : "Names of properties that will be indexed for this object type in by HubSpot's product search.", + "example" : [ "my_object_property" ], + "items" : { + "type" : "string" + } + }, + "portalId" : { + "type" : "integer", + "description" : "The ID of the account that this object type is specific to.", + "format" : "int32", + "example" : 12345678 + }, + "primaryDisplayProperty" : { + "type" : "string", + "description" : "The name of the primary property for this object. This will be displayed as primary on the HubSpot record page for this object type.", + "example" : "my_object_property" + }, + "name" : { + "type" : "string", + "description" : "A unique name for this object. For internal use only.", + "example" : "my_object" + }, + "id" : { + "type" : "string", + "description" : "A unique ID for this object type. Will be defined as {meta-type}-{unique ID}.", + "example" : "123456" + }, + "updatedAt" : { + "type" : "string", + "description" : "When the object type was last updated.", + "format" : "date-time", + "example" : "2020-02-21T14:13:28.818+00:00" + } + }, + "description" : "Defines an object type.", + "example" : { + "id" : "123456", + "createdAt" : "2020-02-20T18:07:11.390Z", + "updatedAt" : "2020-02-21T14:13:28.818002Z", + "labels" : { + "singular" : "My object", + "plural" : "My objects" + }, + "requiredProperties" : [ "my_object_property" ], + "searchableProperties" : [ "my_object_property" ], + "primaryDisplayProperty" : "my_object_property", + "portalId" : 12345678, + "metaType" : "PORTAL_SPECIFIC", + "name" : "my_object" + } + }, + "OptionInput" : { + "required" : [ "displayOrder", "hidden", "label", "value" ], + "type" : "object", + "properties" : { + "hidden" : { + "type" : "boolean", + "description" : "Hidden options won't be shown in HubSpot.", + "example" : false + }, + "displayOrder" : { + "type" : "integer", + "description" : "Options are shown in order starting with the lowest positive integer value. Values of -1 will cause the option to be displayed after any positive values.", + "format" : "int32", + "example" : 1 + }, + "description" : { + "type" : "string", + "description" : "A description of the option.", + "example" : "Choice number one" + }, + "label" : { + "type" : "string", + "description" : "A human-readable option label that will be shown in HubSpot.", + "example" : "Option A" + }, + "value" : { + "type" : "string", + "description" : "The internal value of the option, which must be used when setting the property value through the API.", + "example" : "A" + } + }, + "description" : "Defines a enumeration property option", + "example" : { + "label" : "Option A", + "description" : "Choice number one", + "value" : "A", + "displayOrder" : 1, + "hidden" : false + } + }, + "Error" : { + "required" : [ "category", "correlationId", "message" ], + "type" : "object", + "properties" : { + "subCategory" : { + "type" : "string", + "description" : "A specific category that contains more specific detail about the error" + }, + "context" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : "Context about the error condition", + "example" : { + "missingScopes" : [ "scope1", "scope2" ], + "invalidPropertyName" : [ "propertyValue" ] + } + }, + "correlationId" : { + "type" : "string", + "description" : "A unique identifier for the request. Include this value with any error reports or support tickets", + "format" : "uuid", + "example" : "aeb5f871-7f07-4993-9211-075dc63e7cbf" + }, + "links" : { + "type" : "object", + "additionalProperties" : { + "type" : "string" + }, + "description" : "A map of link names to associated URIs containing documentation about the error or recommended remediation steps", + "example" : { + "knowledge-base" : "https://www.hubspot.com/products/service/knowledge-base" + } + }, + "message" : { + "type" : "string", + "description" : "A human readable message describing the error along with remediation steps where appropriate", + "example" : "Invalid input (details will vary based on the error)" + }, + "category" : { + "type" : "string", + "description" : "The error category", + "example" : "VALIDATION_ERROR" + }, + "errors" : { + "type" : "array", + "description" : "further information about the error", + "items" : { + "$ref" : "#/components/schemas/ErrorDetail" + } + } + }, + "example" : { + "message" : "Invalid input (details will vary based on the error)", + "correlationId" : "aeb5f871-7f07-4993-9211-075dc63e7cbf", + "category" : "VALIDATION_ERROR", + "links" : { + "knowledge-base" : "https://www.hubspot.com/products/service/knowledge-base" + } + } + }, + "ErrorDetail" : { + "required" : [ "message" ], + "type" : "object", + "properties" : { + "subCategory" : { + "type" : "string", + "description" : "A specific category that contains more specific detail about the error" + }, + "code" : { + "type" : "string", + "description" : "The status code associated with the error detail" + }, + "in" : { + "type" : "string", + "description" : "The name of the field or parameter in which the error was found." + }, + "context" : { + "type" : "object", + "additionalProperties" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "description" : "Context about the error condition", + "example" : { + "missingScopes" : [ "scope1", "scope2" ] + } + }, + "message" : { + "type" : "string", + "description" : "A human readable message describing the error along with remediation steps where appropriate" + } + } + }, + "ObjectTypePropertyCreate" : { + "required" : [ "fieldType", "label", "name", "type" ], + "type" : "object", + "properties" : { + "hidden" : { + "type" : "boolean" + }, + "optionSortStrategy" : { + "type" : "string", + "description" : "Controls how the property options will be sorted in the HubSpot UI.", + "enum" : [ "DISPLAY_ORDER", "ALPHABETICAL" ] + }, + "displayOrder" : { + "type" : "integer", + "description" : "The order that this property should be displayed in the HubSpot UI relative to other properties for this object type. Properties are displayed in order starting with the lowest positive integer value. A value of -1 will cause the property to be displayed **after** any positive values.", + "format" : "int32", + "example" : 2 + }, + "description" : { + "type" : "string", + "description" : "A description of the property that will be shown as help text in HubSpot." + }, + "showCurrencySymbol" : { + "type" : "boolean", + "description" : "Whether the property will display the currency symbol in the HubSpot UI." + }, + "label" : { + "type" : "string", + "description" : "A human-readable property label that will be shown in HubSpot.", + "example" : "My object property" + }, + "type" : { + "type" : "string", + "description" : "The data type of the property.", + "example" : "enumeration", + "enum" : [ "string", "number", "date", "datetime", "enumeration", "bool" ] + }, + "formField" : { + "type" : "boolean", + "description" : "Whether the property can be used in a HubSpot form." + }, + "groupName" : { + "type" : "string", + "description" : "The name of the group this property belongs to.", + "example" : "my_object_information" + }, + "referencedObjectType" : { + "type" : "string", + "description" : "Defines the options this property will return, e.g. OWNER would return name of users on the portal." + }, + "textDisplayHint" : { + "type" : "string", + "description" : "Controls how text properties are formatted in the HubSpot UI", + "enum" : [ "unformatted_single_line", "multi_line", "email", "phone_number", "domain_name", "ip_address", "physical_address", "postal_code" ] + }, + "name" : { + "type" : "string", + "description" : "The internal property name, which must be used when referencing the property from the API." + }, + "options" : { + "type" : "array", + "description" : "A list of available options for the property. This field is only required for enumerated properties.", + "example" : [ { + "label" : "Option A", + "value" : "A", + "description" : "Choice number one", + "displayOrder" : 1 + }, { + "label" : "Option B", + "value" : "B", + "description" : "Choice number two", + "displayOrder" : 2 + } ], + "items" : { + "$ref" : "#/components/schemas/OptionInput" + } + }, + "searchableInGlobalSearch" : { + "type" : "boolean", + "description" : "Allow users to search for information entered to this field (limited to 3 properties)" + }, + "numberDisplayHint" : { + "type" : "string", + "description" : "Controls how numeric properties are formatted in the HubSpot UI", + "enum" : [ "unformatted", "formatted", "currency", "percentage", "duration", "probability" ] + }, + "hasUniqueValue" : { + "type" : "boolean", + "description" : "Whether or not the property's value must be unique. Once set, this can't be changed.", + "example" : false + }, + "fieldType" : { + "type" : "string", + "description" : "Controls how the property appears in HubSpot.", + "example" : "select" + } + }, + "description" : "Defines a property to create.", + "example" : { + "label" : "My object property", + "type" : "enumeration", + "fieldType" : "select", + "groupName" : "my_object_information", + "displayOrder" : 2, + "hasUniqueValue" : false, + "options" : [ { + "label" : "Option A", + "description" : "Choice number one", + "value" : "A", + "displayOrder" : 1 + }, { + "label" : "Option B", + "description" : "Choice number two", + "value" : "B", + "displayOrder" : 2 + } ] + } + }, + "PropertyModificationMetadata" : { + "required" : [ "archivable", "readOnlyDefinition", "readOnlyValue" ], + "type" : "object", + "properties" : { + "readOnlyOptions" : { + "type" : "boolean", + "description" : "" + }, + "readOnlyValue" : { + "type" : "boolean", + "description" : "" + }, + "readOnlyDefinition" : { + "type" : "boolean", + "description" : "" + }, + "archivable" : { + "type" : "boolean", + "description" : "" + } + } + }, + "AssociationDefinitionEgg" : { + "required" : [ "fromObjectTypeId", "toObjectTypeId" ], + "type" : "object", + "properties" : { + "fromObjectTypeId" : { + "type" : "string", + "description" : "ID of the primary object type to link from.", + "example" : "2-123456" + }, + "name" : { + "type" : "string", + "description" : "A unique name for this association.", + "example" : "my_object_to_contact" + }, + "toObjectTypeId" : { + "type" : "string", + "description" : "ID of the target object type to link to.", + "example" : "contact" + } + }, + "description" : "Defines an association between two object types.", + "example" : { + "fromObjectTypeId" : "2-123456", + "toObjectTypeId" : "contact", + "name" : "my_object_to_contact" + } + }, + "AssociationDefinition" : { + "required" : [ "fromObjectTypeId", "id", "toObjectTypeId" ], + "type" : "object", + "properties" : { + "createdAt" : { + "type" : "string", + "description" : "When the association was defined.", + "format" : "date-time" + }, + "fromObjectTypeId" : { + "type" : "string", + "description" : "ID of the primary object type to link from.", + "example" : "2-123456" + }, + "name" : { + "type" : "string", + "description" : "A unique name for this association.", + "example" : "my_object_to_contact" + }, + "id" : { + "type" : "string", + "description" : "A unique ID for this association.", + "example" : "105" + }, + "toObjectTypeId" : { + "type" : "string", + "description" : "ID of the target object type to link to.", + "example" : "0-1" + }, + "updatedAt" : { + "type" : "string", + "description" : "When the association was last updated.", + "format" : "date-time" + } + }, + "description" : "Defines an association between two object types.", + "example" : { + "id" : "105", + "fromObjectTypeId" : "2-123456", + "toObjectTypeId" : "0-1", + "name" : "my_object_to_contact" + } + }, + "ObjectSchema" : { + "required" : [ "associations", "id", "labels", "name", "properties", "requiredProperties" ], + "type" : "object", + "properties" : { + "associations" : { + "type" : "array", + "description" : "Associations defined for a given object type.", + "example" : [ { + "id" : "123", + "name" : "my_object_to_contact", + "toObjectTypeId" : "0-1", + "fromObjectTypeId" : "2-123456" + } ], + "items" : { + "$ref" : "#/components/schemas/AssociationDefinition" + } + }, + "secondaryDisplayProperties" : { + "type" : "array", + "description" : "The names of secondary properties for this object. These will be displayed as secondary on the HubSpot record page for this object type.", + "items" : { + "type" : "string" + } + }, + "createdByUserId" : { + "type" : "integer", + "format" : "int32" + }, + "objectTypeId" : { + "type" : "string" + }, + "description" : { + "type" : "string" + }, + "updatedByUserId" : { + "type" : "integer", + "format" : "int32" + }, + "fullyQualifiedName" : { + "type" : "string", + "description" : "An assigned unique ID for the object, including portal ID and object name.", + "example" : "p7878787_my_object\"" + }, + "labels" : { + "$ref" : "#/components/schemas/ObjectTypeDefinitionLabels" + }, + "archived" : { + "type" : "boolean" + }, + "createdAt" : { + "type" : "string", + "description" : "When the object schema was created.", + "format" : "date-time", + "example" : "2020-02-20T18:07:11.390+00:00" + }, + "requiredProperties" : { + "type" : "array", + "description" : "The names of properties that should be **required** when creating an object of this type.", + "example" : [ "my_object_property" ], + "items" : { + "type" : "string" + } + }, + "searchableProperties" : { + "type" : "array", + "description" : "Names of properties that will be indexed for this object type in by HubSpot's product search.", + "example" : [ "my_object_property" ], + "items" : { + "type" : "string" + } + }, + "primaryDisplayProperty" : { + "type" : "string", + "description" : "The name of the primary property for this object. This will be displayed as primary on the HubSpot record page for this object type.", + "example" : "my_object_property" + }, + "name" : { + "type" : "string", + "description" : "A unique name for the schema's object type.", + "example" : "my_object" + }, + "id" : { + "type" : "string", + "description" : "A unique ID for this schema's object type. Will be defined as {meta-type}-{unique ID}.", + "example" : "123456" + }, + "properties" : { + "type" : "array", + "description" : "Properties defined for this object type.", + "example" : [ { + "name" : "my_object_property", + "type" : "string", + "label" : "My object property", + "archived" : false, + "createdAt" : "2020-02-20T18:07:11.802Z", + "fieldType" : "text", + "groupName" : "my_object_information", + "updatedAt" : "2020-02-20T18:07:11.802Z", + "calculated" : false, + "displayOrder" : -1, + "hasUniqueValue" : false, + "externalOptions" : false + } ], + "items" : { + "$ref" : "#/components/schemas/Property" + } + }, + "updatedAt" : { + "type" : "string", + "description" : "When the object schema was last updated.", + "format" : "date-time", + "example" : "2020-02-20T18:09:07.555+00:00" + } + }, + "description" : "Defines an object schema, including its properties and associations.", + "example" : { + "id" : "123456", + "createdAt" : "2020-02-20T18:07:11.390Z", + "updatedAt" : "2020-02-20T18:09:07.555Z", + "properties" : [ { + "updatedAt" : "2020-02-20T18:07:11.802Z", + "createdAt" : "2020-02-20T18:07:11.802Z", + "name" : "my_object_property", + "label" : "My object property", + "type" : "string", + "fieldType" : "text", + "groupName" : "my_object_information", + "displayOrder" : -1, + "calculated" : false, + "externalOptions" : false, + "archived" : false, + "hasUniqueValue" : false + } ], + "associations" : [ { + "id" : "123", + "fromObjectTypeId" : "2-123456", + "toObjectTypeId" : "0-1", + "name" : "my_object_to_contact" + } ], + "labels" : { + "singular" : "My object", + "plural" : "My objects" + }, + "requiredProperties" : [ "my_object_property" ], + "searchableProperties" : [ "my_object_property" ], + "primaryDisplayProperty" : "my_object_property", + "metaType" : "PORTAL_SPECIFIC", + "fullyQualifiedName" : "p7878787_my_object\"", + "name" : "my_object" + } + }, + "ObjectSchemaEgg" : { + "required" : [ "associatedObjects", "labels", "name", "properties", "requiredProperties" ], + "type" : "object", + "properties" : { + "secondaryDisplayProperties" : { + "type" : "array", + "description" : "The names of secondary properties for this object. These will be displayed as secondary on the HubSpot record page for this object type.", + "items" : { + "type" : "string" + } + }, + "requiredProperties" : { + "type" : "array", + "description" : "The names of properties that should be **required** when creating an object of this type.", + "example" : [ "my_object_property" ], + "items" : { + "type" : "string" + } + }, + "searchableProperties" : { + "type" : "array", + "description" : "Names of properties that will be indexed for this object type in by HubSpot's product search.", + "items" : { + "type" : "string" + } + }, + "primaryDisplayProperty" : { + "type" : "string", + "description" : "The name of the primary property for this object. This will be displayed as primary on the HubSpot record page for this object type.", + "example" : "my_object_property" + }, + "name" : { + "type" : "string", + "description" : "A unique name for this object. For internal use only.", + "example" : "my_object" + }, + "description" : { + "type" : "string" + }, + "associatedObjects" : { + "type" : "array", + "description" : "Associations defined for this object type.", + "example" : [ "CONTACT" ], + "items" : { + "type" : "string" + } + }, + "properties" : { + "type" : "array", + "description" : "Properties defined for this object type.", + "example" : [ { + "name" : "my_object_property", + "label" : "My object property", + "isPrimaryDisplayLabel" : true + } ], + "items" : { + "$ref" : "#/components/schemas/ObjectTypePropertyCreate" + } + }, + "labels" : { + "$ref" : "#/components/schemas/ObjectTypeDefinitionLabels" + } + }, + "description" : "Defines a new object type, its properties, and associations.", + "example" : { + "name" : "my_object", + "labels" : { + "singular" : "My object", + "plural" : "My objects" + }, + "primaryDisplayProperty" : "my_object_property", + "requiredProperties" : [ "my_object_property" ], + "properties" : [ { + "name" : "my_object_property", + "label" : "My object property", + "isPrimaryDisplayLabel" : true + } ], + "associatedObjects" : [ "CONTACT" ], + "metaType" : "PORTAL_SPECIFIC" + } + }, + "ObjectTypeDefinitionLabels" : { + "type" : "object", + "properties" : { + "plural" : { + "type" : "string", + "description" : "The word for multiple objects. (There’s no way to change this later.)", + "example" : "My objects" + }, + "singular" : { + "type" : "string", + "description" : "The word for one object. (There’s no way to change this later.)", + "example" : "My object" + } + }, + "description" : "Singular and plural labels for the object. Used in CRM display.", + "example" : { + "singular" : "My object", + "plural" : "My objects" + } + }, + "Option" : { + "required" : [ "hidden", "label", "value" ], + "type" : "object", + "properties" : { + "hidden" : { + "type" : "boolean", + "description" : "Hidden options will not be displayed in HubSpot.", + "example" : false + }, + "displayOrder" : { + "type" : "integer", + "description" : "Options are displayed in order starting with the lowest positive integer value. Values of -1 will cause the option to be displayed after any positive values.", + "format" : "int32", + "example" : 1 + }, + "description" : { + "type" : "string", + "description" : "A description of the option.", + "example" : "Choice number one" + }, + "label" : { + "type" : "string", + "description" : "A human-readable option label that will be shown in HubSpot.", + "example" : "Option A" + }, + "value" : { + "type" : "string", + "description" : "The internal value of the option, which must be used when setting the property value through the API.", + "example" : "A" + } + }, + "description" : "The options available when a property is an enumeration", + "example" : { + "label" : "Option A", + "description" : "Choice number one", + "value" : "A", + "displayOrder" : 1, + "hidden" : false + } + }, + "Property" : { + "required" : [ "description", "fieldType", "groupName", "label", "name", "options", "type" ], + "type" : "object", + "properties" : { + "hidden" : { + "type" : "boolean" + }, + "displayOrder" : { + "type" : "integer", + "description" : "The order that this property should be displayed in the HubSpot UI relative to other properties for this object type. Properties are displayed in order starting with the lowest positive integer value. A value of -1 will cause the property to be displayed **after** any positive values.", + "format" : "int32", + "example" : 2 + }, + "description" : { + "type" : "string", + "description" : "A description of the property that will be shown as help text in HubSpot." + }, + "showCurrencySymbol" : { + "type" : "boolean", + "description" : "Whether the property will display the currency symbol set in the account settings." + }, + "type" : { + "type" : "string", + "description" : "The property data type.", + "example" : "enumeration" + }, + "hubspotDefined" : { + "type" : "boolean", + "description" : "This will be true for default object properties built into HubSpot." + }, + "createdAt" : { + "type" : "string", + "description" : "When the property was created", + "format" : "date-time" + }, + "archived" : { + "type" : "boolean", + "description" : "Whether or not the property is archived." + }, + "options" : { + "type" : "array", + "description" : "A list of valid options for the property. This field is required for enumerated properties, but will be empty for other property types.", + "example" : [ { + "label" : "Option A", + "value" : "A", + "hidden" : false, + "description" : "Choice number one", + "displayOrder" : 1 + }, { + "label" : "Option B", + "value" : "B", + "hidden" : false, + "description" : "Choice number two", + "displayOrder" : 2 + } ], + "items" : { + "$ref" : "#/components/schemas/Option" + } + }, + "hasUniqueValue" : { + "type" : "boolean", + "description" : "Whether or not the property's value must be unique. Once set, this can't be changed.", + "example" : false + }, + "calculated" : { + "type" : "boolean", + "description" : "For default properties, true indicates that the property is calculated by a HubSpot process. It has no effect for custom properties." + }, + "externalOptions" : { + "type" : "boolean", + "description" : "For default properties, true indicates that the options are stored externally to the property settings." + }, + "updatedAt" : { + "type" : "string", + "description" : "", + "format" : "date-time" + }, + "createdUserId" : { + "type" : "string", + "description" : "The internal ID of the user who created the property in HubSpot. This field may not exist if the property was created outside of HubSpot." + }, + "modificationMetadata" : { + "$ref" : "#/components/schemas/PropertyModificationMetadata" + }, + "sensitiveDataCategories" : { + "type" : "array", + "items" : { + "type" : "string" + } + }, + "label" : { + "type" : "string", + "description" : "A human-readable property label that will be shown in HubSpot.", + "example" : "My object property" + }, + "formField" : { + "type" : "boolean", + "description" : "Whether or not the property can be used in a HubSpot form." + }, + "dataSensitivity" : { + "type" : "string", + "enum" : [ "non_sensitive", "sensitive", "highly_sensitive" ] + }, + "archivedAt" : { + "type" : "string", + "description" : "When the property was archived.", + "format" : "date-time" + }, + "groupName" : { + "type" : "string", + "description" : "The name of the property group the property belongs to.", + "example" : "my_object_information" + }, + "referencedObjectType" : { + "type" : "string", + "description" : "If this property is related to other object(s), they'll be listed here." + }, + "name" : { + "type" : "string", + "description" : "The internal property name, which must be used when referencing the property via the API.", + "example" : "my_object_property" + }, + "calculationFormula" : { + "type" : "string" + }, + "fieldType" : { + "type" : "string", + "description" : "Controls how the property appears in HubSpot.", + "example" : "select" + }, + "updatedUserId" : { + "type" : "string", + "description" : "The internal user ID of the user who updated the property in HubSpot. This field may not exist if the property was updated outside of HubSpot." + } + }, + "description" : "Defines a property", + "example" : { + "name" : "my_object_property", + "label" : "My object property", + "type" : "enumeration", + "fieldType" : "select", + "groupName" : "my_object_information", + "displayOrder" : 2, + "hasUniqueValue" : false, + "modificationMetadata" : { + "readOnlyOptions" : false, + "readOnlyValue" : false, + "readOnlyDefinition" : false, + "archivable" : true + }, + "options" : [ { + "label" : "Option A", + "description" : "Choice number one", + "value" : "A", + "displayOrder" : 1, + "hidden" : false + }, { + "label" : "Option B", + "description" : "Choice number two", + "value" : "B", + "displayOrder" : 2, + "hidden" : false + } ] + } + } + }, + "responses" : { + "Error" : { + "description" : "An error occurred.", + "content" : { + "*/*" : { + "schema" : { + "$ref" : "#/components/schemas/Error" + } + } + } + } + }, + "securitySchemes" : { + "oauth2_legacy" : { + "type" : "oauth2", + "flows" : { + "authorizationCode" : { + "authorizationUrl" : "https://app.hubspot.com/oauth/authorize", + "tokenUrl" : "https://api.hubapi.com/oauth/v1/token", + "scopes" : { + "crm.objects.custom.read" : "View custom object records", + "crm.schemas.custom.read" : "View custom object definitions" + } + } + } + }, + "private_apps_legacy" : { + "type" : "apiKey", + "name" : "private-app-legacy", + "in" : "header" + } + } + }, + "x-hubspot-available-client-libraries" : [ "PHP", "Node", "Ruby", "Python" ], + "x-hubspot-product-tier-requirements" : { + "marketing" : "ENTERPRISE", + "sales" : "ENTERPRISE", + "service" : "ENTERPRISE", + "cms" : "ENTERPRISE" + }, + "x-hubspot-documentation-banner" : "NONE" +} \ No newline at end of file diff --git a/docs/spec/sanitations.md b/docs/spec/sanitations.md index 6440959..5cc6dd9 100644 --- a/docs/spec/sanitations.md +++ b/docs/spec/sanitations.md @@ -5,20 +5,29 @@ _Edition_: Swan Lake # Sanitation for OpenAPI specification -This document records the sanitation done on top of the official OpenAPI specification from HubSpot CRM Object Schemas. -The OpenAPI specification is obtained from (TODO: Add source link). +This document records the sanitation done on top of the official OpenAPI specification from HubSpot CRM Object Tickets. +The OpenAPI specification is obtained from [Tickets OpenAPI](https://github.com/HubSpot/HubSpot-public-api-spec-collection/blob/main/PublicApiSpecs/CRM/Schemas/Rollouts/145900/v3/schemas.json). + These changes are done in order to improve the overall usability, and as workarounds for some known language limitations. -[//]: # (TODO: Add sanitation details) -1. -2. -3. +1. **Changed the date-time type mentioned in openapi.json to datetime** + +2. **Change the url property of the servers object:** + + - **Original**: https://api.hubapi.com
+ - **Updated**: https://api.hubapi.com/crm/v3/objects/schemas + - **Reason**: This change is made to ensure that all API paths are relative to the versioned base URL (crm/v3/objects/taxes), which improves the consistency and usability of the APIs. + +3. **Update API Paths:** + + - **Original**: `/crm/v3/objects/schemas` + - **Updated**: `/` + - **Reason**: This modification simplifies the API paths, making them shorter and more readable. It also centralizes the versioning to the base URL, which is a common best practice. ## OpenAPI cli command The following command was used to generate the Ballerina client from the OpenAPI specification. The command should be executed from the repository root directory. ```bash -# TODO: Add OpenAPI CLI command used to generate the client +bal openapi -i docs/spec/openapi.json --mode client -o ballerina ``` -Note: The license year is hardcoded to 2024, change if necessary. diff --git a/examples/README.md b/examples/README.md index 670f52b..d87b56c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,16 +1,25 @@ # Examples -The `ballerinax/hubspot.crm.object.schemas` connector provides practical examples illustrating usage in various scenarios. +The `hubspot.crm.object.schemas` connector provides practical examples illustrating usage in various scenarios. Explore these [examples](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/tree/main/examples), covering real world use cases. + +1. [Product and Category schemas](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/tree/main/examples/product-update) - Integrate hubspot.crm.object.schemas API to create and update a schema for a product. + +2. [Book and Author schemas](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/tree/main/examples/book-author) - Integrate hubspot.crm.object.schemas API to create schema for a book and author association. -[//]: # (TODO: Add examples) -1. -2. ## Prerequisites -[//]: # (TODO: Add prerequisites) +1. Generate Credentials to authenticate the connector as described in the [Setup Guide](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/tree/main/README.md). + +2. For each example, create a `Config.toml` file the related configuration. Here's an example of how your `Config.toml` file should look: + + ```toml + clientId = "" + clientSecret = "" + refreshToken = "" + ``` -## Running an example +## Running an Example Execute the following commands to build an example from the source: @@ -24,22 +33,4 @@ Execute the following commands to build an example from the source: ```bash bal run - ``` - -## Building the examples with the local module - -**Warning**: Due to the absence of support for reading local repositories for single Ballerina files, the Bala of the module is manually written to the central repository as a workaround. Consequently, the bash script may modify your local Ballerina repositories. - -Execute the following commands to build all the examples against the changes you have made to the module locally: - -* To build all the examples: - - ```bash - ./build.sh build - ``` - -* To run all the examples: - - ```bash - ./build.sh run - ``` + ``` \ No newline at end of file diff --git a/examples/book-author/.devcontainer.json b/examples/book-author/.devcontainer.json new file mode 100644 index 0000000..75bd926 --- /dev/null +++ b/examples/book-author/.devcontainer.json @@ -0,0 +1,8 @@ +{ + "image": "ballerina/ballerina-devcontainer:2201.10.3", + "customizations": { + "vscode": { + "extensions": ["WSO2.ballerina"] + } + } +} diff --git a/examples/book-author/Ballerina.toml b/examples/book-author/Ballerina.toml new file mode 100644 index 0000000..850cafc --- /dev/null +++ b/examples/book-author/Ballerina.toml @@ -0,0 +1,14 @@ +[package] +org = "wso2" +name = "bookauthor" +version = "0.1.0" +distribution = "2201.10.3" + +[build-options] +observabilityIncluded = true + +[[dependency]] +org = "ballerinax" +name = "hubspot.crm.obj.schemas" +version = "1.0.0" +repository = "local" diff --git a/examples/book-author/README.md b/examples/book-author/README.md new file mode 100644 index 0000000..d91d139 --- /dev/null +++ b/examples/book-author/README.md @@ -0,0 +1,27 @@ +# Ballerina HubSpot CRM Object Schemas connector + +This use case demonstrates how the `hubspot.crm.object.schemas` API can be utilized to create and update a schema for a product. First a schema is created to represent a product. Then the Update endpoint is used to update the existing schema with new properties. + +## Prerequisites + +### 1. Setup the Hubspot developer account + +Refer to the [Setup guide](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/tree/main/examples/README.md#setup-guide) to obtain necessary credentials (client Id, client secret, Refresh tokens). + +### 2. Configuration + +Create a `Config.toml` file in the example's root directory and, provide your Hubspot account related configurations as follows: + +```toml +clientId = "" +clientSecret = "" +refreshToken = "" +``` + +## Run the example + +Execute the following command to run the example: + +```bash +bal run +``` \ No newline at end of file diff --git a/examples/book-author/main.bal b/examples/book-author/main.bal new file mode 100644 index 0000000..663889d --- /dev/null +++ b/examples/book-author/main.bal @@ -0,0 +1,103 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/oauth2; +import ballerinax/hubspot.crm.obj.schemas as schemas; +import ballerina/io; + +// Configurable variables for OAuth2 authentication to connect with the HubSpot API +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; + +// Entry point of the program +public function main() returns error? { + + // OAuth2 configuration for authentication with HubSpot API using the refresh token + schemas:OAuth2RefreshTokenGrantConfig authConfig = { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + }; + + // HTTP client configuration for communicating with HubSpot API + schemas:ConnectionConfig clientConfig = { + auth: authConfig + }; + + // Initializing the HubSpot CRM Client with the configuration + final schemas:Client hubSpotClient = check new schemas:Client(clientConfig); + + // Define the schema for "Author" with its properties and labels + schemas:ObjectSchemaEgg authorSchemaPayload = { + name: "author", + labels: { + singular: "Author", + plural: "Authors" + }, + primaryDisplayProperty: "author_name", + requiredProperties: ["author_name", "author_id"], + properties: [ + {name: "author_id", label: "Author ID", "type": "string", fieldType: "text"}, + {name: "author_name", label: "Author Name", "type": "string", fieldType: "text"}, + {name: "location", label: "Location", "type": "string", fieldType: "text"} + ], + associatedObjects: [] + }; + + // Creating the "Author" schema in HubSpot + schemas:ObjectSchema authorSchemaResponse = check hubSpotClient->/.post(authorSchemaPayload); + + // Define the schema for "Book" with its properties and labels + schemas:ObjectSchemaEgg bookSchemaPayload = { + name: "book", + labels: { + singular: "Book", + plural: "Books" + }, + primaryDisplayProperty: "book_name", + requiredProperties: ["book_name", "published_date"], + properties: [ + {name: "book_name", label: "Book Name", "type": "string", fieldType: "text"}, + {name: "published_date", label: "Published Date", 'type: "datetime", fieldType: "date"} + ], + associatedObjects: [] + }; + + // Creating the "Book" schema in HubSpot + schemas:ObjectSchema bookSchemaResponse = check hubSpotClient->/.post(bookSchemaPayload); + + // Retrieve the object type IDs for both schemas + string? bookSchemaId = bookSchemaResponse.objectTypeId; + string? authorSchemaId = authorSchemaResponse.objectTypeId; + + // Check if the schema creation was successful + if bookSchemaId is () || authorSchemaId is () { + return error("Failed to create schemas!"); + } + + // Define the association between "Book" and "Author" + schemas:AssociationDefinitionEgg payload = { + fromObjectTypeId: bookSchemaId, + name: "book_to_author", + toObjectTypeId: authorSchemaId + }; + + // Create the association in HubSpot + schemas:AssociationDefinition response = check hubSpotClient->/book/associations.post(payload); + io:print(response.name ); +} \ No newline at end of file diff --git a/examples/product-update/.devcontainer.json b/examples/product-update/.devcontainer.json new file mode 100644 index 0000000..75bd926 --- /dev/null +++ b/examples/product-update/.devcontainer.json @@ -0,0 +1,8 @@ +{ + "image": "ballerina/ballerina-devcontainer:2201.10.3", + "customizations": { + "vscode": { + "extensions": ["WSO2.ballerina"] + } + } +} diff --git a/examples/product-update/Ballerina.toml b/examples/product-update/Ballerina.toml new file mode 100644 index 0000000..a4b2990 --- /dev/null +++ b/examples/product-update/Ballerina.toml @@ -0,0 +1,15 @@ +[package] +org = "thilan" +name = "productupdate" +version = "0.1.0" +distribution = "2201.10.3" + +[build-options] +observabilityIncluded = true + + +[[dependency]] +org = "ballerinax" +name = "hubspot.crm.obj.schemas" +version = "1.0.0" +repository = "local" diff --git a/examples/product-update/README.md b/examples/product-update/README.md new file mode 100644 index 0000000..d91d139 --- /dev/null +++ b/examples/product-update/README.md @@ -0,0 +1,27 @@ +# Ballerina HubSpot CRM Object Schemas connector + +This use case demonstrates how the `hubspot.crm.object.schemas` API can be utilized to create and update a schema for a product. First a schema is created to represent a product. Then the Update endpoint is used to update the existing schema with new properties. + +## Prerequisites + +### 1. Setup the Hubspot developer account + +Refer to the [Setup guide](https://github.com/ballerina-platform/module-ballerinax-hubspot.crm.object.schemas/tree/main/examples/README.md#setup-guide) to obtain necessary credentials (client Id, client secret, Refresh tokens). + +### 2. Configuration + +Create a `Config.toml` file in the example's root directory and, provide your Hubspot account related configurations as follows: + +```toml +clientId = "" +clientSecret = "" +refreshToken = "" +``` + +## Run the example + +Execute the following command to run the example: + +```bash +bal run +``` \ No newline at end of file diff --git a/examples/product-update/main.bal b/examples/product-update/main.bal new file mode 100644 index 0000000..5bbcc45 --- /dev/null +++ b/examples/product-update/main.bal @@ -0,0 +1,71 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; +import ballerina/io; +import ballerina/oauth2; +import ballerinax/hubspot.crm.obj.schemas as schemas; + +// Configurable variables for OAuth2 authentication to connect with the HubSpot API +configurable string clientId = ?; +configurable string clientSecret = ?; +configurable string refreshToken = ?; +const string objId = "test123"; + +// Entry point of the program +public function main() returns error? { + + // OAuth2 configuration for authentication with HubSpot API using the refresh token + schemas:OAuth2RefreshTokenGrantConfig authConfig = { + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + credentialBearer: oauth2:POST_BODY_BEARER + }; + + // HTTP client configuration for communicating with HubSpot API + schemas:ConnectionConfig clientConfig = { + auth: authConfig + }; + + // Initializing the HubSpot CRM Client with the configuration + final schemas:Client hubSpotClient = check new schemas:Client(clientConfig); + + schemas:ObjectSchema productSearchResponse = check hubSpotClient->/[objId].get(); + + // Define the schema for "Product" with its properties and labels + schemas:ObjectTypeDefinitionPatch productSchemaPayload = { + name: "product", + labels: { + singular: "Product", + plural: "Products" + }, + primaryDisplayProperty: "product_name", + requiredProperties: ["product_name", "product_id"], + properties: [ + {"name": "product_id", "label": "Product ID", "type": "string", "fieldType": "text"}, + {"name": "product_name", "label": "Product Name", "type": "string", "fieldType": "text"}, + {"name": "price", "label": "Price", "type": "number", "fieldType": "number"}, + {"name": "availability", "label": "Availability", "type": "bool", "fieldType": "checkbox"} + ], + associatedObjects: [] + }; + + // Creating the "Product" schema in HubSpot + schemas:ObjectTypeDefinition productSchemaResponse = check hubSpotClient->/[objId].patch(productSchemaPayload); + io:print(productSchemaResponse.name ); + +} diff --git a/settings.gradle b/settings.gradle index fe9d77c..b2ab596 100644 --- a/settings.gradle +++ b/settings.gradle @@ -27,11 +27,11 @@ plugins { id "com.gradle.enterprise" version "3.2" } -rootProject.name = 'module-ballerinax-hubspot.crm.object.schemas' +rootProject.name = 'module-ballerinax-hubspot.crm.obj.schemas' -include ':hubspot.crm.object.schemas-ballerina' +include ':hubspot.crm.obj.schemas-ballerina' -project(':hubspot.crm.object.schemas-ballerina').projectDir = file("ballerina") +project(':hubspot.crm.obj.schemas-ballerina').projectDir = file("ballerina") gradleEnterprise { buildScan {