Initial source commit
This commit is contained in:
parent
6eecd3c414
commit
ec57d8f7c4
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
|
.DS_STORE
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
|
|
||||||
logs
|
logs
|
||||||
|
|||||||
324
package-lock.json
generated
324
package-lock.json
generated
@ -7,6 +7,9 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "web-tombutcher-work",
|
"name": "web-tombutcher-work",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@notionhq/client": "^2.2.16"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/vitest-pool-workers": "^0.6.4",
|
"@cloudflare/vitest-pool-workers": "^0.6.4",
|
||||||
"vitest": "~2.1.9",
|
"vitest": "~2.1.9",
|
||||||
@ -1027,6 +1030,19 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@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": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.34.9",
|
"version": "4.34.9",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz",
|
||||||
@ -1300,6 +1316,25 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@vitest/expect": {
|
||||||
"version": "2.1.9",
|
"version": "2.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
|
||||||
@ -1456,6 +1491,12 @@
|
|||||||
"node": ">=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": {
|
"node_modules/birpc": {
|
||||||
"version": "0.2.14",
|
"version": "0.2.14",
|
||||||
"resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.14.tgz",
|
"resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.14.tgz",
|
||||||
@ -1483,6 +1524,19 @@
|
|||||||
"node": ">=8"
|
"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": {
|
"node_modules/chai": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
|
||||||
@ -1566,6 +1620,18 @@
|
|||||||
"simple-swizzle": "^0.2.2"
|
"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": {
|
"node_modules/confbox": {
|
||||||
"version": "0.1.8",
|
"version": "0.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
|
||||||
@ -1625,6 +1691,15 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||||
@ -1643,6 +1718,38 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/es-module-lexer": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
|
||||||
@ -1650,6 +1757,33 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.17.19",
|
"version": "0.17.19",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz",
|
||||||
@ -1734,6 +1868,21 @@
|
|||||||
"node": ">=12.0.0"
|
"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": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"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": "^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": {
|
"node_modules/get-source": {
|
||||||
"version": "2.0.12",
|
"version": "2.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz",
|
"resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz",
|
||||||
@ -1767,6 +1962,57 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause"
|
"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": {
|
"node_modules/is-arrayish": {
|
||||||
"version": "0.3.2",
|
"version": "0.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
|
||||||
@ -1792,6 +2038,15 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
"@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": {
|
"node_modules/mime": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||||
@ -1805,6 +2060,27 @@
|
|||||||
"node": ">=10.0.0"
|
"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": {
|
"node_modules/miniflare": {
|
||||||
"version": "3.20250204.1",
|
"version": "3.20250204.1",
|
||||||
"resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20250204.1.tgz",
|
"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": "^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": {
|
"node_modules/ohash": {
|
||||||
"version": "1.1.5",
|
"version": "1.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.5.tgz",
|
||||||
@ -2266,6 +2562,12 @@
|
|||||||
"node": ">=14.0.0"
|
"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": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
@ -2294,6 +2596,12 @@
|
|||||||
"node": ">=14.0"
|
"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": {
|
"node_modules/unenv": {
|
||||||
"version": "2.0.0-rc.1",
|
"version": "2.0.0-rc.1",
|
||||||
"resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.1.tgz",
|
"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": {
|
"node_modules/why-is-node-running": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
|
||||||
|
|||||||
@ -12,5 +12,8 @@
|
|||||||
"@cloudflare/vitest-pool-workers": "^0.6.4",
|
"@cloudflare/vitest-pool-workers": "^0.6.4",
|
||||||
"vitest": "~2.1.9",
|
"vitest": "~2.1.9",
|
||||||
"wrangler": "^3.111.0"
|
"wrangler": "^3.111.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@notionhq/client": "^2.2.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/index.js
46
src/index.js
@ -1,15 +1,33 @@
|
|||||||
/**
|
import { handleContactRequest } from './routes/contact.js';
|
||||||
* Welcome to Cloudflare Workers! This is your first worker.
|
import { handleSocialsRequest } from './routes/socials.js';
|
||||||
*
|
import { handleBlogsListRequest, handleBlogsViewRequest } from './routes/blogs.js';
|
||||||
* - Run `npm run dev` in your terminal to start a development server
|
import { handleFlushCacheRequest } from './routes/utils.js';
|
||||||
* - 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/
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default {
|
async function handleRequest(request) {
|
||||||
async fetch(request, env, ctx) {
|
if (request.method === 'POST' && request.url.split('?')[0].endsWith('/api/contact')) {
|
||||||
return new Response('Hello World!');
|
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));
|
||||||
|
});
|
||||||
|
|||||||
152
src/routes/blogs.js
Normal file
152
src/routes/blogs.js
Normal file
@ -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' },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,9 @@
|
|||||||
import { getLocation } from '../utils/geolocation.js';
|
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) {
|
export async function handleContactRequest(request) {
|
||||||
const { email, token } = await request.json();
|
const { email, token } = await request.json();
|
||||||
@ -15,26 +17,29 @@ export async function handleContactRequest(request) {
|
|||||||
|
|
||||||
// Get location info
|
// Get location info
|
||||||
const location = await getLocation(ip);
|
const location = await getLocation(ip);
|
||||||
|
const locationString = location.city + ' - ' + location.country;
|
||||||
|
|
||||||
// Verify the Turnstile token
|
if (TURNSTILE_ENABLED) {
|
||||||
const verificationResponse = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
// Verify the Turnstile token
|
||||||
method: 'POST',
|
const verificationResponse = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
headers: { 'Content-Type': 'application/json' },
|
||||||
secret: TURNSTILE_SECRET_KEY,
|
body: JSON.stringify({
|
||||||
response: token,
|
secret: TURNSTILE_SECRET_KEY,
|
||||||
}),
|
response: token,
|
||||||
});
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
const verificationData = await verificationResponse.json();
|
const verificationData = await verificationResponse.json();
|
||||||
|
|
||||||
if (!verificationData.success) {
|
if (!verificationData.success) {
|
||||||
return new Response('Turnstile verification failed', { status: 400 });
|
return new Response('Turnstile verification failed', { status: 400 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the email and location to Notion
|
// Add the email and location to Notion
|
||||||
try {
|
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' }), {
|
return new Response(JSON.stringify({ success: true, message: 'Email processed and added to Notion' }), {
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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' },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/routes/utils.js
Normal file
15
src/routes/utils.js
Normal file
@ -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
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
const GEOLOCATION_API_KEY = 'your-geolocation-api-key';
|
const GEOLOCATION_API_KEY = '1489c8e06deb4693bf31733a18fe2351';
|
||||||
|
|
||||||
export async function getLocation(ip) {
|
export async function getLocation(ip) {
|
||||||
const geoApiUrl = `https://api.ipgeolocation.io/ipgeo?apiKey=${GEOLOCATION_API_KEY}&ip=${ip}`;
|
const geoApiUrl = `https://api.ipgeolocation.io/ipgeo?apiKey=${GEOLOCATION_API_KEY}&ip=${ip}`;
|
||||||
|
|||||||
161
src/utils/htmlgen.js
Normal file
161
src/utils/htmlgen.js
Normal file
@ -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 = `<a href="${item.href}">${text}</a>`;
|
||||||
|
if (bold) text = `<strong>${text}</strong>`;
|
||||||
|
if (italic) text = `<em>${text}</em>`;
|
||||||
|
if (strikethrough) text = `<s>${text}</s>`;
|
||||||
|
if (underline) text = `<u>${text}</u>`;
|
||||||
|
if (code) text = `<code>${text}</code>`;
|
||||||
|
if (item.href) text = `<a href="${item.href}">${text}</a>`;
|
||||||
|
if (color && color !== 'default') text = `<span style="color:${color};">${text}</span>`;
|
||||||
|
|
||||||
|
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 += `<ol>`;
|
||||||
|
olListMode = true;
|
||||||
|
} else if (olListMode == true && block.type != 'numbered_list_item') {
|
||||||
|
blogContentHTML += `</ol>`;
|
||||||
|
olListMode = false;
|
||||||
|
}
|
||||||
|
if (ulListMode == false && block.type == 'bulleted_list_item') {
|
||||||
|
blogContentHTML += `<ul>`;
|
||||||
|
ulListMode = true;
|
||||||
|
} else if (ulListMode == true && block.type != 'bulleted_list_item') {
|
||||||
|
blogContentHTML += `</ul>`;
|
||||||
|
ulListMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var children = '';
|
||||||
|
|
||||||
|
if (block.has_children == true) {
|
||||||
|
const getChildren = async (id) => {
|
||||||
|
console.log('Getting child:', id);
|
||||||
|
const childrenBlocksContent = await getNotionBlocksWithCache(id);
|
||||||
|
children += await generateBlockHTML(childrenBlocksContent);
|
||||||
|
};
|
||||||
|
await getChildren(block.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (block.type) {
|
||||||
|
case 'paragraph':
|
||||||
|
blogContentHTML += `<p>${richTextToHTML(block['paragraph'].rich_text)}</p>\n`;
|
||||||
|
break;
|
||||||
|
case 'heading_1':
|
||||||
|
blogContentHTML += `<h1>${richTextToHTML(block['heading_1'].rich_text)}</h1>\n`;
|
||||||
|
break;
|
||||||
|
case 'heading_2':
|
||||||
|
blogContentHTML += `<h2>${richTextToHTML(block['heading_2'].rich_text)}</h2>\n`;
|
||||||
|
break;
|
||||||
|
case 'heading_3':
|
||||||
|
blogContentHTML += `<h3>${richTextToHTML(block['heading_3'].rich_text)}</h3>\n`;
|
||||||
|
break;
|
||||||
|
case 'numbered_list_item':
|
||||||
|
blogContentHTML += `<li>${richTextToHTML(block['numbered_list_item'].rich_text)}\n${children}</li>\n`;
|
||||||
|
break;
|
||||||
|
case 'bulleted_list_item':
|
||||||
|
blogContentHTML += `<li>${richTextToHTML(block['bulleted_list_item'].rich_text)}\n${children}</li>\n`;
|
||||||
|
break;
|
||||||
|
case 'callout':
|
||||||
|
var calloutIcon = '';
|
||||||
|
if (block['callout'].icon != null) {
|
||||||
|
if (block['callout'].icon.type == 'emoji') {
|
||||||
|
calloutIcon = `<i class='tbemoji'>${block['callout'].icon.emoji}</i>`;
|
||||||
|
}
|
||||||
|
if (block['callout'].icon.type == 'file') {
|
||||||
|
calloutIcon = `<img class='tbicon' src='${block['callout'].icon.file.url}'></img>`;
|
||||||
|
}
|
||||||
|
if (block['callout'].icon.type == 'external') {
|
||||||
|
const svgIcon = await getNotionSVGIcon(block['callout'].icon.external.url);
|
||||||
|
calloutIcon = `<div class='tbicon'>${svgIcon}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var richText = '';
|
||||||
|
if (block['callout'].rich_text.length != 0) {
|
||||||
|
richText = `<p>${richTextToHTML(block['callout'].rich_text)}</p>`;
|
||||||
|
}
|
||||||
|
blogContentHTML += `<div class='tbcallout'">${calloutIcon}<div class='tbcalloutcontent'>${richText}\n${children}\n</div></div>\n`;
|
||||||
|
break;
|
||||||
|
case 'divider':
|
||||||
|
blogContentHTML += `<hr>\n`;
|
||||||
|
break;
|
||||||
|
case 'quote':
|
||||||
|
blogContentHTML += `<p class='tbquote'>${richTextToHTML(block['quote'].rich_text)}</p>\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 = `<p class='tbimagecaption'>${richTextToHTML(block['image'].caption)}</p>`;
|
||||||
|
}
|
||||||
|
blogContentHTML += `<div class='tbimage'><img src='${imageSrc}'></img>${imageCaption}</div>\n`;
|
||||||
|
break;
|
||||||
|
case 'column_list':
|
||||||
|
blogContentHTML += `<div class='tbcolumnlist'>${children}</div>\n`;
|
||||||
|
break;
|
||||||
|
case 'column':
|
||||||
|
blogContentHTML += `<div class='tbcolumn'>${children}</div>\n`;
|
||||||
|
break;
|
||||||
|
case 'table':
|
||||||
|
var tableClass = 'tbtable ';
|
||||||
|
if (block['table'].has_row_header == true) {
|
||||||
|
tableClass += 'tbrowheader ';
|
||||||
|
}
|
||||||
|
if (block['table'].has_column_header == true) {
|
||||||
|
tableCellTag = 'th';
|
||||||
|
tableClass += 'tbcolumnheader';
|
||||||
|
}
|
||||||
|
blogContentHTML += `<table class='${tableClass}'>${children}</table>\n`;
|
||||||
|
break;
|
||||||
|
case 'table_row':
|
||||||
|
blogContentHTML += `<tr>\n`;
|
||||||
|
block['table_row'].cells.forEach((cell) => {
|
||||||
|
blogContentHTML += `<${tableCellTag}>${richTextToHTML(cell)}</${tableCellTag}>\n`;
|
||||||
|
});
|
||||||
|
if (tableCellTag == 'th') {
|
||||||
|
tableCellTag = 'td';
|
||||||
|
}
|
||||||
|
blogContentHTML += `</tr>\n`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('Unhandled type:', block.type);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocksContent.length - 1 == index) {
|
||||||
|
if (olListMode && block.type == 'numbered_list_item') {
|
||||||
|
blogContentHTML += `</ol>\n`;
|
||||||
|
}
|
||||||
|
if (ulListMode && block.type == 'bulleted_list_item') {
|
||||||
|
blogContentHTML += `</ul>\n`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return blogContentHTML;
|
||||||
|
}
|
||||||
@ -1,41 +1,231 @@
|
|||||||
const NOTION_API_TOKEN = 'your-notion-api-token';
|
const { Client } = require('@notionhq/client');
|
||||||
|
|
||||||
export async function addToNotionDatabase(params, database) {
|
let cacheVersion;
|
||||||
const notionApiUrl = `https://api.notion.com/v1/pages`;
|
const iconColorSubstitutions = [
|
||||||
|
['#D44C47', '#FF453A'],
|
||||||
|
['#55534E', '#FFFFFF'],
|
||||||
|
['#448361', '#32D74B'],
|
||||||
|
['#337ea9', '#0A84FF'],
|
||||||
|
['#9065B0', '#BF5AF2'],
|
||||||
|
['#CB912F', '#FFD60A'],
|
||||||
|
['#C14C8A', '#FF375F'],
|
||||||
|
['#d9730d', '#FF9F0A'],
|
||||||
|
];
|
||||||
|
|
||||||
const notionPayload = {
|
flushCache();
|
||||||
parent: {
|
|
||||||
database_id: database,
|
// Initialize Notion client with API token
|
||||||
},
|
const notion = new Client({
|
||||||
properties: {},
|
auth: process.env.NOTION_AUTH, // Use the API token stored in environment variables
|
||||||
};
|
});
|
||||||
|
|
||||||
|
function fnv1aHash(str) {
|
||||||
|
let hash = 0x811c9dc5;
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
hash ^= str.charCodeAt(i);
|
||||||
|
hash += (hash << 1) + (hash << 4) + (hash << 7) + (hash << 8) + (hash << 24);
|
||||||
|
}
|
||||||
|
return (hash >>> 0).toString(16);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function flushCache() {
|
||||||
|
cacheVersion = Math.random().toString(36).substring(2, 10);
|
||||||
|
console.log('New cache version:', cacheVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addToNotionDatabase(params, databaseId) {
|
||||||
|
console.log('Adding to Notion DB...');
|
||||||
|
|
||||||
|
// Construct the properties object for the page
|
||||||
|
const properties = {};
|
||||||
|
|
||||||
// Loop through each key-value pair in the params object
|
// Loop through each key-value pair in the params object
|
||||||
for (const [key, value] of Object.entries(params)) {
|
for (const [key, value] of Object.entries(params)) {
|
||||||
notionPayload.properties[key] = {
|
// Handling specific property types based on the key
|
||||||
rich_text: [
|
if (key === 'Email') {
|
||||||
{
|
// Expecting the "Email" property to be of type "email"
|
||||||
text: {
|
properties[key] = {
|
||||||
content: value,
|
email: value,
|
||||||
},
|
};
|
||||||
|
} else if (key === 'Location') {
|
||||||
|
// Expecting the "Location" property to be of type "select"
|
||||||
|
properties[key] = {
|
||||||
|
select: {
|
||||||
|
name: value, // Assume value is a valid option in the "Location" select dropdown
|
||||||
},
|
},
|
||||||
],
|
};
|
||||||
};
|
} else if (key === 'Name') {
|
||||||
|
// Expecting the "Name" property to be of type "title"
|
||||||
|
properties[key] = {
|
||||||
|
title: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Default to "rich_text" for any other fields
|
||||||
|
properties[key] = {
|
||||||
|
rich_text: [
|
||||||
|
{
|
||||||
|
text: {
|
||||||
|
content: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(notionApiUrl, {
|
try {
|
||||||
method: 'POST',
|
// Create the page in the Notion database
|
||||||
headers: {
|
const response = await notion.pages.create({
|
||||||
Authorization: `Bearer ${NOTION_API_TOKEN}`,
|
parent: { database_id: databaseId },
|
||||||
'Content-Type': 'application/json',
|
properties: properties,
|
||||||
'Notion-Version': '2021-05-13',
|
});
|
||||||
},
|
|
||||||
body: JSON.stringify(notionPayload),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
console.log('Added to Notion DB!');
|
||||||
|
return response;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to add to Notion:', error);
|
||||||
throw new Error('Failed to add to Notion');
|
throw new Error('Failed to add to Notion');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return response.json();
|
|
||||||
|
export async function getNotionDatabaseWithCache(databaseId, queryParams = {}, cacheTtl = 60) {
|
||||||
|
// Unique cache key based on database ID and query parameters
|
||||||
|
const queryHash = fnv1aHash(JSON.stringify(queryParams));
|
||||||
|
const cacheKey = `https://cache.api/notion-db-${databaseId}-${queryHash}-${cacheVersion}`;
|
||||||
|
// Try to get data from cache first
|
||||||
|
const cache = caches.default;
|
||||||
|
let response = await cache.match(cacheKey);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
// Cache hit - return the cached data
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(`Cache hit for database ${databaseId} query ${queryHash}`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache miss - fetch from Notion API
|
||||||
|
console.log(`Cache miss for database ${databaseId} query ${queryHash}, fetching from Notion API`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Query the database
|
||||||
|
const result = await notion.databases.query({
|
||||||
|
database_id: databaseId,
|
||||||
|
...queryParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract the results
|
||||||
|
const data = result.results;
|
||||||
|
|
||||||
|
// Store in cache
|
||||||
|
const newResponse = new Response(JSON.stringify(data), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Cache-Control': `max-age=${cacheTtl}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await cache.put(cacheKey, newResponse);
|
||||||
|
console.log(`Stored database ${databaseId} query ${queryHash} in cache for ${cacheTtl} seconds`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching Notion database ${databaseId} query ${queryHash}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNotionBlocksWithCache(id, queryParams = {}, cacheTtl = 60) {
|
||||||
|
// Unique cache key based on ID and query parameters
|
||||||
|
const queryHash = fnv1aHash(JSON.stringify(queryParams));
|
||||||
|
const cacheKey = `https://cache.api/notion-page-${id}-${queryHash}-${cacheVersion}`;
|
||||||
|
// Try to get data from cache first
|
||||||
|
const cache = caches.default;
|
||||||
|
let response = await cache.match(cacheKey);
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
// Cache hit - return the cached data
|
||||||
|
const data = await response.json();
|
||||||
|
console.log(`Cache hit for page ${id} query ${queryHash}`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache miss - fetch from Notion API
|
||||||
|
console.log(`Cache miss for page ${id} query ${queryHash}, fetching from Notion API`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Query notion blocks
|
||||||
|
const result = await notion.blocks.children.list({
|
||||||
|
block_id: id,
|
||||||
|
page_size: 50,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract the results
|
||||||
|
const data = result.results;
|
||||||
|
|
||||||
|
// Store in cache
|
||||||
|
const newResponse = new Response(JSON.stringify(data), {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Cache-Control': `max-age=${cacheTtl}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await cache.put(cacheKey, newResponse);
|
||||||
|
console.log(`Stored page ${id} query ${queryHash} in cache for ${cacheTtl} seconds`);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching Notion page ${id} query ${queryHash}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNotionSVGIcon(url) {
|
||||||
|
try {
|
||||||
|
// Unique cache key based on URL hash
|
||||||
|
const urlHash = fnv1aHash(url);
|
||||||
|
const cacheKey = `https://cache.api/notion-icon-${urlHash}-${cacheVersion}`;
|
||||||
|
|
||||||
|
// Try to get data from cache first
|
||||||
|
const cache = caches.default;
|
||||||
|
const cachedResponse = await cache.match(cacheKey);
|
||||||
|
console.log(cachedResponse);
|
||||||
|
if (cachedResponse) {
|
||||||
|
const svgText = await cachedResponse.text();
|
||||||
|
console.log(`Cache hit for icon ${urlHash}`);
|
||||||
|
return svgText;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache miss - fetch from Notion API
|
||||||
|
console.log(`Cache miss for icon ${urlHash}, fetching from Notion`);
|
||||||
|
const response = await fetch(url, { method: 'GET' });
|
||||||
|
if (!response.ok) throw new Error(`Failed to fetch SVG from Notion: ${response.statusText}`);
|
||||||
|
let svgText = await response.text();
|
||||||
|
|
||||||
|
// Replace colors using direct string replacement
|
||||||
|
iconColorSubstitutions.forEach(([searchColor, replaceColor]) => {
|
||||||
|
svgText = svgText.replaceAll(searchColor, replaceColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
const newResponse = new Response(svgText, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/svg+xml',
|
||||||
|
'Cache-Control': 'public, max-age=31536000, immutable',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await cache.put(cacheKey, newResponse);
|
||||||
|
console.log(`Stored icon ${urlHash} in cache for a year.`);
|
||||||
|
|
||||||
|
return svgText;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
src/utils/svgcolormap.js
Normal file
0
src/utils/svgcolormap.js
Normal file
@ -8,40 +8,44 @@
|
|||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"compatibility_date": "2025-02-24",
|
"compatibility_date": "2025-02-24",
|
||||||
"observability": {
|
"observability": {
|
||||||
"enabled": true
|
"enabled": true,
|
||||||
}
|
},
|
||||||
/**
|
/**
|
||||||
* Smart Placement
|
* Smart Placement
|
||||||
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||||
*/
|
*/
|
||||||
// "placement": { "mode": "smart" },
|
// "placement": { "mode": "smart" },
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bindings
|
* Bindings
|
||||||
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
|
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
|
||||||
* databases, object storage, AI inference, real-time communication and more.
|
* databases, object storage, AI inference, real-time communication and more.
|
||||||
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
|
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Environment Variables
|
* Environment Variables
|
||||||
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||||
*/
|
*/
|
||||||
// "vars": { "MY_VARIABLE": "production_value" },
|
"vars": {
|
||||||
/**
|
"BLOGS_DB": "1abdd26d60b680d68150f73a53183b6e",
|
||||||
* Note: Use secrets to store sensitive data.
|
"SOCIALS_DB": "1abdd26d60b6803bb3ddec9b179fa345",
|
||||||
* https://developers.cloudflare.com/workers/configuration/secrets/
|
"REFERRERS_DB": "1abdd26d60b680dcb677e18b76d50f26",
|
||||||
*/
|
},
|
||||||
|
/**
|
||||||
|
* Note: Use secrets to store sensitive data.
|
||||||
|
* https://developers.cloudflare.com/workers/configuration/secrets/
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static Assets
|
* Static Assets
|
||||||
* https://developers.cloudflare.com/workers/static-assets/binding/
|
* https://developers.cloudflare.com/workers/static-assets/binding/
|
||||||
*/
|
*/
|
||||||
// "assets": { "directory": "./public/", "binding": "ASSETS" },
|
// "assets": { "directory": "./public/", "binding": "ASSETS" },
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service Bindings (communicate between multiple Workers)
|
* Service Bindings (communicate between multiple Workers)
|
||||||
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||||
*/
|
*/
|
||||||
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
|
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user