diff --git a/.gitignore b/.gitignore
index 3b0fe33..90ee3e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+.DS_STORE
+
# Logs
logs
diff --git a/package-lock.json b/package-lock.json
index 5f0be4d..be38337 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,6 +7,9 @@
"": {
"name": "web-tombutcher-work",
"version": "0.0.0",
+ "dependencies": {
+ "@notionhq/client": "^2.2.16"
+ },
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.6.4",
"vitest": "~2.1.9",
@@ -1027,6 +1030,19 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
+ "node_modules/@notionhq/client": {
+ "version": "2.2.16",
+ "resolved": "https://registry.npmjs.org/@notionhq/client/-/client-2.2.16.tgz",
+ "integrity": "sha512-3GlkfhLw8+Jw8U2iFEmHA6WfCgYhZCXLxgPdqDJkYMFotELNpQO+yGSy2QWURsG8ndu21sLt+FEOfDbNcCtFMg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node-fetch": "^2.5.10",
+ "node-fetch": "^2.6.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.34.9",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz",
@@ -1300,6 +1316,25 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "22.13.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.9.tgz",
+ "integrity": "sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
+ "node_modules/@types/node-fetch": {
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz",
+ "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "form-data": "^4.0.0"
+ }
+ },
"node_modules/@vitest/expect": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
@@ -1456,6 +1491,12 @@
"node": ">=12"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
"node_modules/birpc": {
"version": "0.2.14",
"resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.14.tgz",
@@ -1483,6 +1524,19 @@
"node": ">=8"
}
},
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/chai": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
@@ -1566,6 +1620,18 @@
"simple-swizzle": "^0.2.2"
}
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
@@ -1625,6 +1691,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
@@ -1643,6 +1718,38 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/es-module-lexer": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
@@ -1650,6 +1757,33 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/esbuild": {
"version": "0.17.19",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
@@ -1734,6 +1868,21 @@
"node": ">=12.0.0"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -1749,6 +1898,52 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/get-source": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz",
@@ -1767,6 +1962,57 @@
"dev": true,
"license": "BSD-2-Clause"
},
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
@@ -1792,6 +2038,15 @@
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
"node_modules/mime": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
@@ -1805,6 +2060,27 @@
"node": ">=10.0.0"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/miniflare": {
"version": "3.20250204.1",
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250204.1.tgz",
@@ -1897,6 +2173,26 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/node-fetch": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ohash": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.5.tgz",
@@ -2266,6 +2562,12 @@
"node": ">=14.0.0"
}
},
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "license": "MIT"
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@@ -2294,6 +2596,12 @@
"node": ">=14.0"
}
},
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "license": "MIT"
+ },
"node_modules/unenv": {
"version": "2.0.0-rc.1",
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.1.tgz",
@@ -2870,6 +3178,22 @@
}
}
},
+ "node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
"node_modules/why-is-node-running": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
diff --git a/package.json b/package.json
index 00d2634..a474dbe 100644
--- a/package.json
+++ b/package.json
@@ -12,5 +12,8 @@
"@cloudflare/vitest-pool-workers": "^0.6.4",
"vitest": "~2.1.9",
"wrangler": "^3.111.0"
+ },
+ "dependencies": {
+ "@notionhq/client": "^2.2.16"
}
}
diff --git a/src/index.js b/src/index.js
index de66b62..f469372 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,15 +1,33 @@
-/**
- * Welcome to Cloudflare Workers! This is your first worker.
- *
- * - Run `npm run dev` in your terminal to start a development server
- * - Open a browser tab at http://localhost:8787/ to see your worker in action
- * - Run `npm run deploy` to publish your worker
- *
- * Learn more at https://developers.cloudflare.com/workers/
- */
+import { handleContactRequest } from './routes/contact.js';
+import { handleSocialsRequest } from './routes/socials.js';
+import { handleBlogsListRequest, handleBlogsViewRequest } from './routes/blogs.js';
+import { handleFlushCacheRequest } from './routes/utils.js';
-export default {
- async fetch(request, env, ctx) {
- return new Response('Hello World!');
- },
-};
+async function handleRequest(request) {
+ if (request.method === 'POST' && request.url.split('?')[0].endsWith('/api/contact')) {
+ return handleContactRequest(request);
+ }
+
+ if (request.method === 'GET' && request.url.split('?')[0].endsWith('/api/list/socials')) {
+ return handleSocialsRequest(request);
+ }
+
+ if (request.method === 'GET' && request.url.split('?')[0].endsWith('/api/list/blogs')) {
+ return handleBlogsListRequest(request);
+ }
+
+ if (request.method === 'GET' && request.url.split('?')[0].endsWith('/api/view/blog')) {
+ return handleBlogsViewRequest(request);
+ }
+
+ if (request.method === 'POST' && request.url.split('?')[0].endsWith('/api/utils/cache')) {
+ return handleFlushCacheRequest(request);
+ }
+
+ // Return 404 if the route is not found
+ return new Response('Not Found', { status: 404 });
+}
+
+addEventListener('fetch', (event) => {
+ event.respondWith(handleRequest(event.request));
+});
diff --git a/src/routes/blogs.js b/src/routes/blogs.js
new file mode 100644
index 0000000..364c135
--- /dev/null
+++ b/src/routes/blogs.js
@@ -0,0 +1,152 @@
+import { getNotionDatabaseWithCache, getNotionBlocksWithCache } from '../utils/notion.js';
+import { generateBlockHTML } from '../utils/htmlgen.js';
+const blogsDB = process.env.BLOGS_DB;
+
+export async function handleBlogsListRequest(request, env) {
+ console.log('Listing blogs...');
+ try {
+ // Parse URL to get query parameters
+ const url = new URL(request.url);
+ const filter = url.searchParams.get('f') || '';
+
+ let queryParam = {
+ filter: {
+ property: 'Status',
+ status: {
+ equals: 'Published', // Only show published articles
+ },
+ },
+ };
+
+ // Get blogs from Notion (with caching)
+ const blogsData = await getNotionDatabaseWithCache(blogsDB, queryParam);
+
+ if (blogsData.length <= 0) {
+ console.error('Invalid filter.');
+ return new Response(
+ JSON.stringify({
+ error: 'Invalid filter.',
+ }),
+ {
+ status: 404,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
+ }
+
+ var blogsResponse = [];
+
+ for (const blog of blogsData) {
+ const blogTags = blog.properties['Tags'].multi_select.map((item) => item.name);
+ const blogObject = {
+ title: blog.properties['Title'].title[0].plain_text,
+ slug: blog.properties['Slug'].formula.string,
+ description: blog.properties['Description'].rich_text[0].plain_text,
+ author: blog.properties['Author'].created_by.name,
+ last_edited: blog.properties['Last edited'].last_edited_time,
+ tags: blogTags,
+ };
+ if (filter !== '') {
+ const filters = filter.split(',');
+ if (filters.some((tag) => blogTags.includes(tag))) {
+ blogsResponse.push(blogObject);
+ }
+ } else {
+ blogsResponse.push(blogObject);
+ }
+ }
+
+ // Return the processed data
+ console.log('Finished listing blogs.');
+ return new Response(JSON.stringify(blogsResponse), {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Access-Control-Allow-Origin': '*', // Enable CORS
+ 'Cache-Control': 'public, max-age=600', // 10 minute browser cache
+ },
+ });
+ } catch (error) {
+ console.error('Error handling blogs request:', error);
+ return new Response(
+ JSON.stringify({
+ error: 'Failed to retrieve blogs',
+ message: error.message,
+ }),
+ {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
+ }
+}
+
+export async function handleBlogsViewRequest(request, env) {
+ console.log('Viewing blog...');
+ try {
+ // Parse URL to get query parameters
+ const url = new URL(request.url);
+ const slug = url.searchParams.get('b') || '';
+
+ let queryParam = {
+ filter: {
+ property: 'Status',
+ status: {
+ equals: 'Published', // Only show published articles
+ },
+ },
+ };
+
+ // Get blogs from Notion (with caching)
+ const blogsData = await getNotionDatabaseWithCache(blogsDB, queryParam);
+
+ const blog = blogsData.find((blog) => blog.properties?.['Slug']?.formula?.string == slug) || null;
+
+ if (blog == null) {
+ console.error('Invalid not found.');
+ return new Response(
+ JSON.stringify({
+ error: 'Invalid not found.',
+ }),
+ {
+ status: 404,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
+ }
+
+ const blogContent = await getNotionBlocksWithCache(blog.id, queryParam);
+
+ const blogTags = blog.properties['Tags'].multi_select.map((item) => item.name);
+ var blogResponse = {
+ title: blog.properties['Title'].title[0].plain_text,
+ slug: blog.properties['Slug'].formula.string,
+ description: blog.properties['Description'].rich_text[0].plain_text,
+ author: blog.properties['Author'].created_by.name,
+ last_edited: blog.properties['Last edited'].last_edited_time,
+ tags: blogTags,
+ content: await generateBlockHTML(blogContent),
+ };
+
+ // Return the processed data
+ console.log('Finished listing blogs.');
+ return new Response(JSON.stringify(blogResponse), {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Access-Control-Allow-Origin': '*', // Enable CORS
+ 'Cache-Control': 'public, max-age=600', // 10 minute browser cache
+ },
+ });
+ } catch (error) {
+ console.error('Error handling blogs request:', error);
+ return new Response(
+ JSON.stringify({
+ error: 'Failed to retrieve blogs',
+ message: error.message,
+ }),
+ {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
+ }
+}
diff --git a/src/routes/contact.js b/src/routes/contact.js
index cb3e850..7bd340e 100644
--- a/src/routes/contact.js
+++ b/src/routes/contact.js
@@ -1,7 +1,9 @@
import { getLocation } from '../utils/geolocation.js';
-import { addToNotionDatabase } from '../utils/notion.js';
+import { addToNotionDatabase, getNotionDatabaseWithCache } from '../utils/notion.js';
-const TURNSTILE_SECRET_KEY = 'your-turnstile-secret-key';
+const TURNSTILE_SECRET_KEY = process.env.TURNSTILE_AUTH;
+
+const TURNSTILE_ENABLED = true;
export async function handleContactRequest(request) {
const { email, token } = await request.json();
@@ -15,26 +17,29 @@ export async function handleContactRequest(request) {
// Get location info
const location = await getLocation(ip);
+ const locationString = location.city + ' - ' + location.country;
- // Verify the Turnstile token
- const verificationResponse = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- secret: TURNSTILE_SECRET_KEY,
- response: token,
- }),
- });
+ if (TURNSTILE_ENABLED) {
+ // Verify the Turnstile token
+ const verificationResponse = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ secret: TURNSTILE_SECRET_KEY,
+ response: token,
+ }),
+ });
- const verificationData = await verificationResponse.json();
+ const verificationData = await verificationResponse.json();
- if (!verificationData.success) {
- return new Response('Turnstile verification failed', { status: 400 });
+ if (!verificationData.success) {
+ return new Response('Turnstile verification failed', { status: 400 });
+ }
}
// Add the email and location to Notion
try {
- await addToNotionDatabase(email, location);
+ await addToNotionDatabase({ Name: email.split('@')[0], Email: email, Location: locationString }, '1abdd26d60b68076a886fb0525ff0a4f');
return new Response(JSON.stringify({ success: true, message: 'Email processed and added to Notion' }), {
headers: { 'Content-Type': 'application/json' },
});
diff --git a/src/routes/socials.js b/src/routes/socials.js
index e69de29..65887ac 100644
--- a/src/routes/socials.js
+++ b/src/routes/socials.js
@@ -0,0 +1,77 @@
+import { getNotionDatabaseWithCache } from '../utils/notion.js';
+const socialsDB = process.env.SOCIALS_DB;
+const referrersDB = process.env.REFERRERS_DB;
+
+export async function handleSocialsRequest(request, env) {
+ console.log('Listing socials...');
+ try {
+ // Parse URL to get query parameters
+ const url = new URL(request.url);
+ const referrer = url.searchParams.get('r') || 'unknown';
+
+ console.log('Referrer:', referrer);
+
+ // Get social links from Notion (with caching)
+ const socialsData = await getNotionDatabaseWithCache(socialsDB);
+
+ // Get referrers from Notion (with caching)
+ const referrersData = await getNotionDatabaseWithCache(referrersDB, {
+ filter: {
+ property: 'Slug',
+ rich_text: {
+ equals: referrer,
+ },
+ },
+ });
+
+ if (referrersData.length <= 0) {
+ console.error('Invalid referrer.');
+ return new Response(
+ JSON.stringify({
+ error: 'Invalid referrer.',
+ }),
+ {
+ status: 404,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
+ }
+
+ const referrerObject = referrersData[0];
+
+ var socialsResponse = [];
+
+ // Loop through each relation
+ for (const relation of referrerObject.properties['Social Media'].relation) {
+ console.log('Social ID:', relation.id);
+ const socialMedia = Object.fromEntries(socialsData.map((social) => [social.id, social]))[relation.id];
+ const socialsResponseObject = {
+ name: socialMedia.properties['Name'].title[0].plain_text,
+ icon: socialMedia.properties['Icon'].rich_text[0].plain_text,
+ url: socialMedia.properties['Link'].url,
+ };
+ socialsResponse.push(socialsResponseObject);
+ }
+ // Return the processed data
+ console.log('Finished listing socials.');
+ return new Response(JSON.stringify(socialsResponse), {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Access-Control-Allow-Origin': '*', // Enable CORS
+ 'Cache-Control': 'public, max-age=600', // 10 minute browser cache
+ },
+ });
+ } catch (error) {
+ console.error('Error handling socials request:', error);
+ return new Response(
+ JSON.stringify({
+ error: 'Failed to retrieve social links',
+ message: error.message,
+ }),
+ {
+ status: 500,
+ headers: { 'Content-Type': 'application/json' },
+ },
+ );
+ }
+}
diff --git a/src/routes/utils.js b/src/routes/utils.js
new file mode 100644
index 0000000..5ca1e85
--- /dev/null
+++ b/src/routes/utils.js
@@ -0,0 +1,15 @@
+import { flushCache } from '../utils/notion.js';
+
+export async function handleFlushCacheRequest(request) {
+ await flushCache();
+ const response = {
+ result: 'ok',
+ };
+ return new Response(JSON.stringify(response), {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Access-Control-Allow-Origin': '*', // Enable CORS
+ 'Cache-Control': 'public, max-age=600', // 10 minute browser cache
+ },
+ });
+}
diff --git a/src/utils/geolocation.js b/src/utils/geolocation.js
index 48ba0cc..566e604 100644
--- a/src/utils/geolocation.js
+++ b/src/utils/geolocation.js
@@ -1,4 +1,4 @@
-const GEOLOCATION_API_KEY = 'your-geolocation-api-key';
+const GEOLOCATION_API_KEY = '1489c8e06deb4693bf31733a18fe2351';
export async function getLocation(ip) {
const geoApiUrl = `https://api.ipgeolocation.io/ipgeo?apiKey=${GEOLOCATION_API_KEY}&ip=${ip}`;
diff --git a/src/utils/htmlgen.js b/src/utils/htmlgen.js
new file mode 100644
index 0000000..c12c15b
--- /dev/null
+++ b/src/utils/htmlgen.js
@@ -0,0 +1,161 @@
+import { getNotionBlocksWithCache, getNotionSVGIcon } from './notion';
+
+function richTextToHTML(richText) {
+ return richText
+ .map((item) => {
+ let text = item.plain_text;
+
+ let { bold, italic, strikethrough, underline, code, color } = item.annotations;
+ if (item.href) text = `${text}`;
+ if (bold) text = `${text}`;
+ if (italic) text = `${text}`;
+ if (strikethrough) text = `${text}`;
+ if (underline) text = `${text}`;
+ if (code) text = `${text}`;
+ if (item.href) text = `${text}`;
+ if (color && color !== 'default') text = `${text}`;
+
+ return text;
+ })
+ .join('');
+}
+
+export async function generateBlockHTML(blocksContent) {
+ var blogContentHTML = '';
+
+ var olListMode = false;
+ var ulListMode = false;
+ var tableCellTag = 'td';
+
+ var index = 0;
+ for (const block of blocksContent) {
+ if (olListMode == false && block.type == 'numbered_list_item') {
+ blogContentHTML += `
${richTextToHTML(block['paragraph'].rich_text)}
\n`; + break; + case 'heading_1': + blogContentHTML += `${richTextToHTML(block['callout'].rich_text)}
`; + } + blogContentHTML += `${richTextToHTML(block['quote'].rich_text)}
\n`; + break; + case 'image': + var imageSrc = ''; + if (block['image'].type == 'external') { + imageSrc = block['image'].external.url; + } + if (block['image'].type == 'file') { + imageSrc = block['image'].file.url; + } + var imageCaption = ''; + if (block['image'].caption.length != 0) { + imageCaption = `${richTextToHTML(block['image'].caption)}
`; + } + blogContentHTML += `