Skip to content

Commit

Permalink
Support for HubSpot custom objects (#3)
Browse files Browse the repository at this point in the history
* Support for HubSpot custom objects

* HubSpot SDK version change
  • Loading branch information
sitole authored Jun 3, 2022
1 parent e6862e1 commit 3588422
Show file tree
Hide file tree
Showing 10 changed files with 198 additions and 16 deletions.
54 changes: 49 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ General implementation of [HubSpot](https://developers.hubspot.com/docs/api/crm/
⚠️ GitHub packages is depricated, use official Maven repository

## Supported features
| Feature | List | Read | Create | Change | Delete |
|-----------|--------|--------|----------|----------|----------|
| Company ||||||
| Contact ||||||
| Deal ||||||
| Feature | List | Read | Create | Change | Delete |
|------------------|--------|--------|----------|----------|----------|
| Company ||||||
| Custom objects ||||||
| Contact ||||||
| Deal ||||||

## Supported types

| Type | Note | Supported |
|---------|------------------------------------------|-----------|
| String | - ||
| Boolean | - ||
| Long | - ||
| Int | - ||
| Enum | Use Java String converted ||
| Date | Use Java 8 dates with toString formatter ||

## Usage Examples

Expand All @@ -37,6 +49,8 @@ val client = Client(
)
```

## #️⃣ Company entity

### Create brand-new company
```kotlin
val companyRequest = CompanyRequest(
Expand Down Expand Up @@ -70,3 +84,33 @@ val companyResponse = companiesClient.changeCompany(123456789, companyRequest)
println(companyResponse.id) // HubSpot company ID
println(companyResponse.properties["name"]) // John Doe
```

## #️⃣ Custom objects

### Create brand-new custom object record
```kotlin
val request = CustomObjectRequest(
properties = MySuperEventProperties(
name = "Party #2022",
address = "New York",

// Date must be formatted as "YYYY-MM-DD"
dateFrom = LocalDate
.now()
.plusDays(10)
.format(DateTimeFormatter.ISO_LOCAL_DATE),
dateUntil = LocalDate
.now()
.plusDays(15)
.format(DateTimeFormatter.ISO_LOCAL_DATE),
)
)

// HubSpot client and name of custom object table
val myEventsClient = CustomObjectClient(client, "events")

val response = myEventsClient.createCustomObjectRecord(request)

println(response.id) // HubSpot ID
println(response.properties["name"]) // Party #2022
```
2 changes: 1 addition & 1 deletion hubspot/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
}

group = "com.goforboom"
version = "1.0.2"
version = "1.1.0"

java.sourceCompatibility = JavaVersion.VERSION_11
java.targetCompatibility = JavaVersion.VERSION_11
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ object ClientRequestCatalog {
object V3 {
const val COMPANIES = "/crm/v3/objects/companies"
const val COMPANIES_DETAIL = "/crm/v3/objects/companies/{companyId}"

const val CUSTOM_OBJECT = "/crm/v3/objects/{customObjectEntity}"
const val CUSTOM_OBJECT_DETAIL = "/crm/v3/objects/{customObjectEntity}/{customObjectId}"
}
}
16 changes: 16 additions & 0 deletions hubspot/src/main/kotlin/com/goforboom/hubspot/domain/DataEntity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.goforboom.hubspot.domain

import java.math.BigInteger
import java.time.LocalDateTime

interface DataEntity {
val id: BigInteger

// Jackson is not able to recognize generic in generic,
// it means he is not able to cast two levels of generic (company and custom company props)
val properties: Map<String, Any>

val createdAt: LocalDateTime
val updatedAt: LocalDateTime
val archived: Boolean
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package com.goforboom.hubspot.domain.company

import com.goforboom.hubspot.domain.DataEntity
import java.math.BigInteger
import java.time.LocalDateTime

class Company(
val id: BigInteger,

// Jackson is not able to recognize generic in generic,
// it means he is not able to cast two levels of generic (company and custom company props)
val properties: Map<String, Any>,

val createdAt: LocalDateTime,
val updatedAt: LocalDateTime,
val archived: Boolean
)
override val id: BigInteger,
override val properties: Map<String, Any>,
override val createdAt: LocalDateTime,
override val updatedAt: LocalDateTime,
override val archived: Boolean
) : DataEntity
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.goforboom.hubspot.domain.customobject

import com.goforboom.hubspot.domain.DataEntity
import java.math.BigInteger
import java.time.LocalDateTime

class CustomObject(
override val id: BigInteger,
override val properties: Map<String, Any>,
override val createdAt: LocalDateTime,
override val updatedAt: LocalDateTime,
override val archived: Boolean
) : DataEntity



Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.goforboom.hubspot.domain.customobject

import com.goforboom.hubspot.model.http.RequestMethod
import com.goforboom.hubspot.model.http.Requester
import com.goforboom.hubspot.Client
import com.goforboom.hubspot.ClientRequestCatalog
import com.goforboom.hubspot.domain.customobject.exceptions.CustomObjectNotFoundException
import com.goforboom.hubspot.model.http.exceptions.HttpRequestException
import com.goforboom.hubspot.model.mapper.Mapper
import java.math.BigInteger

class CustomObjectClient(
private val hubSpotClient: Client,
private val customObjectEntity: String
) {

private fun buildUrlForObjectRecord(customObjectId: BigInteger): String {
return ClientRequestCatalog.V3.CUSTOM_OBJECT_DETAIL
.replace("{customObjectEntity}", customObjectEntity)
.replace(
"{customObjectId}", customObjectId.toString()
)
}


fun <P> createCustomObjectRecord(request: CustomObjectRequest<P>): CustomObject {
val requestUrl = ClientRequestCatalog.V3.CUSTOM_OBJECT.replace("{customObjectEntity}", customObjectEntity)

val response = Requester.requestJson(hubSpotClient, RequestMethod.POST, requestUrl, emptyMap(), request)

if (response.isSuccess) {
return Mapper.mapToObject(response.body)
} else {
throw RuntimeException(response.body.toPrettyString())
}
}


@Throws(
CustomObjectNotFoundException::class,
HttpRequestException::class
)
fun findCustomObjectRecord(customObjectId: BigInteger): CustomObject {
val requestUrl = this.buildUrlForObjectRecord(customObjectId)
val response = Requester.requestJson(hubSpotClient, RequestMethod.GET, requestUrl)

if (response.isSuccess) {
return Mapper.mapToObject(response.body)
} else {
when (response.status) {
404 -> throw CustomObjectNotFoundException(this.customObjectEntity, customObjectId)
else -> throw HttpRequestException(response.status, response.statusText)
}
}
}


fun <P> changeCustomObjectRecord(customObjectId: BigInteger, request: CustomObjectRequest<P>): CustomObject {
val requestUrl = this.buildUrlForObjectRecord(customObjectId)
val response = Requester.requestJson(hubSpotClient, RequestMethod.PATCH, requestUrl, emptyMap(), request)

if (response.isSuccess) {
return Mapper.mapToObject(response.body)
} else {
when (response.status) {
404 -> throw CustomObjectNotFoundException(this.customObjectEntity, customObjectId)
else -> throw HttpRequestException(response.status, response.statusText)
}
}
}

@Throws(
HttpRequestException::class
)
fun removeCustomObjectRecord(customObjectId: BigInteger) {
val requestUrl = this.buildUrlForObjectRecord(customObjectId)

// Unknown company returns HTTP code 204
val response = Requester.requestVoid(hubSpotClient, RequestMethod.DELETE, requestUrl)

if (!response.isSuccess) {
when (response.status) {
else -> throw HttpRequestException(response.status, response.statusText)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.goforboom.hubspot.domain.customobject

data class CustomObjectRequest<P>(
val properties: P
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.goforboom.hubspot.domain.customobject.exceptions

import com.goforboom.hubspot.exceptions.HubSpotException

abstract class CustomObjectException(override val message: String) : HubSpotException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.goforboom.hubspot.domain.customobject.exceptions

import java.math.BigInteger

class CustomObjectNotFoundException(
customObjectEntity: String,
customObjectId: BigInteger,
override val message: String = "Custom object '$customObjectId' was not found in table '$customObjectEntity'."
) : CustomObjectException(message)

0 comments on commit 3588422

Please sign in to comment.