chore: initial commit
Signed-off-by: Alan Brault <alan.brault@visus.io>
This commit is contained in:
147
.gitignore
vendored
Normal file
147
.gitignore
vendored
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
.output
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Sveltekit cache directory
|
||||||
|
.svelte-kit/
|
||||||
|
|
||||||
|
# vitepress build output
|
||||||
|
**/.vitepress/dist
|
||||||
|
|
||||||
|
# vitepress cache directory
|
||||||
|
**/.vitepress/cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# Firebase cache directory
|
||||||
|
.firebase/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v3
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# Vite files
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
.vite/
|
||||||
|
|
||||||
|
# Intellij
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# VSCode
|
||||||
|
.vscode/
|
||||||
12
.prettierrc.json
Normal file
12
.prettierrc.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "always",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"endOfLine": "lf",
|
||||||
|
"jsxSingleQuote": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"useTabs": false
|
||||||
|
}
|
||||||
40
README.md
Normal file
40
README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/create-next-app).
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
First, run the development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
# or
|
||||||
|
yarn dev
|
||||||
|
# or
|
||||||
|
pnpm dev
|
||||||
|
# or
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||||
|
|
||||||
|
You can start editing the page by modifying `app/route.ts`. The page auto-updates as you edit the file.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
To learn more about Next.js, take a look at the following resources:
|
||||||
|
|
||||||
|
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||||
|
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||||
|
|
||||||
|
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
||||||
|
|
||||||
|
## Deploy on Vercel
|
||||||
|
|
||||||
|
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||||
|
|
||||||
|
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
||||||
|
|
||||||
|
## API Routes
|
||||||
|
|
||||||
|
This directory contains example API routes for the headless API app.
|
||||||
|
|
||||||
|
For more details, see [route.js file convention](https://nextjs.org/docs/app/api-reference/file-conventions/route).
|
||||||
29
eslint.config.mts
Normal file
29
eslint.config.mts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { dirname } from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { FlatCompat } from '@eslint/eslintrc';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = dirname(__filename);
|
||||||
|
|
||||||
|
const compat = new FlatCompat({
|
||||||
|
baseDirectory: __dirname,
|
||||||
|
recommendedConfig: {
|
||||||
|
plugins: [],
|
||||||
|
rules: {},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const eslintConfig = [
|
||||||
|
...compat.extends('next/typescript', 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'),
|
||||||
|
{
|
||||||
|
ignores: ['coverage/**', 'node_modules/**', '.next/**', 'out/**', 'build/**', 'next-env.d.ts'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.{js,jsx,ts,tsx}'],
|
||||||
|
rules: {
|
||||||
|
// Add any API-specific rules here
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
6
next-env.d.ts
vendored
Normal file
6
next-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/// <reference types="next" />
|
||||||
|
/// <reference types="next/image-types/global" />
|
||||||
|
/// <reference path="./.next/types/routes.d.ts" />
|
||||||
|
|
||||||
|
// NOTE: This file should not be edited
|
||||||
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
7
next.config.ts
Normal file
7
next.config.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { NextConfig } from 'next';
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
/* config options here */
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
5408
package-lock.json
generated
Normal file
5408
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
package.json
Normal file
33
package.json
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"name": "notion-pages-api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev --turbopack",
|
||||||
|
"build": "next build --turbopack",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "eslint",
|
||||||
|
"lint:fix": "eslint --fix .",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"check-format": "prettier --check ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"next": "15.5.4",
|
||||||
|
"zod": "^4.1.11"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/eslintrc": "^3.3.1",
|
||||||
|
"@eslint/js": "^9.36.0",
|
||||||
|
"@next/eslint-plugin-next": "^15.5.4",
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^19",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.45.0",
|
||||||
|
"@typescript-eslint/parser": "^8.45.0",
|
||||||
|
"eslint": "^9.36.0",
|
||||||
|
"eslint-config-next": "^15.5.4",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"eslint-plugin-react": "^7.37.5",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/app/v1/pages/[page_id]/route.ts
Normal file
0
src/app/v1/pages/[page_id]/route.ts
Normal file
10
src/lib/schemas/index.ts
Normal file
10
src/lib/schemas/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export * from './notion/block.schema';
|
||||||
|
export * from './notion/colors';
|
||||||
|
export * from './notion/emoji.schema';
|
||||||
|
export * from './notion/file.schema';
|
||||||
|
export * from './notion/fileUpload.schema';
|
||||||
|
export * from './notion/page.schema';
|
||||||
|
export * from './notion/pageProperties.schema';
|
||||||
|
export * from './notion/parent.schema';
|
||||||
|
export * from './notion/richText.schema';
|
||||||
|
export * from './notion/user.schema';
|
||||||
129
src/lib/schemas/notion/block.schema.ts
Normal file
129
src/lib/schemas/notion/block.schema.ts
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { emojiSchema, fileSchema, NOTION_COLORS, parentSchema, richTextSchema, userSchema } from '@/lib/schemas';
|
||||||
|
|
||||||
|
const headingsObject = z.object({
|
||||||
|
rich_text: z.array(richTextSchema),
|
||||||
|
color: z.enum(NOTION_COLORS),
|
||||||
|
is_toggleable: z.boolean(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema representing a Notion block object.
|
||||||
|
*/
|
||||||
|
export const blockSchema = z.object({
|
||||||
|
object: z.literal('block'),
|
||||||
|
id: z.uuid(),
|
||||||
|
parent: parentSchema,
|
||||||
|
type: z.enum([
|
||||||
|
'audio',
|
||||||
|
'bookmark',
|
||||||
|
'breadcrumb',
|
||||||
|
'bulleted_list_item',
|
||||||
|
'callout',
|
||||||
|
'child_database',
|
||||||
|
'child_page',
|
||||||
|
'column',
|
||||||
|
'column_list',
|
||||||
|
'divider',
|
||||||
|
'embed',
|
||||||
|
'equation',
|
||||||
|
'file',
|
||||||
|
'heading_1',
|
||||||
|
'heading_2',
|
||||||
|
'heading_3',
|
||||||
|
'image',
|
||||||
|
'link_preview',
|
||||||
|
'numbered_list_item',
|
||||||
|
'paragraph',
|
||||||
|
'pdf',
|
||||||
|
'quote',
|
||||||
|
'synced_block',
|
||||||
|
'table',
|
||||||
|
'table_of_contents',
|
||||||
|
'table_row',
|
||||||
|
'template',
|
||||||
|
'to_do',
|
||||||
|
'toggle',
|
||||||
|
'unsupported',
|
||||||
|
'video',
|
||||||
|
]),
|
||||||
|
created_time: z.iso.datetime(),
|
||||||
|
created_by: userSchema,
|
||||||
|
last_edited_time: z.iso.datetime(),
|
||||||
|
last_edited_by: userSchema,
|
||||||
|
archived: z.boolean(),
|
||||||
|
in_trash: z.boolean(),
|
||||||
|
has_children: z.boolean(),
|
||||||
|
|
||||||
|
// Optional properties for each type
|
||||||
|
audio: fileSchema.optional(),
|
||||||
|
bookmark: z
|
||||||
|
.object({
|
||||||
|
caption: z.array(richTextSchema),
|
||||||
|
url: z.url(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
breadcrumb: z.object({}).optional(),
|
||||||
|
bulleted_list_item: z
|
||||||
|
.object({
|
||||||
|
rich_text: z.array(richTextSchema),
|
||||||
|
color: z.enum(NOTION_COLORS),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
callout: z
|
||||||
|
.object({
|
||||||
|
rich_text: z.array(richTextSchema),
|
||||||
|
icon: z.union([emojiSchema, fileSchema]),
|
||||||
|
color: z.enum(NOTION_COLORS),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
child_database: z
|
||||||
|
.object({
|
||||||
|
title: z.string(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
child_page: z
|
||||||
|
.object({
|
||||||
|
title: z.string(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
code: z
|
||||||
|
.object({
|
||||||
|
caption: z.array(richTextSchema),
|
||||||
|
rich_text: z.array(richTextSchema),
|
||||||
|
language: z.enum([]), // TODO: Fill in with actual languages
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
column_list: z.object({}).optional(),
|
||||||
|
column: z
|
||||||
|
.object({
|
||||||
|
width_ratio: z.number().min(0).max(1).optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
divider: z.object({}).optional(),
|
||||||
|
embed: z
|
||||||
|
.object({
|
||||||
|
url: z.url(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
equation: z.object({ expression: z.string() }).optional(),
|
||||||
|
file: z
|
||||||
|
.object({
|
||||||
|
caption: z.array(richTextSchema),
|
||||||
|
type: z.enum(['file', 'file_upload', 'external']),
|
||||||
|
file: fileSchema,
|
||||||
|
external: fileSchema,
|
||||||
|
file_upload: fileSchema,
|
||||||
|
name: z.string(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
heading_1: headingsObject.optional(),
|
||||||
|
heading_2: headingsObject.optional(),
|
||||||
|
heading_3: headingsObject.optional(),
|
||||||
|
image: fileSchema.optional(),
|
||||||
|
link_preview: z.object({ url: z.url() }).optional(),
|
||||||
|
|
||||||
|
// TODO: Continue with mention and others
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NotionBlock = z.infer<typeof blockSchema>;
|
||||||
22
src/lib/schemas/notion/colors.ts
Normal file
22
src/lib/schemas/notion/colors.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/** Notion color options */
|
||||||
|
export const NOTION_COLORS = [
|
||||||
|
'blue',
|
||||||
|
'blue_background',
|
||||||
|
'brown',
|
||||||
|
'brown_background',
|
||||||
|
'default',
|
||||||
|
'gray',
|
||||||
|
'gray_background',
|
||||||
|
'green',
|
||||||
|
'green_background',
|
||||||
|
'orange',
|
||||||
|
'orange_background',
|
||||||
|
'pink',
|
||||||
|
'pink_background',
|
||||||
|
'purple',
|
||||||
|
'purple_background',
|
||||||
|
'red',
|
||||||
|
'red_background',
|
||||||
|
'yellow',
|
||||||
|
'yellow_background',
|
||||||
|
];
|
||||||
8
src/lib/schemas/notion/emoji.schema.ts
Normal file
8
src/lib/schemas/notion/emoji.schema.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
export const emojiSchema = z.object({
|
||||||
|
type: z.literal('emoji'),
|
||||||
|
emoji: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NotionEmoji = z.infer<typeof emojiSchema>;
|
||||||
33
src/lib/schemas/notion/file.schema.ts
Normal file
33
src/lib/schemas/notion/file.schema.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema representing a Notion file object.
|
||||||
|
*
|
||||||
|
* This schema can represent three types of files:
|
||||||
|
* 1. 'file': A file hosted by Notion, with a URL and an expiry time.
|
||||||
|
* 2. 'file_upload': A file uploaded to Notion, identified by a unique ID.
|
||||||
|
* 3. 'external': A file hosted externally, represented by a URL.
|
||||||
|
*
|
||||||
|
* Each type has its own structure, and only one type will be present in a valid object.
|
||||||
|
*/
|
||||||
|
export const fileSchema = z.object({
|
||||||
|
type: z.enum(['file', 'file_upload', 'external']),
|
||||||
|
file: z
|
||||||
|
.object({
|
||||||
|
url: z.url(),
|
||||||
|
expiry_time: z.iso.datetime(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
file_upload: z
|
||||||
|
.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
external: z
|
||||||
|
.object({
|
||||||
|
url: z.url(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NotionFile = z.infer<typeof fileSchema>;
|
||||||
22
src/lib/schemas/notion/fileUpload.schema.ts
Normal file
22
src/lib/schemas/notion/fileUpload.schema.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema representing a Notion file upload object.
|
||||||
|
*
|
||||||
|
* Includes details about the file upload status, URLs, and metadata.
|
||||||
|
*/
|
||||||
|
export const fileUploadSchema = z.object({
|
||||||
|
object: z.literal('file_upload'),
|
||||||
|
id: z.uuid(),
|
||||||
|
created_time: z.iso.datetime(),
|
||||||
|
expiry_time: z.iso.datetime().nullable(),
|
||||||
|
status: z.enum(['pending', 'uploaded', 'expired', 'failed']),
|
||||||
|
filename: z.string(),
|
||||||
|
content_type: z.string().nullable(),
|
||||||
|
content_length: z.number().nullable(),
|
||||||
|
upload_url: z.string(),
|
||||||
|
complete_url: z.string(),
|
||||||
|
file_import_result: z.string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NotionFileUpload = z.infer<typeof fileUploadSchema>;
|
||||||
26
src/lib/schemas/notion/page.schema.ts
Normal file
26
src/lib/schemas/notion/page.schema.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { fileSchema, pagePropertiesSchema, parentSchema, userSchema } from '@/lib/schemas';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema representing a Notion page object.
|
||||||
|
*
|
||||||
|
* Includes metadata about the page, its properties, and its parent.
|
||||||
|
*/
|
||||||
|
export const pageSchema = z.object({
|
||||||
|
object: z.literal('page'),
|
||||||
|
id: z.uuid(),
|
||||||
|
created_time: z.iso.datetime(),
|
||||||
|
created_by: userSchema,
|
||||||
|
last_edited_time: z.iso.datetime(),
|
||||||
|
last_edited_by: userSchema,
|
||||||
|
archived: z.boolean(),
|
||||||
|
in_trash: z.boolean(),
|
||||||
|
icon: z.nullable(fileSchema),
|
||||||
|
cover: z.nullable(fileSchema),
|
||||||
|
properties: z.record(z.string(), pagePropertiesSchema),
|
||||||
|
parent: parentSchema,
|
||||||
|
url: z.url(),
|
||||||
|
public_url: z.url().nullable(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NotionPage = z.infer<typeof pageSchema>;
|
||||||
92
src/lib/schemas/notion/pageProperties.schema.ts
Normal file
92
src/lib/schemas/notion/pageProperties.schema.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { NOTION_COLORS, richTextSchema, userSchema } from '@/lib/schemas';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema representing the properties of a Notion page.
|
||||||
|
*
|
||||||
|
* Includes various property types such as text, number, select, multi-select, date, people, files, and more.
|
||||||
|
*/
|
||||||
|
export const pagePropertiesSchema = z.object({
|
||||||
|
id: z.string(),
|
||||||
|
type: z.enum([
|
||||||
|
'checkbox',
|
||||||
|
'created_by',
|
||||||
|
'created_time',
|
||||||
|
'date',
|
||||||
|
'email',
|
||||||
|
'files',
|
||||||
|
'formula',
|
||||||
|
'last_edited_by',
|
||||||
|
'last_edited_time',
|
||||||
|
'multi_select',
|
||||||
|
'number',
|
||||||
|
'people',
|
||||||
|
'phone_number',
|
||||||
|
'relation',
|
||||||
|
'rich_text',
|
||||||
|
'rollup',
|
||||||
|
'select',
|
||||||
|
'status',
|
||||||
|
'title',
|
||||||
|
'url',
|
||||||
|
'unique_id',
|
||||||
|
'verification',
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Optional properties for each type
|
||||||
|
checkbox: z.boolean().optional(),
|
||||||
|
created_by: userSchema.optional(),
|
||||||
|
created_time: z.iso.datetime().optional(),
|
||||||
|
date: z
|
||||||
|
.object({
|
||||||
|
start: z.iso.datetime(),
|
||||||
|
end: z.iso.datetime().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
email: z.email().optional(),
|
||||||
|
files: z.array(z.any()).optional(),
|
||||||
|
formula: z.any().optional(),
|
||||||
|
last_edited_by: userSchema.optional(),
|
||||||
|
last_edited_time: z.iso.datetime().optional(),
|
||||||
|
multi_select: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
color: z.enum(NOTION_COLORS),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
number: z.number().optional(),
|
||||||
|
people: z.array(userSchema).optional(),
|
||||||
|
phone_number: z.string().optional(),
|
||||||
|
relation: z.array(z.object({ id: z.string() })).optional(),
|
||||||
|
rich_text: richTextSchema.optional(),
|
||||||
|
rollup: z.any().optional(),
|
||||||
|
select: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
color: z.enum(NOTION_COLORS),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
status: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
color: z.enum(NOTION_COLORS),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
title: richTextSchema.optional(),
|
||||||
|
url: z.url().optional(),
|
||||||
|
unique_id: z.string().optional(),
|
||||||
|
verification: z
|
||||||
|
.object({
|
||||||
|
state: z.string(),
|
||||||
|
verified_by: userSchema.nullable(),
|
||||||
|
date: z.iso.datetime(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NotionPageProperties = z.infer<typeof pagePropertiesSchema>;
|
||||||
17
src/lib/schemas/notion/parent.schema.ts
Normal file
17
src/lib/schemas/notion/parent.schema.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema representing the parent of a Notion page.
|
||||||
|
*
|
||||||
|
* Includes various types of parents such as database, data source, page, workspace, or block.
|
||||||
|
*/
|
||||||
|
export const parentSchema = z.object({
|
||||||
|
type: z.enum(['database_id', 'data_source_id', 'page_id', 'workspace', 'block_id']),
|
||||||
|
database_id: z.uuid().optional(),
|
||||||
|
data_source_id: z.uuid().optional(),
|
||||||
|
page_id: z.uuid().optional(),
|
||||||
|
workspace: z.boolean().optional(),
|
||||||
|
block_id: z.uuid().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NotionParent = z.infer<typeof parentSchema>;
|
||||||
68
src/lib/schemas/notion/richText.schema.ts
Normal file
68
src/lib/schemas/notion/richText.schema.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { NOTION_COLORS, userSchema } from '@/lib/schemas';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema representing a Notion rich text object.
|
||||||
|
*
|
||||||
|
* Includes text content, annotations, mentions, and equations.
|
||||||
|
*/
|
||||||
|
export const richTextSchema = z.array(
|
||||||
|
z.object({
|
||||||
|
type: z.literal('text'),
|
||||||
|
text: z.object({
|
||||||
|
content: z.string(),
|
||||||
|
link: z.url().nullable(),
|
||||||
|
}),
|
||||||
|
mention: z
|
||||||
|
.object({
|
||||||
|
type: z.enum(['database', 'date', 'link_preview', 'page', 'template_mention', 'user']),
|
||||||
|
database: z
|
||||||
|
.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
date: z
|
||||||
|
.object({
|
||||||
|
start: z.iso.datetime(),
|
||||||
|
end: z.iso.datetime().optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
link_preview: z
|
||||||
|
.object({
|
||||||
|
url: z.url(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
page: z
|
||||||
|
.object({
|
||||||
|
id: z.uuid(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
template_mention: z
|
||||||
|
.object({
|
||||||
|
type: z.enum(['template_mention_date', 'template_mention_user']),
|
||||||
|
template_mention_date: z.enum(['today', 'now']).optional(),
|
||||||
|
template_mention_user: z.literal('me').optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
user: z.object(userSchema).optional(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
equation: z
|
||||||
|
.object({
|
||||||
|
expression: z.string(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
|
annotations: z.object({
|
||||||
|
bold: z.boolean(),
|
||||||
|
italic: z.boolean(),
|
||||||
|
strikethrough: z.boolean(),
|
||||||
|
underline: z.boolean(),
|
||||||
|
code: z.boolean(),
|
||||||
|
color: z.enum(NOTION_COLORS),
|
||||||
|
}),
|
||||||
|
plain_text: z.string(),
|
||||||
|
href: z.url().optional(),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
export type NotionRichText = z.infer<typeof richTextSchema>;
|
||||||
16
src/lib/schemas/notion/user.schema.ts
Normal file
16
src/lib/schemas/notion/user.schema.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schema representing a Notion user object.
|
||||||
|
*
|
||||||
|
* Includes both person and bot user types.
|
||||||
|
*/
|
||||||
|
export const userSchema = z.object({
|
||||||
|
object: z.literal('user'),
|
||||||
|
id: z.uuid(),
|
||||||
|
type: z.enum(['person', 'bot']).optional(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
avatar_url: z.url().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NotionUser = z.infer<typeof userSchema>;
|
||||||
27
tsconfig.json
Normal file
27
tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2017",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user