Skip to content

Commit

Permalink
feat: add data cache for Notion API
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-guoba committed Mar 25, 2024
1 parent 98360c0 commit 534f643
Show file tree
Hide file tree
Showing 16 changed files with 258 additions and 125 deletions.
12 changes: 5 additions & 7 deletions app/(blog)/article/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@ import Link from "next/link";
import { Metadata } from "next";

import { RenderBlock } from "@/app/notion/render";
import { retrieveBlockChildren, retrievePage } from "@/app/notion/api";
import Shell from "@/components/shells/shell";
import React from "react";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeftIcon } from "@radix-ui/react-icons";
import { formatDate } from "@/lib/utils";
import { PageHeader, PageHeaderDescription, PageHeaderHeading } from "@/components/page-header";
// import { Separator } from "@/components/ui/separator";

import { siteMeta } from "@/config/meta";
import { pagePublished, rawText } from "@/app/notion/block-parse";
import { notFound } from "next/navigation";
import { env } from "@/env.mjs";
import { ContentLoadingSkeleton } from "@/components/post-skeleton";
import { NotionApiCache } from "@/app/notion/cache";

// https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#revalidate
export const revalidate = env.REVALIDATE_PAGES; // revalidate the data interval
// // export const dynamic = 'force-dynamic';
// // export const revalidate = 0;
// // export const dynamicParams = true; // true | false,

// export const dynamic = 'force-dynamic';

