Skip to content
This repository has been archived by the owner on Nov 3, 2022. It is now read-only.

Commit

Permalink
Improve mapping, v1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
petersamokhin committed Nov 25, 2020
1 parent 01c8336 commit 1dabdf5
Show file tree
Hide file tree
Showing 21 changed files with 786 additions and 346 deletions.
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ repositories {
jcenter()
}

group = "com.petersamokhin.notionapi"
version = "0.0.6"
group = "com.petersamokhin.notionsdk"
version = "1.0.0"


dependencies {
Expand Down
6 changes: 3 additions & 3 deletions buildSrc/src/main/kotlin/Config.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
object Config {
object Versions {
object Kotlin {
const val kotlin = "1.4.20-RC"
const val coroutines = "1.4.1"
const val kotlin = "1.4.20"
const val serialization = "1.0.1"
}
const val ktor = "1.4.1"

const val ktor = "1.4.2"
}
}
2 changes: 1 addition & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rootProject.name = "notionapi"
rootProject.name = "notion-sdk-kotlin"
44 changes: 36 additions & 8 deletions src/main/kotlin/com/petersamokhin/notionapi/Notion.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package com.petersamokhin.notionapi

import com.petersamokhin.notionapi.model.LoadPageChunkRequestBody
import com.petersamokhin.notionapi.model.Loader
import com.petersamokhin.notionapi.model.NotionResponse
import com.petersamokhin.notionapi.model.QueryCollectionRequestBody
import com.petersamokhin.notionapi.model.NotionCredentials
import com.petersamokhin.notionapi.model.error.NotionAuthException
import com.petersamokhin.notionapi.model.request.LoadPageChunkRequestBody
import com.petersamokhin.notionapi.model.request.Loader
import com.petersamokhin.notionapi.model.request.QueryCollectionRequestBody
import com.petersamokhin.notionapi.model.response.NotionResponse
import com.petersamokhin.notionapi.request.LoadPageChunkRequest
import com.petersamokhin.notionapi.request.QueryNotionCollectionRequest
import com.petersamokhin.notionapi.request.base.NotionRequest
import io.ktor.client.*
import io.ktor.client.features.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.*

class Notion(private val token: String, private var httpClient: HttpClient) {
class Notion internal constructor(token: String, private var httpClient: HttpClient) {
init {
httpClient = httpClient.config {
defaultRequest {
Expand All @@ -21,14 +24,12 @@ class Notion(private val token: String, private var httpClient: HttpClient) {
}
}

@KtorExperimentalAPI
suspend fun loadPage(pageId: String, limit: Int = 50): NotionResponse {
return LoadPageChunkRequest(httpClient).execute(
LoadPageChunkRequestBody(pageId, limit, 0, false)
)
}

@KtorExperimentalAPI
suspend fun queryCollection(collectionId: String, collectionViewId: String, limit: Int = 70): NotionResponse {
return QueryNotionCollectionRequest(httpClient).execute(
QueryCollectionRequestBody(
Expand All @@ -39,7 +40,34 @@ class Notion(private val token: String, private var httpClient: HttpClient) {

fun close() = httpClient.close()

fun setHttpClient(newHttpClient: HttpClient) {
httpClient = newHttpClient
}

companion object {
private const val NOTION_TOKEN_COOKIE_KEY = "token_v2"

@JvmStatic
fun fromToken(token: String, httpClient: HttpClient): Notion {
return Notion(token, httpClient)
}

@JvmStatic
suspend fun fromEmailAndPassword(credentials: NotionCredentials, httpClient: HttpClient): Notion {
val endpoint = "${NotionRequest.API_BASE_URL}/${NotionRequest.Endpoint.LOGIN_WITH_EMAIL}"
val response = httpClient.post<HttpResponse>(endpoint) {
headers.appendAll(NotionRequest.BASE_HEADERS)
contentType(ContentType.Application.Json)
body = credentials
}

val token = response.headers.getAll(HttpHeaders.SetCookie)?.firstOrNull {
it.contains("$NOTION_TOKEN_COOKIE_KEY=", true)
}?.split("; ")?.firstOrNull {
it.contains("$NOTION_TOKEN_COOKIE_KEY=", true)
}?.split("=")?.getOrNull(1) ?: throw NotionAuthException("No $NOTION_TOKEN_COOKIE_KEY in headers!")

return fromToken(token, httpClient)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.petersamokhin.notionapi.mapper

import com.petersamokhin.notionapi.model.NotionColumn
import com.petersamokhin.notionapi.model.NotionProperty
import com.petersamokhin.notionapi.model.response.NotionCollection
import com.petersamokhin.notionapi.model.response.NotionColumnType
import com.petersamokhin.notionapi.serializer.NotionBooleanSerializer
import com.petersamokhin.notionapi.utils.contentAsStringOrNull
import com.petersamokhin.notionapi.utils.jsonArrayOrNull
import com.petersamokhin.notionapi.utils.trimNotionTextField
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.decodeFromJsonElement

fun NotionCollection.title() = value.name?.trimNotionTextField()

fun NotionCollection.description() = value.description?.trimNotionTextField()

fun parseNotionColumn(json: Json, name: String, type: NotionColumnType, field: JsonArray?): NotionColumn {
if (field.isNullOrEmpty()) return NotionColumn.SingleValue(name, type, null)
val masterList = field.filterIsInstance<JsonArray>()

return when (type) {
NotionColumnType.Title, NotionColumnType.Text, NotionColumnType.Number,
NotionColumnType.Checkbox, NotionColumnType.Select, NotionColumnType.MultiSelect -> {
val label = masterList.firstOrNull()?.getOrNull(0)?.contentAsStringOrNull
?: return NotionColumn.SingleValue(name, type, null)

return NotionColumn.SingleValue(
name = name, type = type,
value = when (type) {
NotionColumnType.Title -> NotionProperty.Value.Title(label)
NotionColumnType.Text -> NotionProperty.Value.Text(label)
NotionColumnType.Number -> NotionProperty.Value.Number(label.toDouble())
NotionColumnType.Checkbox -> NotionProperty.Value.Checkbox(label == NotionBooleanSerializer.NOTION_TRUE)
NotionColumnType.Select -> NotionProperty.Value.Select(label)
NotionColumnType.MultiSelect -> NotionProperty.Value.MultiSelect(label.split(","))
else -> throw IllegalStateException("exhaustive")
}.let { NotionProperty(label = label, it) }
)
}
NotionColumnType.Email, NotionColumnType.Url, NotionColumnType.PhoneNumber,
NotionColumnType.Person, NotionColumnType.File -> {
NotionColumn.MultiValue(
name = name,
type = type,
values = masterList.map { currentItemList ->
currentItemList.filterIsInstance<JsonArray>().mapNotNull {
currentItemList.firstOrNull()?.contentAsStringOrNull?.let { label ->
it.getOrNull(0)?.jsonArrayOrNull?.getOrNull(1)?.contentAsStringOrNull?.let {
label to it
}
}
}
}.flatten()
.map { (label, item) ->
val v = when (type) {
NotionColumnType.Email -> NotionProperty.Value.Entry.Email(item)
NotionColumnType.Url -> NotionProperty.Value.Entry.Link(item)
NotionColumnType.PhoneNumber -> NotionProperty.Value.Entry.PhoneNumber(item)
NotionColumnType.Person -> NotionProperty.Value.Entry.Person(item)
NotionColumnType.File -> NotionProperty.Value.Entry.File(item)
else -> null
}

NotionProperty(label, v)
}
)
}
NotionColumnType.Date -> {
NotionColumn.SingleValue(
name = name,
type = type,
value = NotionProperty(
label = masterList.firstOrNull()?.getOrNull(0)?.contentAsStringOrNull
?: return NotionColumn.SingleValue(name, type, null),
value = masterList.firstOrNull()?.getOrNull(1)
?.jsonArrayOrNull?.getOrNull(0)
?.jsonArrayOrNull?.getOrNull(1)
?.let<JsonElement, NotionProperty.Value.Entry.Date>(json::decodeFromJsonElement)
)
)
}
NotionColumnType.LastEditedTime, NotionColumnType.LastEditedBy,
NotionColumnType.CreatedTime, NotionColumnType.CreatedBy,
NotionColumnType.Rollup, NotionColumnType.Relation, NotionColumnType.Formula -> {
NotionColumn.SingleValue(name, type, null)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,93 +1,68 @@
package com.petersamokhin.notionapi.mapper

import com.petersamokhin.notionapi.model.*
import com.petersamokhin.notionapi.serializer.NotionBooleanSerializer
import com.petersamokhin.notionapi.utils.trimNotionTextField
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import com.petersamokhin.notionapi.model.response.NotionBlock
import com.petersamokhin.notionapi.model.response.NotionCollection
import com.petersamokhin.notionapi.model.response.NotionResponse
import kotlinx.serialization.json.*

fun NotionCollection.mapTable(blocks: List<NotionBlock>): NotionTable<Map<String, NotionColumn<*>>> {
val rows = blocks.map {
val props = it.value.properties

mutableMapOf<String, NotionColumn<*>>().also { map ->
props?.keys?.forEach { innerRowKey ->
val schemaItem = value.schema[innerRowKey] ?: return@forEach
fun NotionResponse.mapTable(json: Json, sortColumns: Boolean = false): NotionTable? {
val collectionId = recordMap.collectionsMap?.keys?.firstOrNull() ?: return null
val collection = recordMap.collectionsMap[collectionId]

schemaItem.name.also { name ->
val fieldText = props[innerRowKey]?.trimNotionTextField()
val value: NotionColumn.Entry<*> = when (schemaItem.type) {
NotionColumnType.Title, NotionColumnType.Text, NotionColumnType.Select -> {
NotionColumn.Entry.Text(name, fieldText)
}
NotionColumnType.Number -> {
NotionColumn.Entry.Number(name, fieldText?.toDoubleOrNull())
}
NotionColumnType.Checkbox -> {
NotionColumn.Entry.Bool(name, fieldText?.equals(NotionBooleanSerializer.NOTION_TRUE))
}
NotionColumnType.MultiSelect -> {
NotionColumn.Entry.TextList(name, fieldText?.split(","))
}
}
val collectionViewId = recordMap.collectionViewsMap?.keys?.firstOrNull()
val collectionView = recordMap.collectionViewsMap?.get(collectionViewId)
val collectionViewFormat = collectionView?.value?.format

map[name] = NotionColumn(name, schemaItem.type, value)
}
}
}
val sortMap = if (sortColumns && collectionViewFormat != null) {
collectionViewFormat.tableProperties.mapIndexed { index, item -> item.property to index }.toMap()
} else {
null
}

val schema = value.schema.mapKeys { it.value.name }
val blocks = result?.blockIds?.mapNotNull { recordMap.blocksMap?.get(it) }

return NotionTable(rows, schema)
return blocks?.let { collection?.mapTable(json, it, sortMap) }
}

fun NotionResponse.mapTable(): NotionTable<Map<String, NotionColumn<*>>>? {
val collectionId = recordMap.collectionsMap.keys.firstOrNull()
val collection = recordMap.collectionsMap[collectionId]
val blocks = result?.blockIds?.map { recordMap.blocksMap.getValue(it) }

return blocks?.let { collection?.mapTable(it) }
}

fun NotionResponse.mapCollectionToJsonArray(): JsonArray? {
val collectionId = recordMap.collectionsMap.keys.firstOrNull() ?: return null
val collection = recordMap.collectionsMap[collectionId] ?: return null
val blocks = result?.blockIds?.map { recordMap.blocksMap.getValue(it) } ?: return null

val list = blocks.map {
val props = it.value.properties ?: return null
fun NotionCollection.mapTable(
json: Json,
blocks: List<NotionBlock>,
sortMap: Map<String, Int>? = null
): NotionTable {
val rows: List<NotionRow> = blocks.map { block ->
val props = block.value.properties
val propsKeys = props?.keys?.let { propsKeys ->
if (sortMap != null) {
propsKeys.sortedBy { sortMap[it] }
} else {
propsKeys
}
}
val rowItems = mutableMapOf<String, NotionColumn>()

val map = props.keys.mapNotNull { innerRowKey ->
val schemaItem = collection.value.schema[innerRowKey] ?: return@mapNotNull null
propsKeys?.forEach { innerRowKey ->
val schemaItem = value.schema[innerRowKey] ?: return@forEach

schemaItem.name.let { name ->
val fieldText = props[innerRowKey]?.trimNotionTextField()
val value = when (schemaItem.type) {
NotionColumnType.Title, NotionColumnType.Text, NotionColumnType.Select -> {
JsonPrimitive(fieldText)
}
NotionColumnType.Number -> {
JsonPrimitive(fieldText?.toDoubleOrNull())
}
NotionColumnType.Checkbox -> {
JsonPrimitive(fieldText?.equals(NotionBooleanSerializer.NOTION_TRUE))
}
NotionColumnType.MultiSelect -> {
fieldText?.let {
JsonArray(fieldText.split(",").map(::JsonPrimitive))
} ?: JsonNull
}
}
schemaItem.name.also { name ->
val field = props[innerRowKey]

name to value
rowItems[name] = parseNotionColumn(json, name, schemaItem.type, field)
}
}.toMap()
}

JsonObject(map)
NotionRow(
properties = rowItems,
metaInfo = NotionRow.MetaInfo(
lastEditedBy = block.value.lastEditedById,
lastEditedTime = block.value.lastEditedTime,
createdBy = block.value.createdById,
createdTime = block.value.createdTime
)
)
}

return JsonArray(list)
val schema = value.schema.mapKeys { it.value.name }

return NotionTable(rows, schema)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.petersamokhin.notionapi.model

import kotlinx.serialization.Serializable

@Serializable
data class NotionCredentials(
val email: String,
val password: String
)

This file was deleted.

Loading

0 comments on commit 1dabdf5

Please sign in to comment.