From d33cc0c786df7f204b417486111d442fa2e5234f Mon Sep 17 00:00:00 2001 From: Tobias Lins Date: Tue, 21 Apr 2020 19:05:18 +0200 Subject: [PATCH] Resolve users from table --- src/api/notion.ts | 11 +++++------ src/api/types.ts | 5 ++++- src/api/utils.ts | 2 +- src/index.ts | 36 +++++++++++++++++++++++++++++++----- src/routes/page.ts | 3 ++- src/routes/table.ts | 31 ++++++++++++++++++++----------- src/routes/user.ts | 7 ++++--- src/types.d.ts | 30 ++++++++++++++++++++++++++++++ 8 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 src/types.d.ts diff --git a/src/api/notion.ts b/src/api/notion.ts index 06623f8..8e3840f 100644 --- a/src/api/notion.ts +++ b/src/api/notion.ts @@ -1,5 +1,3 @@ -import { resolve } from "dns"; - const NOTION_API = "https://www.notion.so/api/v3"; type JSONData = @@ -29,7 +27,6 @@ const fetchNotionData = async ({ resource, body }: INotionParams) => { "content-type": "application/json", }, body: JSON.stringify(body), - //cf: {} }); return res.json(); @@ -74,7 +71,7 @@ export const fetchTableData = async ( return table; }; -export const fetchNotionUser = async ( +export const fetchNotionUsers = async ( userIds: string[] ): Promise<{ id: string; full_name: string }[]> => { const users = await fetchNotionData({ @@ -83,11 +80,13 @@ export const fetchNotionUser = async ( requests: userIds.map((id) => ({ id, table: "notion_user" })), }, }); - return users.results.map((u: any) => { const user = { id: u.value.id, - full_name: u.value.given_name + " " + u.value.family_name, + firstName: u.value.given_name, + lastLame: u.value.family_name, + fullName: u.value.given_name + " " + u.value.family_name, + profilePhoto: u.value.profile_photo, }; return user; }); diff --git a/src/api/types.ts b/src/api/types.ts index 5a0b1c1..950fbef 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -41,12 +41,15 @@ export type ColumnSchemaType = { type: ColumnType; }; +type UserType = { id: string; full_name: string }; + export type RowContentType = | string | boolean | number | string[] - | { title: string; id: string }; + | { title: string; id: string } + | UserType[]; export interface BaseValueType { id: string; diff --git a/src/api/utils.ts b/src/api/utils.ts index 4fca68a..d001be3 100644 --- a/src/api/utils.ts +++ b/src/api/utils.ts @@ -31,7 +31,7 @@ export const getNotionValue = ( case "select": return val[0][0]; case "multi_select": - return val[0] as string[]; + return val[0][0].split(",") as string[]; case "number": return Number(val[0][0]); default: diff --git a/src/index.ts b/src/index.ts index 44a96ed..e5cc4eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,13 @@ import {} from "@cloudflare/workers-types"; -import { Router, Method } from "tiny-request-router"; +import { Router, Method, Params } from "tiny-request-router"; import { pageRoute } from "./routes/page"; import { tableRoute } from "./routes/table"; import { userRoute } from "./routes/user"; -const router = new Router(); +export type Handler = (params: Params) => Promise | Response; + +const router = new Router(); router.options("*", () => new Response("", { headers: {} })); router.get("/v1/page/:pageId", pageRoute); @@ -14,16 +16,19 @@ router.get("/v1/user/:userId", userRoute); router.get( "*", - async (event: FetchEvent) => + async () => new Response( `Route not found! Available routes: - /v1/page/:pageId - - /v1/table/:pageId`, + - /v1/table/:pageId + - /v1/user/:pageId`, { status: 404 } ) ); +const cache = (caches as any).default; + const handleRequest = async (fetchEvent: FetchEvent): Promise => { const request = fetchEvent.request; const { pathname } = new URL(request.url); @@ -34,7 +39,28 @@ const handleRequest = async (fetchEvent: FetchEvent): Promise => { return new Response("Endpoint not found.", { status: 404 }); } - return match.handler(match.params); + const cacheKey = request.url; + let response; + try { + const cachedResponse = await cache.match(cacheKey); + if (cachedResponse) { + response = cachedResponse; + } + } catch (err) {} + + const getResponseAndPersist = async () => { + const res = await match.handler(match.params); + + await cache.put(cacheKey, res.clone()); + return res; + }; + + if (response) { + fetchEvent.waitUntil(getResponseAndPersist()); + return response; + } + + return getResponseAndPersist(); }; self.addEventListener("fetch", async (event: Event) => { diff --git a/src/routes/page.ts b/src/routes/page.ts index 4d3b96d..c5b9952 100644 --- a/src/routes/page.ts +++ b/src/routes/page.ts @@ -1,8 +1,9 @@ +import { Params } from "tiny-request-router"; import { fetchPageById } from "../api/notion"; import { parsePageId } from "../api/utils"; import { createResponse } from "../response"; -export async function pageRoute(params: { pageId: string }) { +export async function pageRoute(params: Params) { const pageId = parsePageId(params.pageId); const res = await fetchPageById(pageId); diff --git a/src/routes/table.ts b/src/routes/table.ts index 0c82334..6f1e3f1 100644 --- a/src/routes/table.ts +++ b/src/routes/table.ts @@ -1,13 +1,15 @@ -import { fetchPageById, fetchTableData } from "../api/notion"; +import { Params } from "tiny-request-router"; +import { fetchPageById, fetchTableData, fetchNotionUsers } from "../api/notion"; import { parsePageId, getNotionValue } from "../api/utils"; import { RowContentType, CollectionType, RowType } from "../api/types"; import { createResponse } from "../response"; -export async function tableRoute(params: { pageId: string }) { +export async function tableRoute(params: Params) { const pageId = parsePageId(params.pageId); const page = await fetchPageById(pageId); - console.log({ page }); + if (!page.recordMap.collection) + return new Response("No table found on Notion page: " + pageId); const collection: CollectionType = Object.keys(page.recordMap.collection).map( (k) => page.recordMap.collection[k] @@ -23,8 +25,6 @@ export async function tableRoute(params: { pageId: string }) { collectionView.value.id ); - console.log({ table }); - const collectionRows = collection.value.schema; const collectionColKeys = Object.keys(collectionRows); @@ -37,17 +37,26 @@ export async function tableRoute(params: { pageId: string }) { b.value && b.value.properties && b.value.parent_id === collection.value.id ); - const rows = tableData.map((td) => { - let row: { [key: string]: RowContentType } = { id: td.value.id }; - collectionColKeys.forEach((key) => { + type Row = { id: string; [key: string]: RowContentType }; + + const rows: Row[] = []; + + for (const td of tableData) { + let row: Row = { id: td.value.id }; + + for (const key of collectionColKeys) { const val = td.value.properties[key]; if (val) { const schema = collectionRows[key]; row[schema.name] = getNotionValue(val, schema.type); + if (schema.type === "person") { + const users = await fetchNotionUsers(row[schema.name] as string[]); + row[schema.name] = users; + } } - }); - return row; - }); + } + rows.push(row); + } return createResponse(JSON.stringify(rows)); } diff --git a/src/routes/user.ts b/src/routes/user.ts index 6a825e6..73ac375 100644 --- a/src/routes/user.ts +++ b/src/routes/user.ts @@ -1,8 +1,9 @@ -import { fetchNotionUser } from "../api/notion"; +import { Params } from "tiny-request-router"; +import { fetchNotionUsers } from "../api/notion"; import { createResponse } from "../response"; -export async function userRoute(params: { userId: string }) { - const users = await fetchNotionUser([params.userId]); +export async function userRoute(params: Params) { + const users = await fetchNotionUsers([params.userId]); return createResponse(JSON.stringify(users[0])); } diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..18b6dc2 --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,30 @@ +interface CacheOptions { + /** + * Consider the request method a GET regardless of its actual value. + */ + ignoreMethod?: boolean; +} + +export interface Caches { + default: { + /** + * Adds to the cache a response keyed to the given request. + * Returns a promise that resolves to undefined once the cache stores the response. + */ + put(request: Request | string, response: Response): Promise; + /** + * Returns a promise wrapping the response object keyed to that request. + */ + match( + request: Request | string, + options?: CacheOptions + ): Promise; + /** + * Deletes the Response object from the cache and + * returns a Promise for a Boolean response + */ + delete(request: Request | string, options?: CacheOptions): Promise; + }; +} + +declare let caches: Caches;