/**
* If you want to enable static rendering, uncommment the following function
Expand Down Expand Up @@ -67,7 +65,7 @@ async function parseSlug(slug: string[]) {
if (slug.length >= 1) {
pageID = slug[0];

const page: any = await retrievePage(pageID);
const page: any = await NotionApiCache.RetrievePage(pageID);
if (page && pagePublished(page)) {
summary = rawText(page?.properties?.Summary?.rich_text);
// May be linked from child-page not in database list which still have a default title property
Expand All @@ -79,7 +77,7 @@ async function parseSlug(slug: string[]) {
}

async function ContentRender({ pageID }: { pageID: string }) {
const blocks = await retrieveBlockChildren(pageID);
const blocks = await NotionApiCache.RetrieveBlockChildren(pageID);
if (!blocks) {
return <div />;
}
Expand Down
7 changes: 4 additions & 3 deletions app/(blog)/db/[...row]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ import { PageHeader, PageHeaderDescription, PageHeaderHeading } from "@/componen
import { Separator } from "@/components/ui/separator";

// import { env } from "@/env.mjs";
import { QueryDatabase, RetrieveDatabase } from "@/app/notion/api";
// import { QueryDatabase } from "@/app/notion/api";
import { DatabaseRenderer } from "@/app/notion/_components/db/database";
import { IconRender } from "@/app/notion/_components/emoji";
import RichText from "@/app/notion/text";
import { filterBase } from "@/app/notion/block-parse";
import { NotionApiCache } from "@/app/notion/cache";

export default async function Page({ params }: { params: { row: string[] } }) {
const dbID = params.row[0];
const columns: any = await RetrieveDatabase(dbID);
const columns: any = await NotionApiCache.RetrieveDatabase(dbID);
if (!columns) {
return null;
}

const queryParam = filterBase(dbID);
const data = await QueryDatabase(dbID, queryParam);
const data = await NotionApiCache.QueryDatabase(dbID, queryParam);

const { icon, title, description } = columns;
return (
Expand Down
33 changes: 12 additions & 21 deletions app/(blog)/page/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import Link from "next/link";
import { Metadata } from "next";

import { RenderBlock } from "@/app/notion/render";
import { QueryDatabase, retrieveBlockChildren } from "@/app/notion/api";
// import { QueryDatabase } from "@/app/notion/api";
import Shell from "@/components/shells/shell";
import React from "react";
import React, { cache } from "react";
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
import { ChevronLeftIcon } from "@radix-ui/react-icons";
Expand All @@ -16,6 +16,7 @@ import { filterBase, filterSelect, filterText, rawText } from "@/app/notion/bloc
import { notFound } from "next/navigation";
import { env } from "@/env.mjs";
import { ContentLoadingSkeleton } from "@/components/post-skeleton";
import { NotionApiCache } from "@/app/notion/cache";

export const revalidate = env.REVALIDATE_PAGES; // revalidate the data interval

Expand All @@ -42,7 +43,7 @@ type Props = {

// Generate metadata for this page.
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const pageInfo = await filterPageBySlug(params.slug);
const pageInfo = await filterPageBySlug(params.slug[0]);

return {
title: pageInfo.title,
Expand Down Expand Up @@ -77,18 +78,19 @@ function slugParam(slug: string) {
return { ...defaultParam, ...filters };
}

async function filterPageBySlug(slug: string[]) {
// cache: If your arguments are not primitives (ex. objects, functions, arrays), ensure you’re passing the same object reference.
const filterPageBySlug = cache(async (slug: string) => {
const info = {
pageID: "",
lastEditTime: "",
title: "",
summary: "",
};

if (slug.length >= 1) {
const params = slugParam(slug[0]);
if (slug) {
const params = slugParam(slug);

const posts = await QueryDatabase(env.NOTION_DATABASE_ID, params);
const posts = await NotionApiCache.QueryDatabase(env.NOTION_DATABASE_ID, params);
if (posts.length == 0) {
return info;
}
Expand All @@ -100,10 +102,10 @@ async function filterPageBySlug(slug: string[]) {
info.lastEditTime = post?.properties?.PublishDate?.date?.start || post?.last_edited_time;
}
return info;
}
});

async function ContentRender({ pageID }: { pageID: string }) {
const blocks = await retrieveBlockChildren(pageID);
const blocks = await NotionApiCache.RetrieveBlockChildren(pageID);
if (!blocks) {
return <div />;
}
Expand All @@ -119,17 +121,12 @@ async function ContentRender({ pageID }: { pageID: string }) {

export default async function Page({ params }: { params: { slug: string[] } }) {
// retrieve page meta info by page ID
const { pageID, title, summary } = await filterPageBySlug(params.slug);
const { pageID, title, summary } = await filterPageBySlug(params.slug[0]);
if (!pageID || !title) {
console.log("page not found or unpublished", pageID, title);
return notFound();
}

// const blocks = await retrieveBlockChildren(pageID);
// if (!blocks) {
// return <div />;
// }

return (
<Shell as="article" className="relative flex min-h-screen flex-col">
<PageHeader>
Expand All @@ -139,12 +136,6 @@ export default async function Page({ params }: { params: { slug: string[] } }) {
</PageHeaderDescription>
</PageHeader>

{/* <section className="flex w-full flex-col gap-y-0.5 mt-8">
{blocks.map((block: any) => (
<RenderBlock key={block.id} block={block}></RenderBlock>
))}
</section> */}

<React.Suspense fallback={<ContentLoadingSkeleton></ContentLoadingSkeleton>}>
<ContentRender pageID={pageID}></ContentRender>
</React.Suspense>
Expand Down
5 changes: 3 additions & 2 deletions app/(blog)/project/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// import Link from "next/link";

import { QueryDatabase } from "@/app/notion/api";
// import { QueryDatabase } from "@/app/notion/api";
import "@/app/styles/globals.css";
import Shell from "@/components/shells/shell";
import { PageHeader, PageHeaderHeading, PageHeaderDescription } from "@/components/page-header";
Expand All @@ -10,6 +10,7 @@ import { filterBase, filterSelect, sorterProperties } from "@/app/notion/block-p
import { env } from "@/env.mjs";
import { PostPagination } from "@/components/pagination";
import { PostCardLayout } from "@/components/layouts/list-postcard";
import { NotionApiCache } from "@/app/notion/cache";

export const revalidate = env.REVALIDATE_PAGES; // revalidate the data interval

