UNPKG

sveltekit-notion-blog

Version:

A plug and play library for setting up blogs in subdirectory or main directory in Sveltekit projects using Notion as CMS.

296 lines (295 loc) 9.72 kB
import { isFullBlock, isFullPage, isPageObjectResponse } from "../types/helpers"; import { ok, err, Result } from 'neverthrow'; const BASE_URL = "https://api.notion.com/v1"; const sendRequest = async (url, blogClient, method, body) => { const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${blogClient.config.notionToken}`, "Notion-Version": "2022-06-28" }; const response = await fetch(url, { method, headers, body: JSON.stringify(body) }); //get response type const contentType = response.headers.get("Content-Type"); let data; if (contentType) { if (contentType?.indexOf("application/json") > -1) { data = await response.json(); } else { data = await response.text(); } } if (!response.ok) { return { error: data, data: null }; } return { error: null, data }; }; const api = { get: async (url, blogClient) => { return sendRequest(url, blogClient, "GET"); }, post: async (url, blogClient, body) => { return sendRequest(url, blogClient, "POST", body); } }; export const getDatabaseById = async (blogClient) => { try { const url = `${BASE_URL}/databases/${blogClient.config.databaseId}/query`; const notion = blogClient.config.notionToken; if (!notion) { return err({ code: 400, message: "Invalid or missing notion secret" }); } const database = await api.post(url, blogClient, { "filter": { "property": "Published", "checkbox": { "equals": true } } }); const results = database.data?.results; if (isPageObjectResponse(results) && results?.length > 0) { const result = results; return ok(result); } else { return ok([]); } } catch (error) { return handleNotionError(error); } }; export const getBlogSlugs = async (blogClient) => { try { const url = `${BASE_URL}/databases/${blogClient.config.databaseId}/query`; const notion = blogClient.config.notionToken; if (!notion) { return err({ code: 400, message: "Invalid or missing notion secret" }); } const database = await api.post(url, blogClient, { filter: { and: [ { property: "Slug", rich_text: { is_not_empty: true } }, { property: "Published", checkbox: { "equals": true }, } ] } }); if (database.error) { return err({ code: 500, message: database.error }); } const results = database.data?.results; if (isPageObjectResponse(results) && results?.length > 0) { const result = results; //@ts-ignore const slugs = result.map((page) => page.properties["Slug"].rich_text[0].plain_text); return ok(slugs); } return ok([]); } catch (error) { return handleNotionError(error); } }; export const getPageBySlug = async (blogClient, slug) => { try { const notion = blogClient.config.notionToken; if (!notion) { return err({ code: 400, message: "Invalid or missing notion secret" }); } const url = `${BASE_URL}/databases/${blogClient.config.databaseId}/query`; const res = await api.post(url, blogClient, { "filter": { "property": "Slug", "rich_text": { "equals": slug } } }); let pages = []; if (res.data) { for (const page of res.data.results) { if (!isFullPage(page)) { continue; } pages.push(page); } return ok(pages); } else { return err({ code: 500, message: "Some error ocurred" }); } } catch (error) { return handleNotionError(error); } }; export const getBlocks = async (blogClient, blockId) => { if (!blogClient.config.notionToken) { return err({ code: 400, message: "Invalid or missing notion secret" }); } let blocks = []; const results = await getAllBlocksRecursively(blogClient, blockId, blocks, undefined); if (results.isErr()) { return err({ code: 500, message: results.error.message }); } if (results.value.length > 0) { return ok(results.value); } else if (results && results.value.length == 0) { return ok([]); } else { return err({ code: 500, message: "Unknown Error" }); } }; const getAllBlocksRecursively = async (blogClient, blockId, blocks, nextCursor) => { try { let url = `${BASE_URL}/blocks/${blockId}/children?page_size=100`; if (nextCursor) { url += `&start_cursor=${nextCursor}`; } const res = await api.get(url, blogClient); if (res.error) { return err({ code: 500, message: res.error }); } if (res.data) { const { results, has_more, next_cursor } = res.data; for (const block of results) { if (!isFullBlock(block)) continue; blocks.push(block); } if (has_more && next_cursor) { await getAllBlocksRecursively(blogClient, blockId, blocks, next_cursor); } } return ok(blocks); } catch (error) { return handleNotionError(error); } }; const handleNotionError = (error) => { switch (error.code) { case "notionhq_client_request_timeout": return err({ code: 400, message: "Request could not be completed" }); case "object_not_found": return err({ code: 500, message: "Database of page not found" }); case "unauthorized": return err({ code: 401, message: "User not authorized" }); default: return err({ code: 400, message: error ? Object.keys(error)?.length > 0 ? "" : "" : "Some error ocurred" }); } }; export const getNotionUser = async (blogClient, userId) => { try { if (!blogClient.config.notionToken) { return err({ code: 400, message: "Invalid or missing notion secret" }); } const url = `${BASE_URL}/users/${userId}`; const response = await api.get(url, blogClient); if (response.error) { return err({ code: 500, message: response.error }); } if (response.data) { return ok(response.data); } return err({ code: 500, message: "Some error ocurred" }); } catch (error) { return handleNotionError(error); } }; export const getFAQs = async (blogClent, id) => { const response = await getBlocks(blogClent, id); if (response.isOk()) { const faqs = response.value.map((row) => { if (row.type == "table_row") { if (row.table_row.cells?.[0]?.[0]?.plain_text == "Question") { return { question: null, answer: null }; } else { return { question: row.table_row.cells?.[0]?.[0]?.plain_text, answer: row.table_row.cells?.[1]?.map((r) => r.plain_text)?.join("") }; } } else { return { question: null, answer: null }; } }); return ok(faqs); } else if (response.isErr()) { return err(response.error); } else { return err({ code: 500, message: "Unknown Error" }); } }; export const groupNumberedListItems = (blocks) => { const result = []; let currentGroup = []; for (let index = 0; index < blocks.length; index++) { const block = blocks[index]; const prevBlock = blocks[index - 1]; const nextBlock = blocks[index + 1]; if (block.type === 'numbered_list_item') { if ((!prevBlock || prevBlock?.type !== 'numbered_list_item') && nextBlock?.type === 'numbered_list_item') { // first item in the doc currentGroup.push(block); } else if (prevBlock.type === 'numbered_list_item' && nextBlock?.type === 'numbered_list_item') { // next && prev block are numbered list items currentGroup.push(block); } else if (prevBlock?.type === 'numbered_list_item' && nextBlock?.type !== 'numbered_list_item') { currentGroup.push(block); // last item in the list result.push({ type: 'grouped_numbered_list', items: currentGroup }); currentGroup = []; } else { // number_list_item but not preceded or succeeded by number_list_item result.push(block); } } else { result.push(block); } } return result; };