Compare commits
No commits in common. "03e6c0bf5af4fcf8b1d94d49a09b97621d926409" and "53c2fcee0c27d7b3de1c25a607b6a131a9821440" have entirely different histories.
03e6c0bf5a
...
53c2fcee0c
14
README.md
14
README.md
@ -1,14 +0,0 @@
|
|||||||
# Hope Family Fellowship Notion API Client
|
|
||||||
|
|
||||||
## Building
|
|
||||||
|
|
||||||
### Software Packages
|
|
||||||
|
|
||||||
`hff_notion_api_client` depends on a number of packages which are not yet
|
|
||||||
available in he official Nim repository. The easiest way to get access to these
|
|
||||||
packages is to add a new `PackageList` to your [nimble configuration] for the
|
|
||||||
[JDB Software Nim packages repository]. The url is
|
|
||||||
`https://git.jdb-software.com/jdb/nim-packages/raw/main/packages.json`
|
|
||||||
|
|
||||||
[nimble configuration]: https://github.com/nim-lang/nimble#configuration
|
|
||||||
[JDB Software Nim packages]: https://git.jdb-software.com/jdb/nim-packages
|
|
@ -1,14 +0,0 @@
|
|||||||
# Package
|
|
||||||
|
|
||||||
version = "0.5.0"
|
|
||||||
author = "Jonathan Bernard"
|
|
||||||
description = "Utilities and bindings for HFF's Notion API."
|
|
||||||
license = "GPL-3.0-or-later"
|
|
||||||
srcDir = "src"
|
|
||||||
|
|
||||||
# Dependencies
|
|
||||||
|
|
||||||
requires "nim >= 1.4.8"
|
|
||||||
|
|
||||||
# packages from git.jdb-software.com/jdb/nim-packages
|
|
||||||
requires @["buffoonery", "timeutils >= 0.5.0", "namespaced_logging >= 0.3.1"]
|
|
13
notion_utils.nimble
Normal file
13
notion_utils.nimble
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Package
|
||||||
|
|
||||||
|
version = "0.2.0"
|
||||||
|
author = "Jonathan Bernard"
|
||||||
|
description = "Utilities and bindings for the Notion API."
|
||||||
|
license = "GPL-3.0-or-later"
|
||||||
|
srcDir = "src"
|
||||||
|
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
|
||||||
|
requires "nim >= 1.4.8"
|
||||||
|
requires "https://git.jdb-software.com/jdb/nim-time-utils.git >= 0.5.0"
|
@ -1,181 +0,0 @@
|
|||||||
import std/[httpclient, json, logging, options, sequtils, tables, strutils]
|
|
||||||
|
|
||||||
import buffoonery/jsonutils
|
|
||||||
|
|
||||||
import hff_notion_api_client/[config, models, sequtils_ext, utils]
|
|
||||||
|
|
||||||
type
|
|
||||||
NotionClientConfig* = object
|
|
||||||
apiBaseUrl*: string
|
|
||||||
apiVersion*: string
|
|
||||||
configDbId*: string
|
|
||||||
integrationToken*: string
|
|
||||||
|
|
||||||
NotionClient* = ref object
|
|
||||||
http: HttpClient
|
|
||||||
apiBaseUrl: string
|
|
||||||
config*: HffNotionConfig
|
|
||||||
|
|
||||||
proc loadNotionConfig(http: HttpClient, cfg: NotionClientConfig): HffNotionConfig =
|
|
||||||
let url = cfg.apiBaseUrl & "/databases/" & cfg.configDbId & "/query"
|
|
||||||
let body = $(%*{ "page_size": NOTION_MAX_PAGE_SIZE })
|
|
||||||
|
|
||||||
debug "loadNotionConfig\n\tPOST " & url & "\n\t" & $body
|
|
||||||
let resp = http.postContent(url, body)
|
|
||||||
let resultsJson = parseJson(resp)["results"]
|
|
||||||
|
|
||||||
result = parseHffNotionConfig(resultsJson)
|
|
||||||
|
|
||||||
proc initNotionClient*(cfg: NotionClientConfig): NotionClient =
|
|
||||||
result = NotionClient(
|
|
||||||
apiBaseUrl: cfg.apiBaseUrl,
|
|
||||||
http: newHttpClient(headers = newHttpHeaders([
|
|
||||||
("Content-Type", "application/json"),
|
|
||||||
("Authorization", "Bearer " & cfg.integrationToken),
|
|
||||||
("Notion-Version", cfg.apiVersion)
|
|
||||||
], true)))
|
|
||||||
|
|
||||||
result.config = result.http.loadNotionConfig(cfg)
|
|
||||||
|
|
||||||
proc `%`*(c: NotionClientConfig): JsonNode =
|
|
||||||
%*{
|
|
||||||
"apiBaseUrl": c.apiBaseUrl,
|
|
||||||
"apiVersion": c.apiVersion,
|
|
||||||
"configDbId": c.configDbId,
|
|
||||||
"integrationToken": c.integrationToken
|
|
||||||
}
|
|
||||||
|
|
||||||
proc parseNotionClientConfig*(n: JsonNode): NotionClientConfig =
|
|
||||||
NotionClientConfig(
|
|
||||||
apiBaseUrl: n.getOrFail("apiBaseUrl").getStr,
|
|
||||||
apiVersion: n.getOrFail("apiVersion").getStr,
|
|
||||||
configDbId: n.getOrFail("configDbId").getStr,
|
|
||||||
integrationToken: n.getOrFail("integrationToken").getStr)
|
|
||||||
|
|
||||||
proc fetchAllPages*(
|
|
||||||
http: HttpClient,
|
|
||||||
url: string,
|
|
||||||
bodyTemplate = %*{ "page_size": NOTION_MAX_PAGE_SIZE}): seq[JsonNode] =
|
|
||||||
|
|
||||||
result = @[]
|
|
||||||
var nextCursor: Option[string] = none[string]()
|
|
||||||
|
|
||||||
while true:
|
|
||||||
let body = parseJson($bodyTemplate)
|
|
||||||
if nextCursor.isSome: body["start_cursor"] = %nextCursor.get
|
|
||||||
|
|
||||||
debug "Fetching pages from database:\n\tPOST " & url & "\n\t" & $body
|
|
||||||
let jsonResp = parseJson(http.postContent(url, $body))
|
|
||||||
|
|
||||||
result = result & jsonResp["results"].getElems
|
|
||||||
if jsonResp.hasKey("next_cursor") and jsonResp["next_cursor"].kind != JNull:
|
|
||||||
nextCursor = some(jsonResp["next_cursor"].getStr)
|
|
||||||
else: break
|
|
||||||
|
|
||||||
template fetchDatabaseObject*(notion: NotionClient, dbId: string): untyped =
|
|
||||||
let resp = notion.http.get(notion.apiBaseUrl & "/databases/" & dbId)
|
|
||||||
|
|
||||||
if not resp.status.startsWith("2"):
|
|
||||||
debug resp.body
|
|
||||||
raise newException(HttpRequestError, "API Request failed: " & resp.body)
|
|
||||||
parseJson(resp.body)
|
|
||||||
|
|
||||||
template fetchPage*(notion: NotionClient, pageId: string): untyped =
|
|
||||||
let resp = notion.http.get(notion.apiBaseUrl & "/pages/" & pageId)
|
|
||||||
|
|
||||||
if not resp.status.startsWith("2"):
|
|
||||||
debug resp.body
|
|
||||||
raise newException(HttpRequestError, "API Request failed: " & resp.body)
|
|
||||||
parseJson(resp.body)
|
|
||||||
|
|
||||||
template createDbPage*(notion: NotionClient, parentDbId: string, r: typed): untyped =
|
|
||||||
let page = r.toPage
|
|
||||||
page["parent"] = %*{ "database_id": parentDbId }
|
|
||||||
|
|
||||||
let resp = notion.http.post(notion.apiBaseUrl & "/pages", $page)
|
|
||||||
|
|
||||||
if not resp.status.startsWith("2"):
|
|
||||||
debug resp.body
|
|
||||||
raise newException(HttpRequestError, "API Request failed: " & resp.body)
|
|
||||||
parseJson(resp.body)
|
|
||||||
|
|
||||||
template updatePage*(notion: NotionClient, r: typed): JsonNode =
|
|
||||||
let page = r.toPage
|
|
||||||
|
|
||||||
let resp = notion.http.patch(notion.apiBaseUrl & "/pages/" & r.id, $page)
|
|
||||||
|
|
||||||
if not resp.status.startsWith("2"): debug resp.body
|
|
||||||
parseJson(resp.body)
|
|
||||||
|
|
||||||
template delete*(notion: NotionClient, r: typed): string =
|
|
||||||
let resp = notion.http.delete(notion.apiBaseUrl & "/blocks/" & r.id)
|
|
||||||
|
|
||||||
if not resp.status.startsWith("2"): debug resp.body
|
|
||||||
parseJson(resp.body)["id"].getStr
|
|
||||||
|
|
||||||
proc fetchSyncRecords*(notion: NotionClient): seq[SyncRecord] =
|
|
||||||
let respRecords = notion.http.fetchAllPages(
|
|
||||||
notion.apiBaseUrl & "/databases/" & notion.config.syncRecordsDbId & "/query")
|
|
||||||
|
|
||||||
return respRecords.mapIt(syncRecordFromPage(it))
|
|
||||||
|
|
||||||
proc create*(notion: NotionClient, r: SyncRecord): SyncRecord =
|
|
||||||
return syncRecordFromPage(createDbPage(notion, notion.config.syncRecordsDbId, r))
|
|
||||||
|
|
||||||
proc update*(notion: NotionClient, r: SyncRecord): SyncRecord =
|
|
||||||
return syncRecordFromPage(updatePage(notion, r))
|
|
||||||
|
|
||||||
proc fetchAddress*(notion: NotionClient, addressId: string): Address =
|
|
||||||
return addressFromPage(notion.fetchPage(addressId))
|
|
||||||
|
|
||||||
proc fetchAddresses*(notion: NotionClient): seq[Address] =
|
|
||||||
let respRecords = notion.http.fetchAllPages(
|
|
||||||
notion.apiBaseUrl & "/databases/" & notion.config.addressesDbId & "/query")
|
|
||||||
|
|
||||||
return resprecords.mapIt(addressFromPage(it))
|
|
||||||
|
|
||||||
proc create*(notion: NotionClient, a: Address): Address =
|
|
||||||
return addressFromPage(notion.createDbPage(notion.config.addressesDbId , a))
|
|
||||||
|
|
||||||
proc update*(notion: NotionClient, r: Address): Address =
|
|
||||||
return addressFromPage(updatePage(notion, r))
|
|
||||||
|
|
||||||
proc fetchFamily*(notion: NotionClient, familyId: string): Family =
|
|
||||||
return familyFromPage(notion.fetchPage(familyId))
|
|
||||||
|
|
||||||
proc fetchFamilies*(notion: NotionClient): seq[Family] =
|
|
||||||
let respRecords = notion.http.fetchAllPages(
|
|
||||||
notion.apiBaseUrl & "/databases/" & notion.config.familiesDbId & "/query")
|
|
||||||
|
|
||||||
return respRecords.mapIt(familyFromPage(it))
|
|
||||||
|
|
||||||
proc create*(notion: NotionClient, f: Family): Family =
|
|
||||||
return familyFromPage(notion.createDbPage(notion.config.familiesDbId , f))
|
|
||||||
|
|
||||||
proc update*(notion: NotionClient, r: Family): Family =
|
|
||||||
return familyFromPage(updatePage(notion, r))
|
|
||||||
|
|
||||||
proc fetchPerson*(notion: NotionClient, personId: string): Person =
|
|
||||||
return personFromPage(notion.fetchPage(personId))
|
|
||||||
|
|
||||||
proc fetchPeople*(notion: NotionClient): seq[Person] =
|
|
||||||
let respRecords = notion.http.fetchAllPages(
|
|
||||||
notion.apiBaseUrl & "/databases/" & notion.config.peopleDbId & "/query")
|
|
||||||
|
|
||||||
return respRecords.mapIt(personFromPage(it))
|
|
||||||
|
|
||||||
proc create*(notion: NotionClient, p: Person): Person =
|
|
||||||
return personFromPage(notion.createDbPage(notion.config.peopleDbId, p))
|
|
||||||
|
|
||||||
proc update*(notion: NotionClient, r: Person): Person =
|
|
||||||
return personFromPage(updatePage(notion, r))
|
|
||||||
|
|
||||||
proc persist*[T](notion: NotionClient, r: T): T =
|
|
||||||
if r.id.isEmptyOrWhitespace: notion.create(r)
|
|
||||||
else: notion.update(r)
|
|
||||||
|
|
||||||
proc fetchMembershipDataSet*(notion: NotionClient): MembershipDataSet =
|
|
||||||
result = MembershipDataSet(
|
|
||||||
addresses: mapById(notion.fetchAddresses()),
|
|
||||||
families: mapById(notion.fetchFamilies()),
|
|
||||||
people: mapById(notion.fetchPeople()))
|
|
@ -1,47 +0,0 @@
|
|||||||
import std/json, std/sequtils, std/times
|
|
||||||
import timeutils
|
|
||||||
|
|
||||||
type
|
|
||||||
HffNotionConfigProperty = tuple[key: string, textNode: JsonNode, dateNode: JsonNode]
|
|
||||||
|
|
||||||
HffNotionConfig* = object
|
|
||||||
addressesDbId*: string
|
|
||||||
familiesDbId*: string
|
|
||||||
peopleDbId*: string
|
|
||||||
syncRecordsDbId*: string
|
|
||||||
lastSyncedAt*: DateTime
|
|
||||||
|
|
||||||
proc getCfgProp(cfgPages: JsonNode, key: string): HffNotionConfigProperty =
|
|
||||||
|
|
||||||
let propNodes = cfgPages.filterIt(
|
|
||||||
it{"properties", "Key", "title"}[0]["plain_text"].getStr == key)
|
|
||||||
|
|
||||||
if propNodes.len != 1:
|
|
||||||
raise newException(Exception,
|
|
||||||
"there is no configuration value for key '" & key &
|
|
||||||
"' in the Notion Configuration table.")
|
|
||||||
|
|
||||||
return (
|
|
||||||
propNodes[0]{"properties", "Key", "title"}[0]["plain_text"].getStr,
|
|
||||||
propNodes[0]{"properties", "TextValue", "rich_text"},
|
|
||||||
propNodes[0]{"properties", "DateTimeValue", "date"})
|
|
||||||
|
|
||||||
proc dateValue(prop: HffNotionConfigProperty): DateTime =
|
|
||||||
if isNil(prop.dateNode) or prop.dateNode.kind == JNull:
|
|
||||||
raise newException(Exception, prop.key & " does not have a date value")
|
|
||||||
|
|
||||||
return parseIso8601(prop.dateNode["start"].getStr)
|
|
||||||
|
|
||||||
proc textValue(prop: HffNotionConfigProperty): string =
|
|
||||||
if prop.textNode.len == 0:
|
|
||||||
raise newException(Exception, prop.key & " does not have a text value")
|
|
||||||
|
|
||||||
return prop.textNode[0]["plain_text"].getStr
|
|
||||||
|
|
||||||
proc parseHffNotionConfig*(n: JsonNode): HffNotionConfig =
|
|
||||||
result = HffNotionConfig(
|
|
||||||
addressesDbId: n.getCfgProp("addressesDbId").textValue,
|
|
||||||
familiesDbId: n.getCfgProp("familiesDbId").textValue,
|
|
||||||
peopleDbId: n.getCfgProp("peopleDbId").textValue,
|
|
||||||
syncRecordsDbId: n.getCfgProp("syncRecordsDbId").textValue,
|
|
||||||
lastSyncedAt: n.getCfgProp("lastSyncedAt").dateValue)
|
|
@ -1,20 +0,0 @@
|
|||||||
import std/options, std/sugar, std/tables
|
|
||||||
|
|
||||||
func sameContents*[T](a: seq[T], b: seq[T]): bool =
|
|
||||||
let aCount = toCountTable(a)
|
|
||||||
let bCount = toCountTable(b)
|
|
||||||
|
|
||||||
for k in aCount.keys:
|
|
||||||
if not bCount.hasKey(k) or aCount[k] != bCount[k]: return false
|
|
||||||
|
|
||||||
return aCount.len == bCount.len
|
|
||||||
|
|
||||||
func findBy*[T](s: seq[T], operation: (T) -> bool): Option[T] =
|
|
||||||
for i in s:
|
|
||||||
if operation(i):
|
|
||||||
return some(i)
|
|
||||||
return none[T]()
|
|
||||||
|
|
||||||
proc mapById*[T](records: seq[T]): TableRef[string, T] =
|
|
||||||
result = newTable[string, T]()
|
|
||||||
for r in records: result[r.id] = r
|
|
@ -1,16 +1,10 @@
|
|||||||
import std/json, std/logging, std/options, std/sequtils, std/times
|
import std/httpclient, std/json, std/logging, std/options, std/sequtils,
|
||||||
import namespaced_logging
|
std/strutils, std/times
|
||||||
import timeutils
|
import timeutils
|
||||||
|
|
||||||
const NOTION_MAX_PAGE_SIZE* = 100
|
const NOTION_MAX_PAGE_SIZE* = 100
|
||||||
const NOTION_DATE_FORMAT = "YYYY-MM-dd"
|
const NOTION_DATE_FORMAT = "YYYY-MM-dd"
|
||||||
|
|
||||||
var logNs {.threadvar.}: LoggingNamespace
|
|
||||||
|
|
||||||
template log(): untyped =
|
|
||||||
if logNs.isNil: logNs = getLoggerForNamespace("hff_notion_api_client/utils", lvlInfo)
|
|
||||||
logNs
|
|
||||||
|
|
||||||
proc parseDate(str: string): DateTime =
|
proc parseDate(str: string): DateTime =
|
||||||
try: result = parseIso8601(str)
|
try: result = parseIso8601(str)
|
||||||
except: result = times.parse(str, NOTION_DATE_FORMAT)
|
except: result = times.parse(str, NOTION_DATE_FORMAT)
|
||||||
@ -57,7 +51,7 @@ proc makeTextProp*(propType: string, value: string): JsonNode =
|
|||||||
## Utility functions for reading Page property values
|
## Utility functions for reading Page property values
|
||||||
## --------------------------------------------------
|
## --------------------------------------------------
|
||||||
|
|
||||||
proc getPropNode*(page: JsonNode, propType, propName: string): JsonNode =
|
proc getPropNode(page: JsonNode, propType, propName: string): JsonNode =
|
||||||
result = page{"properties", propName, propType}
|
result = page{"properties", propName, propType}
|
||||||
|
|
||||||
if isNil(result):
|
if isNil(result):
|
||||||
@ -81,25 +75,6 @@ proc getRelationIds*(page: JsonNode, propName: string): seq[string] =
|
|||||||
let propNode = page.getPropNode("relation", propName)
|
let propNode = page.getPropNode("relation", propName)
|
||||||
return propNode.getElems.mapIt(it["id"].getStr)
|
return propNode.getElems.mapIt(it["id"].getStr)
|
||||||
|
|
||||||
proc getRollupArrayValues*(page: JsonNode, propName: string): seq[JsonNode] =
|
|
||||||
return page.getPropNode("rollup", propName){"array"}.getElems(@[])
|
|
||||||
|
|
||||||
proc getRolledupRecordTitles*(page: JsonNode, propName: string): seq[string] =
|
|
||||||
let rollups = page.getPropNode("rollup", propName)["array"]
|
|
||||||
if rollups.getElems.len == 0: return @[]
|
|
||||||
|
|
||||||
result = @[]
|
|
||||||
for rollupItem in rollups.getElems:
|
|
||||||
if not rollupItem.hasKey("title") or rollupItem["title"].getElems.len != 1:
|
|
||||||
log().debug "Expected exactly one item of type 'title'. Received: \n" &
|
|
||||||
rollupItem.pretty
|
|
||||||
|
|
||||||
raise newException(
|
|
||||||
ValueError,
|
|
||||||
"unexpected format of rollup value for '" & propName & "' property")
|
|
||||||
|
|
||||||
result.add(rollupItem["title"].getElems[0]["plain_text"].getStr)
|
|
||||||
|
|
||||||
proc getText*(page: JsonNode, propName: string): string =
|
proc getText*(page: JsonNode, propName: string): string =
|
||||||
let propNode = page.getPropNode("rich_text", propName)
|
let propNode = page.getPropNode("rich_text", propName)
|
||||||
if propNode.len == 0: return ""
|
if propNode.len == 0: return ""
|
||||||
@ -119,3 +94,31 @@ proc getDateTime*(page: JsonNode, propName: string): Option[DateTime] =
|
|||||||
let propNode = page.getPropNode("date", propName)
|
let propNode = page.getPropNode("date", propName)
|
||||||
if propNode.kind == JNull: result = none[DateTime]()
|
if propNode.kind == JNull: result = none[DateTime]()
|
||||||
else: result = some(parseDate(propNode["start"].getStr))
|
else: result = some(parseDate(propNode["start"].getStr))
|
||||||
|
|
||||||
|
proc newNotionClient*(apiVersion, integrationToken: string): HttpClient =
|
||||||
|
return newHttpClient(headers = newHttpHeaders([
|
||||||
|
("Content-Type", "application/json"),
|
||||||
|
("Authorization", "Bearer " & integrationToken),
|
||||||
|
("Notion-Version", apiVersion)
|
||||||
|
], true))
|
||||||
|
|
||||||
|
proc fetchAllPages*(
|
||||||
|
http: HttpClient,
|
||||||
|
url: string,
|
||||||
|
bodyTemplate = %*{ "page_size": NOTION_MAX_PAGE_SIZE}): seq[JsonNode] =
|
||||||
|
|
||||||
|
result = @[]
|
||||||
|
var nextCursor: string = ""
|
||||||
|
|
||||||
|
while true:
|
||||||
|
let body = parseJson($bodyTemplate)
|
||||||
|
if not nextCursor.isEmptyOrWhitespace: body["start_cursor"] = %nextCursor
|
||||||
|
|
||||||
|
debug "Fetching pages from database:\n\tPOST " & url & "\n\t" & $body
|
||||||
|
let jsonResp = parseJson(http.postContent(url, $body))
|
||||||
|
|
||||||
|
result = result & jsonResp["results"].getElems
|
||||||
|
if jsonResp.hasKey("next_cursor") and jsonResp["next_cursor"].kind != JNull:
|
||||||
|
nextCursor = jsonResp["next_cursor"].getStr
|
||||||
|
|
||||||
|
if nextCursor.isEmptyOrWhitespace: break
|
@ -1,35 +1,30 @@
|
|||||||
import std/json, std/options, std/sequtils, std/sets, std/strutils, std/tables,
|
import std/json, std/options, std/tables, std/times
|
||||||
std/times
|
|
||||||
|
|
||||||
import timeutils
|
import timeutils
|
||||||
|
|
||||||
import ./utils
|
import ../notion_utils
|
||||||
|
|
||||||
type
|
type
|
||||||
AddressObj* = object
|
NAddressObj* = object
|
||||||
id*: string
|
id*: string
|
||||||
city*: string
|
city*: string
|
||||||
state*: string
|
state*: string
|
||||||
street*: string
|
street*: string
|
||||||
suiteOrBuilding*: string
|
|
||||||
zipCode*: string
|
zipCode*: string
|
||||||
createdAt*: Option[DateTime]
|
createdAt*: Option[DateTime]
|
||||||
lastUpdatedAt*: Option[DateTime]
|
lastUpdatedAt*: Option[DateTime]
|
||||||
|
|
||||||
|
|
||||||
FamilyObj* = object
|
NFamilyObj* = object
|
||||||
id*: string
|
id*: string
|
||||||
name*: string
|
name*: string
|
||||||
headsOfHousehold*: seq[string]
|
|
||||||
headOfHouseholdIds*: seq[string]
|
headOfHouseholdIds*: seq[string]
|
||||||
primaryAddress*: string
|
primaryAddressId*: seq[string]
|
||||||
primaryAddressId*: string
|
|
||||||
members*: seq[string]
|
|
||||||
memberIds*: seq[string]
|
memberIds*: seq[string]
|
||||||
createdAt*: Option[DateTime]
|
createdAt*: Option[DateTime]
|
||||||
lastUpdatedAt*: Option[DateTime]
|
lastUpdatedAt*: Option[DateTime]
|
||||||
|
|
||||||
PersonObj* = object
|
NPersonObj* = object
|
||||||
id*: string
|
id*: string
|
||||||
preferredName*: string
|
preferredName*: string
|
||||||
firstName*: string
|
firstName*: string
|
||||||
@ -39,39 +34,18 @@ type
|
|||||||
gender*: string
|
gender*: string
|
||||||
primaryPhoneNumber*: string
|
primaryPhoneNumber*: string
|
||||||
primaryEmailAddress*: string
|
primaryEmailAddress*: string
|
||||||
addresses*: seq[string]
|
|
||||||
addressIds*: seq[string]
|
addressIds*: seq[string]
|
||||||
marriedTo*: string
|
marriedToId*: seq[string]
|
||||||
marriedToId*: string
|
|
||||||
anniversary*: Option[DateTime]
|
anniversary*: Option[DateTime]
|
||||||
parents*: seq[string]
|
|
||||||
parentIds*: seq[string]
|
parentIds*: seq[string]
|
||||||
children*: seq[string]
|
|
||||||
childIds*: seq[string]
|
childIds*: seq[string]
|
||||||
relationshipToHff*: seq[string]
|
relationshipToHff*: seq[string]
|
||||||
createdAt*: Option[DateTime]
|
createdAt*: Option[DateTime]
|
||||||
lastUpdatedAt*: Option[DateTime]
|
lastUpdatedAt*: Option[DateTime]
|
||||||
apiPermissions*: seq[string]
|
|
||||||
|
|
||||||
RecordType* = enum rtPerson, rtFamily, rtAddress
|
NAddress* = ref NAddressObj
|
||||||
|
NFamily* = ref NFamilyObj
|
||||||
SyncRecordObj* = object
|
NPerson* = ref NPersonObj
|
||||||
id*: string
|
|
||||||
knownIds*: HashSet[string]
|
|
||||||
recType*: RecordType
|
|
||||||
notionId*: string
|
|
||||||
pcoId*: string
|
|
||||||
|
|
||||||
|
|
||||||
Address* = ref AddressObj
|
|
||||||
Family* = ref FamilyObj
|
|
||||||
Person* = ref PersonObj
|
|
||||||
SyncRecord* = ref SyncRecordObj
|
|
||||||
|
|
||||||
MembershipDataSet* = ref object
|
|
||||||
addresses*: TableRef[string, Address]
|
|
||||||
families*: TableRef[string, Family]
|
|
||||||
people*: TableRef[string, Person]
|
|
||||||
|
|
||||||
func sameContents[T](a: seq[T], b: seq[T]): bool =
|
func sameContents[T](a: seq[T], b: seq[T]): bool =
|
||||||
let aCount = toCountTable(a)
|
let aCount = toCountTable(a)
|
||||||
@ -83,13 +57,13 @@ func sameContents[T](a: seq[T], b: seq[T]): bool =
|
|||||||
return aCount.len == bCount.len
|
return aCount.len == bCount.len
|
||||||
|
|
||||||
|
|
||||||
func `==`*(a, b: Family): bool =
|
func `==`*(a, b: NFamily): bool =
|
||||||
return a.name == b.name and
|
return a.name == b.name and
|
||||||
sameContents(a.headOfHouseholdIds, b.headOfHouseholdIds) and
|
sameContents(a.headOfHouseholdIds, b.headOfHouseholdIds) and
|
||||||
sameContents(a.memberIds, b.memberIds) and
|
sameContents(a.primaryAddressId, b.primaryAddressId) and
|
||||||
a.primaryAddressId == b.primaryAddressId
|
sameContents(a.memberIds, b.memberIds)
|
||||||
|
|
||||||
func `==`*(a, b: Person): bool =
|
func `==`*(a, b: NPerson): bool =
|
||||||
return a.preferredName == b.preferredName and
|
return a.preferredName == b.preferredName and
|
||||||
a.firstName == b.firstName and
|
a.firstName == b.firstName and
|
||||||
a.middleNames == b.middleNames and
|
a.middleNames == b.middleNames and
|
||||||
@ -99,51 +73,33 @@ func `==`*(a, b: Person): bool =
|
|||||||
a.primaryPhoneNumber == b.primaryPhoneNumber and
|
a.primaryPhoneNumber == b.primaryPhoneNumber and
|
||||||
a.primaryEmailAddress == b.primaryEmailAddress and
|
a.primaryEmailAddress == b.primaryEmailAddress and
|
||||||
a.anniversary == b.anniversary and
|
a.anniversary == b.anniversary and
|
||||||
a.marriedToId == b.marriedToId and
|
|
||||||
sameContents(a.addressIds, b.addressIds) and
|
sameContents(a.addressIds, b.addressIds) and
|
||||||
|
sameContents(a.marriedToId, b.marriedToId) and
|
||||||
sameContents(a.parentIds, b.parentIds) and
|
sameContents(a.parentIds, b.parentIds) and
|
||||||
sameContents(a.childIds, b.childIds) and
|
sameContents(a.childIds, b.childIds) and
|
||||||
sameContents(a.relationshipToHff, b.relationshipToHff)
|
sameContents(a.relationshipToHff, b.relationshipToHff)
|
||||||
|
|
||||||
func `$`*(rt: RecordType): string =
|
func toPage*(a: NAddress): JsonNode =
|
||||||
case rt:
|
|
||||||
of rtPerson: "Person"
|
|
||||||
of rtFamily: "Family"
|
|
||||||
of rtAddress: "Address"
|
|
||||||
|
|
||||||
func initSyncRecord*(recType: RecordType, notionId, pcoId: string, knownIds = initHashSet[string]()): SyncRecord =
|
|
||||||
result = SyncRecord(
|
|
||||||
recType: recType,
|
|
||||||
notionId: notionId,
|
|
||||||
pcoId: pcoId,
|
|
||||||
knownIds: knownIds)
|
|
||||||
|
|
||||||
result.knownIds.incl(notionId)
|
|
||||||
result.knownIds.incl(pcoId)
|
|
||||||
|
|
||||||
|
|
||||||
func toPage*(a: Address): JsonNode =
|
|
||||||
%*{
|
%*{
|
||||||
"properties": {
|
"properties": {
|
||||||
"City": makeTextProp("rich_text", a.city),
|
"City": makeTextProp("rich_text", a.city),
|
||||||
"State": makeSelectProp(a.state),
|
"State": makeSelectProp(a.state),
|
||||||
"Street Address": makeTextProp("title", a.street),
|
"Street Address": makeTextProp("title", a.street),
|
||||||
"Suite or Building": makeTextProp("rich_text", a.suiteOrBuilding),
|
|
||||||
"Zip Code": makeTextProp("rich_text", a.zipCode)
|
"Zip Code": makeTextProp("rich_text", a.zipCode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPage*(f: Family): JsonNode =
|
func toPage*(f: NFamily): JsonNode =
|
||||||
%*{
|
%*{
|
||||||
"properties": {
|
"properties": {
|
||||||
"Name": makeTextProp("title", f.name),
|
"Name": makeTextProp("title", f.name),
|
||||||
"Head(s) of Household": makeRelationProp(f.headOfHouseholdIds),
|
"Head(s) of Household": makeRelationProp(f.headOfHouseholdIds),
|
||||||
"Primary Address": makeRelationProp(@[f.primaryAddressId]),
|
"Primary Address": makeRelationProp(f.primaryAddressId),
|
||||||
"Members": makeRelationProp(f.memberIds)
|
"Members": makeRelationProp(f.memberIds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPage*(p: Person): JsonNode =
|
func toPage*(p: NPerson): JsonNode =
|
||||||
%*{
|
%*{
|
||||||
"properties": {
|
"properties": {
|
||||||
"Preferred Name": makeTextProp("title", p.preferredName),
|
"Preferred Name": makeTextProp("title", p.preferredName),
|
||||||
@ -156,59 +112,35 @@ func toPage*(p: Person): JsonNode =
|
|||||||
"Email Address": { "email": p.primaryEmailAddress },
|
"Email Address": { "email": p.primaryEmailAddress },
|
||||||
"Relationship to HFF": makeMultiSelectProp(p.relationshipToHff),
|
"Relationship to HFF": makeMultiSelectProp(p.relationshipToHff),
|
||||||
"Address": makeRelationProp(p.addressIds),
|
"Address": makeRelationProp(p.addressIds),
|
||||||
"Married To": makeRelationProp(@[p.marriedToId]),
|
"Married To": makeRelationProp(p.marriedToId),
|
||||||
"Anniversary": makeDateProp(p.anniversary),
|
"Anniversary": makeDateProp(p.anniversary),
|
||||||
"Parents": makeRelationProp(p.parentIds),
|
"Parents": makeRelationProp(p.parentIds),
|
||||||
"Children": makeRelationProp(p.childIds),
|
"Children": makeRelationProp(p.childIds),
|
||||||
"System: API Permissions": makeMultiSelectProp(p.apiPermissions),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPage*(sr: SyncRecord): JsonNode =
|
proc parseNAddress*(page: JsonNode): NAddress =
|
||||||
result = %*{
|
result = NAddress(
|
||||||
"properties": {
|
|
||||||
"Notion Rec Id": makeTextProp("title", sr.notionId),
|
|
||||||
"PCO Rec Id": makeTextProp("rich_text", sr.pcoId),
|
|
||||||
"Alternate IDs": makeTextProp("rich_text", toSeq(sr.knownIds).join(";"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case sr.recType
|
|
||||||
of rtAddress: result{"properties", "Rec Type"}= %"Address"
|
|
||||||
of rtFamily: result{"properties", "Rec Type"}= %"Family"
|
|
||||||
of rtPerson: result{"properties", "Rec Type"}= %"Person"
|
|
||||||
|
|
||||||
proc addressFromPage*(page: JsonNode): Address =
|
|
||||||
result = Address(
|
|
||||||
id: page["id"].getStr,
|
id: page["id"].getStr,
|
||||||
city: page.getText("City"),
|
city: page.getText("City"),
|
||||||
state: page.getSelect("State"),
|
state: page.getSelect("State"),
|
||||||
street: page.getTitle("Street Address"),
|
street: page.getTitle("Street Address"),
|
||||||
suiteOrBuilding: page.getText("Suite or Building"),
|
|
||||||
zipCode: page.getText("Zip Code"),
|
zipCode: page.getText("Zip Code"),
|
||||||
createdAt: some(parseIso8601(page["created_time"].getStr)),
|
createdAt: some(parseIso8601(page["created_time"].getStr)),
|
||||||
lastUpdatedAt: some(parseIso8601(page["last_edited_time"].getStr)))
|
lastUpdatedAt: some(parseIso8601(page["last_edited_time"].getStr)))
|
||||||
|
|
||||||
proc familyFromPage*(page: JsonNode): Family =
|
proc parseNFamily*(page: JsonNode): NFamily =
|
||||||
let primaryAddressIds = page.getRelationIds("Primary Address")
|
result = NFamily(
|
||||||
result = Family(
|
|
||||||
id: page["id"].getStr,
|
id: page["id"].getStr,
|
||||||
name: page.getTitle("Name"),
|
name: page.getTitle("Name"),
|
||||||
headsOfHousehold: page.getRolledupRecordTitles("Head(s) of Household (display)"),
|
|
||||||
headOfHouseholdIds: page.getRelationIds("Head(s) of Household"),
|
headOfHouseholdIds: page.getRelationIds("Head(s) of Household"),
|
||||||
primaryAddress: if primaryAddressIds.len == 0: ""
|
primaryAddressId: page.getRelationIds("Primary Address"),
|
||||||
else: page.getRolledupRecordTitles("Primary Address (display)")[0],
|
|
||||||
primaryAddressId: if primaryAddressIds.len == 0: ""
|
|
||||||
else: primaryAddressIds[0],
|
|
||||||
members: page.getRolledupRecordTitles("Members (display)"),
|
|
||||||
memberIds: page.getRelationIds("Members"),
|
memberIds: page.getRelationIds("Members"),
|
||||||
createdAt: some(parseIso8601(page["created_time"].getStr)),
|
createdAt: some(parseIso8601(page["created_time"].getStr)),
|
||||||
lastUpdatedAt: some(parseIso8601(page["last_edited_time"].getStr)))
|
lastUpdatedAt: some(parseIso8601(page["last_edited_time"].getStr)))
|
||||||
|
|
||||||
proc personFromPage*(page: JsonNode): Person =
|
proc parseNPerson*(page: JsonNode): NPerson =
|
||||||
let marriedToIds = page.getRelationIds("Married To")
|
result = NPerson(
|
||||||
|
|
||||||
result = Person(
|
|
||||||
id: page["id"].getStr,
|
id: page["id"].getStr,
|
||||||
preferredName: page.getTitle("Preferred Name"),
|
preferredName: page.getTitle("Preferred Name"),
|
||||||
firstName: page.getText("First Name"),
|
firstName: page.getText("First Name"),
|
||||||
@ -219,29 +151,10 @@ proc personFromPage*(page: JsonNode): Person =
|
|||||||
primaryPhoneNumber: page.getPhone("Primary Phone Number"),
|
primaryPhoneNumber: page.getPhone("Primary Phone Number"),
|
||||||
primaryEmailAddress: page.getEmail("Email Address"),
|
primaryEmailAddress: page.getEmail("Email Address"),
|
||||||
relationshipToHff: page.getMultiSelect("Relationship to HFF"),
|
relationshipToHff: page.getMultiSelect("Relationship to HFF"),
|
||||||
addresses: page.getRollupArrayValues("Full Address").mapIt(it{"formula", "string"}.getStr),
|
|
||||||
addressIds: page.getRelationIds("Address"),
|
addressIds: page.getRelationIds("Address"),
|
||||||
marriedTo: if marriedToIds.len == 0: ""
|
marriedToId: page.getRelationIds("Married To"),
|
||||||
else: page.getRolledupRecordTitles("Married To (display)")[0],
|
|
||||||
marriedToId: if marriedToIds.len == 0: ""
|
|
||||||
else: marriedToIds[0],
|
|
||||||
anniversary: page.getDateTime("Anniversary"),
|
anniversary: page.getDateTime("Anniversary"),
|
||||||
parents: page.getRolledupRecordTitles("Parents (display)"),
|
|
||||||
parentIds: page.getRelationIds("Parents"),
|
parentIds: page.getRelationIds("Parents"),
|
||||||
children: page.getRolledupRecordTitles("Children (display)"),
|
|
||||||
childIds: page.getRelationIds("Children"),
|
childIds: page.getRelationIds("Children"),
|
||||||
createdAt: some(parseIso8601(page["created_time"].getStr)),
|
createdAt: some(parseIso8601(page["created_time"].getStr)),
|
||||||
lastUpdatedAt: some(parseIso8601(page["last_edited_time"].getStr)),
|
lastUpdatedAt: some(parseIso8601(page["last_edited_time"].getStr)))
|
||||||
apiPermissions: page.getMultiSelect("System: API Permissions"))
|
|
||||||
|
|
||||||
func syncRecordFromPage*(page: JsonNode): SyncRecord =
|
|
||||||
result = SyncRecord(
|
|
||||||
id: page["id"].getStr,
|
|
||||||
notionId: page.getTitle("Notion Rec Id"),
|
|
||||||
pcoId: page.getText("PCO Rec Id"),
|
|
||||||
knownIds: toHashSet(page.getText("Alternate IDs").split(";")))
|
|
||||||
|
|
||||||
case page.getSelect("Record Type")
|
|
||||||
of "Address": result.recType = rtAddress
|
|
||||||
of "Family": result.recType = rtFamily
|
|
||||||
of "Person": result.recType = rtPerson
|
|
Loading…
x
Reference in New Issue
Block a user