Expand All @@ -32,7 +33,7 @@ type Props = {

export default async function Home({ searchParams }: Props) {
const queryParams = dbParams();
const posts = await QueryDatabase(env.NOTION_DATABASE_ID, queryParams);
const posts = await NotionApiCache.QueryDatabase(env.NOTION_DATABASE_ID, queryParams);
const total = posts.length;

const page = Number(searchParams["page"]) || 1;
Expand Down
9 changes: 4 additions & 5 deletions app/(lobby)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// import Link from "next/link";

import { QueryDatabase, TypePostList } from "@/app/notion/api";
import { TypePostList } from "@/app/notion/api";
import "@/app/styles/globals.css";
import Shell from "@/components/shells/shell";
import { PageHeader, PageHeaderHeading, PageHeaderDescription } from "@/components/page-header";
Expand All @@ -12,6 +12,7 @@ import { PostPagination } from "@/components/pagination";
// import { PostCardLayout } from "@/components/layouts/list-postcard";
import { PostRowsLayout } from "@/components/layouts/list-post-row";
import { TagList } from "@/components/layouts/tag";
import { NotionApiCache } from "../notion/cache";

function dbParams() {
const defaultParam = filterBase(env.NOTION_DATABASE_ID);
Expand Down Expand Up @@ -42,14 +43,12 @@ async function TagFoot({ posts }: { posts: TypePostList }) {
});
});

return (
<TagList tagCounter={tagCounter} className="gap-0.5 grid-cols-3 md:grid-cols-4 lg:grid-cols-5" />
);
return <TagList tagCounter={tagCounter} className="grid-cols-3 gap-0.5 md:grid-cols-4 lg:grid-cols-5" />;
}

