mirror of
https://github.com/zhigang1992/notion-api-worker.git
synced 2026-01-12 17:32:42 +08:00
Merge branch 'master' of https://github.com/splitbee/notion-api-worker
This commit is contained in:
@@ -4,6 +4,8 @@ import {
|
||||
NotionUserType,
|
||||
LoadPageChunkData,
|
||||
CollectionData,
|
||||
NotionSearchParamsType,
|
||||
NotionSearchResultsType,
|
||||
} from "./types";
|
||||
|
||||
const NOTION_API = "https://www.notion.so/api/v3";
|
||||
@@ -120,3 +122,34 @@ export const fetchBlocks = async (
|
||||
notionToken,
|
||||
});
|
||||
};
|
||||
|
||||
export const fetchNotionSearch = async (
|
||||
params: NotionSearchParamsType,
|
||||
notionToken?: string
|
||||
) => {
|
||||
// TODO: support other types of searches
|
||||
return fetchNotionData<{ results: NotionSearchResultsType }>({
|
||||
resource: "search",
|
||||
body: {
|
||||
type: "BlocksInAncestor",
|
||||
source: "quick_find_public",
|
||||
ancestorId: params.ancestorId,
|
||||
filters: {
|
||||
isDeletedOnly: false,
|
||||
excludeTemplates: true,
|
||||
isNavigableOnly: true,
|
||||
requireEditPermissions: false,
|
||||
ancestors: [],
|
||||
createdBy: [],
|
||||
editedBy: [],
|
||||
lastEditedTime: {},
|
||||
createdTime: {},
|
||||
...params.filters,
|
||||
},
|
||||
sort: "Relevance",
|
||||
limit: params.limit || 20,
|
||||
query: params.query,
|
||||
},
|
||||
notionToken,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Params } from "tiny-request-router";
|
||||
|
||||
type BoldFormatType = ["b"];
|
||||
type ItalicFormatType = ["i"];
|
||||
type StrikeFormatType = ["s"];
|
||||
@@ -146,3 +148,40 @@ export interface CollectionData {
|
||||
blockIds: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface NotionSearchParamsType {
|
||||
ancestorId: string;
|
||||
query: string;
|
||||
filters?: {
|
||||
isDeletedOnly: boolean;
|
||||
excludeTemplates: boolean;
|
||||
isNavigableOnly: boolean;
|
||||
requireEditPermissions: boolean;
|
||||
};
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export interface NotionSearchResultType {
|
||||
id: string;
|
||||
isNavigable: boolean;
|
||||
score: number;
|
||||
highlight: {
|
||||
pathText: string;
|
||||
text: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface NotionSearchResultsType {
|
||||
recordMap: {
|
||||
block: { [key: string]: RowType };
|
||||
};
|
||||
results: NotionSearchResultType[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface HandlerRequest = {
|
||||
params: Params;
|
||||
searchParams: URLSearchParams;
|
||||
request: Request;
|
||||
notionToken?: string;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,10 @@ export const idToUuid = (path: string) =>
|
||||
)}-${path.substr(16, 4)}-${path.substr(20)}`;
|
||||
|
||||
export const parsePageId = (id: string) => {
|
||||
const rawId = id.replace(/\-/g, "").slice(-32);
|
||||
return idToUuid(rawId);
|
||||
if (id) {
|
||||
const rawId = id.replace(/\-/g, "").slice(-32);
|
||||
return idToUuid(rawId);
|
||||
}
|
||||
};
|
||||
|
||||
export const getNotionValue = (
|
||||
@@ -20,7 +22,7 @@ export const getNotionValue = (
|
||||
return getTextContent(val);
|
||||
case "person":
|
||||
return (
|
||||
val.filter(v => v.length > 1).map(v => v[1]![0][1] as string) || []
|
||||
val.filter((v) => v.length > 1).map((v) => v[1]![0][1] as string) || []
|
||||
);
|
||||
case "checkbox":
|
||||
return val[0][0] === "Yes";
|
||||
|
||||
23
src/index.ts
23
src/index.ts
@@ -4,19 +4,27 @@ import { Router, Method, Params } from "tiny-request-router";
|
||||
import { pageRoute } from "./routes/page";
|
||||
import { tableRoute } from "./routes/table";
|
||||
import { userRoute } from "./routes/user";
|
||||
import { searchRoute } from "./routes/search";
|
||||
import { createResponse } from "./response";
|
||||
import * as types from "./api/types";
|
||||
|
||||
export type Handler = (
|
||||
params: Params,
|
||||
notionToken?: string
|
||||
req: types.HandlerRequest
|
||||
) => Promise<Response> | Response;
|
||||
|
||||
const corsHeaders = {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Methods": "GET, HEAD, POST, OPTIONS",
|
||||
};
|
||||
|
||||
const router = new Router<Handler>();
|
||||
|
||||
router.options("*", () => new Response("", { headers: {} }));
|
||||
router.options("*", () => new Response(null, { headers: corsHeaders }));
|
||||
router.get("/v1/page/:pageId", pageRoute);
|
||||
router.get("/v1/table/:pageId", tableRoute);
|
||||
router.get("/v1/user/:userId", userRoute);
|
||||
router.get("/v1/search", searchRoute);
|
||||
|
||||
router.get("*", async () =>
|
||||
createResponse(
|
||||
@@ -35,7 +43,7 @@ const NOTION_API_TOKEN =
|
||||
|
||||
const handleRequest = async (fetchEvent: FetchEvent): Promise<Response> => {
|
||||
const request = fetchEvent.request;
|
||||
const { pathname } = new URL(request.url);
|
||||
const { pathname, searchParams } = new URL(request.url);
|
||||
const notionToken =
|
||||
NOTION_API_TOKEN ||
|
||||
(request.headers.get("Authorization") || "").split("Bearer ")[1] ||
|
||||
@@ -57,7 +65,12 @@ const handleRequest = async (fetchEvent: FetchEvent): Promise<Response> => {
|
||||
} catch (err) {}
|
||||
|
||||
const getResponseAndPersist = async () => {
|
||||
const res = await match.handler(match.params, notionToken);
|
||||
const res = await match.handler({
|
||||
request,
|
||||
searchParams,
|
||||
params: match.params,
|
||||
notionToken,
|
||||
});
|
||||
|
||||
await cache.put(cacheKey, res.clone());
|
||||
return res;
|
||||
|
||||
@@ -3,30 +3,39 @@ import { fetchPageById, fetchBlocks } from "../api/notion";
|
||||
import { parsePageId } from "../api/utils";
|
||||
import { createResponse } from "../response";
|
||||
import { getTableData } from "./table";
|
||||
import { CollectionType, BlockType } from "../api/types";
|
||||
import { CollectionType, BlockType, HandlerRequest } from "../api/types";
|
||||
|
||||
export async function pageRoute(params: Params, notionToken?: string) {
|
||||
const pageId = parsePageId(params.pageId);
|
||||
const page = await fetchPageById(pageId, notionToken);
|
||||
export async function pageRoute(req: HandlerRequest) {
|
||||
const pageId = parsePageId(req.params.pageId);
|
||||
const page = await fetchPageById(pageId, req.notionToken);
|
||||
|
||||
const baseBlocks = page.recordMap.block;
|
||||
const baseBlockKeys = Object.keys(baseBlocks);
|
||||
|
||||
const pendingBlocks = baseBlockKeys.flatMap((blockId) => {
|
||||
const block = baseBlocks[blockId];
|
||||
const content = block.value.content;
|
||||
|
||||
return content ? content.filter((id: string) => !baseBlocks[id]) : [];
|
||||
});
|
||||
|
||||
const additionalBlocks = await fetchBlocks(pendingBlocks).then(
|
||||
(res) => res.recordMap.block
|
||||
);
|
||||
|
||||
const allBlocks: { [id: string]: BlockType & { data?: any } } = {
|
||||
let allBlocks: { [id: string]: BlockType & { data?: any } } = {
|
||||
...baseBlocks,
|
||||
...additionalBlocks
|
||||
};
|
||||
let allBlockKeys;
|
||||
|
||||
while (true) {
|
||||
allBlockKeys = Object.keys(allBlocks);
|
||||
|
||||
const pendingBlocks = allBlockKeys.flatMap((blockId) => {
|
||||
const block = allBlocks[blockId];
|
||||
const content = block.value.content;
|
||||
|
||||
return content ? content.filter((id: string) => !allBlocks[id]) : [];
|
||||
});
|
||||
|
||||
if (!pendingBlocks.length) {
|
||||
break;
|
||||
}
|
||||
|
||||
const newBlocks = await fetchBlocks(pendingBlocks).then(
|
||||
(res) => res.recordMap.block
|
||||
);
|
||||
|
||||
allBlocks = { ...allBlocks, ...newBlocks };
|
||||
}
|
||||
|
||||
const collection = page.recordMap.collection
|
||||
? page.recordMap.collection[Object.keys(page.recordMap.collection)[0]]
|
||||
@@ -41,8 +50,8 @@ export async function pageRoute(params: Params, notionToken?: string) {
|
||||
: null;
|
||||
|
||||
if (collection && collectionView) {
|
||||
const pendingCollections = baseBlockKeys.flatMap((blockId) => {
|
||||
const block = baseBlocks[blockId];
|
||||
const pendingCollections = allBlockKeys.flatMap((blockId) => {
|
||||
const block = allBlocks[blockId];
|
||||
|
||||
return block.value.type === "collection_view" ? [block.value.id] : [];
|
||||
});
|
||||
@@ -51,12 +60,12 @@ export async function pageRoute(params: Params, notionToken?: string) {
|
||||
const data = await getTableData(
|
||||
collection,
|
||||
collectionView.value.id,
|
||||
notionToken
|
||||
req.notionToken
|
||||
);
|
||||
|
||||
allBlocks[b] = {
|
||||
...allBlocks[b],
|
||||
data
|
||||
data,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
29
src/routes/search.ts
Normal file
29
src/routes/search.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { fetchNotionSearch } from "../api/notion";
|
||||
import { createResponse } from "../response";
|
||||
import { HandlerRequest } from "../api/types";
|
||||
import { parsePageId } from "../api/utils";
|
||||
|
||||
export async function searchRoute(req: HandlerRequest) {
|
||||
const ancestorId = parsePageId(req.searchParams.get("ancestorId"));
|
||||
const query = req.searchParams.get("query") || "";
|
||||
const limit = req.searchParams.get("limit") || 20;
|
||||
|
||||
if (!ancestorId) {
|
||||
return createResponse(
|
||||
{ error: 'missing required "ancestorId"' },
|
||||
{ "Content-Type": "application/json" },
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
const results = await fetchNotionSearch(
|
||||
{
|
||||
ancestorId,
|
||||
query,
|
||||
limit,
|
||||
},
|
||||
req.notionToken
|
||||
);
|
||||
|
||||
return createResponse(results);
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
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 {
|
||||
RowContentType,
|
||||
CollectionType,
|
||||
RowType,
|
||||
HandlerRequest,
|
||||
} from "../api/types";
|
||||
import { createResponse } from "../response";
|
||||
|
||||
export const getTableData = async (
|
||||
@@ -51,9 +56,9 @@ export const getTableData = async (
|
||||
return rows;
|
||||
};
|
||||
|
||||
export async function tableRoute(params: Params, notionToken?: string) {
|
||||
const pageId = parsePageId(params.pageId);
|
||||
const page = await fetchPageById(pageId, notionToken);
|
||||
export async function tableRoute(req: HandlerRequest) {
|
||||
const pageId = parsePageId(req.params.pageId);
|
||||
const page = await fetchPageById(pageId, req.notionToken);
|
||||
|
||||
if (!page.recordMap.collection)
|
||||
return createResponse(
|
||||
@@ -75,7 +80,7 @@ export async function tableRoute(params: Params, notionToken?: string) {
|
||||
const rows = await getTableData(
|
||||
collection,
|
||||
collectionView.value.id,
|
||||
notionToken
|
||||
req.notionToken
|
||||
);
|
||||
|
||||
return createResponse(rows);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { Params } from "tiny-request-router";
|
||||
import { fetchNotionUsers } from "../api/notion";
|
||||
import { HandlerRequest } from "../api/types";
|
||||
import { createResponse } from "../response";
|
||||
|
||||
export async function userRoute(params: Params, notionToken?: string) {
|
||||
const users = await fetchNotionUsers([params.userId], notionToken);
|
||||
export async function userRoute(req: HandlerRequest) {
|
||||
const users = await fetchNotionUsers([req.params.userId], req.notionToken);
|
||||
|
||||
return createResponse(users[0]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user