export default async function Home({ searchParams }: Props) {
const queryParams = dbParams();
const posts = await QueryDatabase(env.NOTION_DATABASE_ID, queryParams);
const posts = await NotionApiCache.QueryDatabase(env.NOTION_DATABASE_ID, queryParams);
const total = posts.length;

const page = Number(searchParams["page"]) || 1;
Expand Down
7 changes: 4 additions & 3 deletions app/notion/_components/child-database.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import Link from "next/link";

import { QueryDatabase, RetrieveDatabase } from "../api";
// import { QueryDatabase } from "../api";
import { cn } from "@/lib/utils";
import RichText from "../text";
import { IconRender } from "./emoji";
import { DatabaseRenderer } from "./db/database";
import { filterBase } from "../block-parse";
import { NotionApiCache } from "../cache";

interface ChildDatabaseBlockProps {
block: any;
Expand All @@ -16,7 +17,7 @@ export async function ChildDatabaseRenderer({ block, className }: ChildDatabaseB
const { id } = block;

// Note: database view in Notion client can't be retrieved, as it's has a invliad id which has no relationship with the original one
const columns: any = await RetrieveDatabase(id);
const columns: any = await NotionApiCache.RetrieveDatabase(id);
if (!columns) {
return null;
}
Expand All @@ -37,7 +38,7 @@ export async function ChildDatabaseRenderer({ block, className }: ChildDatabaseB
}

const queryParam = filterBase(id);
const data = await QueryDatabase(id, queryParam);
const data = await NotionApiCache.QueryDatabase(id, queryParam);
return (
<div>
<div className="text-lg font-bold">
Expand Down
76 changes: 29 additions & 47 deletions app/notion/api.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { cache } from "react";

import {
GetDatabaseResponse,
QueryDatabaseParameters,
QueryDatabaseResponse,
} from "@notionhq/client/build/src/api-endpoints";
import { proxyListBlockChildren, proxyQueryDatabases, proxyRetrieveDatabase, proxyRetrievePage } from "./proxy/proxy";

// import { proxyListBlockChildren, proxyQueryDatabases, proxyRetrieveDatabase, proxyRetrievePage } from "./proxy/proxy";
import { NotionAPIWithRetry } from "./public";

const api = new NotionAPIWithRetry();
/**
* Returns a random integer between the specified values, inclusive.
* The value is no lower than `min`, and is less than or equal to `max`.
Expand All @@ -23,56 +28,32 @@ function getRandomInt(minimum: number, maximum: number): number {
** retrieve database
** see: https://developers.notion.com/reference/retrieve-a-database
*/
export const RetrieveDatabase = async (database_id: string): Promise<GetDatabaseResponse | null> => {
return proxyRetrieveDatabase(database_id);
};
export const RetrieveDatabase = cache(async (database_id: string): Promise<GetDatabaseResponse | null> => {
return api.RetrieveDatabase(database_id);
});

// Get rows(pages) from database
// API: https://developers.notion.com/reference/post-database-query
export type TypePostList = QueryDatabaseResponse["results"];
export const QueryDatabase = async (database_id: string, params: QueryDatabaseParameters): Promise<TypePostList> => {
const start = new Date().getTime();

const response = await proxyQueryDatabases(database_id, params);
const end = new Date().getTime();
console.log("[QueryDatabase]", `${end - start}ms`);
return response.results;
};
export const QueryDatabase = cache(
async (database_id: string, params: QueryDatabaseParameters): Promise<TypePostList> => {
const start = new Date().getTime();

const response = await api.QueryDatabases(database_id, params);
const end = new Date().getTime();
console.log("[QueryDatabase]", `${end - start}ms`);
return response.results;
}
);

//
// 读取一个page的基础数据(page object)
// API: https://developers.notion.com/reference/retrieve-a-page
export const retrievePage = async (pageId: any) => {
export const RetrievePage = cache(async (page_id: string) => {
const start = new Date().getTime();
// const response = await notion.pages.retrieve({ page_id: pageId });
const response = await proxyRetrievePage(pageId);
const response = await api.RetrievePage(page_id);
const end = new Date().getTime();
console.log("[getPage] for ", `${pageId}: ${end - start}ms`);
console.log("[getPage] for ", `${page_id}: ${end - start}ms`);
return response;
};

//
// export const queryPageBySlug = async (database_id: string, slug: string) => {
// const start = new Date().getTime();

// const response = await notion.databases.query({
// database_id: database_id,
// filter: {
// property: "Slug",
// formula: {
// string: {
// equals: slug,
// },
// },
// },
// });
// const end = new Date().getTime();
// console.log("[getPageFromSlug]", `${end - start}ms`);

// if (response?.results?.length) {
// return response?.results?.[0];
// }
// };
});

// Most of the time, block id for query children is block's own id.
// but some times it will use another id like duplicated synced block
Expand All @@ -91,12 +72,13 @@ const getBlockID = (block: any): string => {
//
// Returns a paginated array of child block objects contained in the block using the ID specified.
// see: https://developers.notion.com/reference/get-block-children
export const retrieveBlockChildren = async (blockID: string): Promise<any> => {
// NOTE: Calling a memoized function outside of a component will not use the cache.
export const RetrieveBlockChildren = cache(async (block_id: string): Promise<any> => {
const start = new Date().getTime();

const blockId = blockID.replaceAll("-", ""); // ???
const blockId = block_id.replaceAll("-", ""); // ???

const result = await proxyListBlockChildren(blockId);
const result = await api.ListBlockChildren(blockId);
if (!result) {
return [];
}
Expand All @@ -108,7 +90,7 @@ export const retrieveBlockChildren = async (blockID: string): Promise<any> => {
// ignore child pages
if (block.has_children && block.type != "child_page") {
const blockID = getBlockID(block);
const children = await retrieveBlockChildren(blockID);
const children = await RetrieveBlockChildren(blockID);
return { ...block, children };
}

Expand Down Expand Up @@ -148,4 +130,4 @@ export const retrieveBlockChildren = async (blockID: string): Promise<any> => {
return acc;
}, [])
);
};
});
Loading

0 comments on commit 534f643

Please sign in to comment.