diff --git a/.github/workflows/buildAndDeploy.yml b/.github/workflows/buildAndDeploy.yml index 8c12e20..9765771 100644 --- a/.github/workflows/buildAndDeploy.yml +++ b/.github/workflows/buildAndDeploy.yml @@ -4,6 +4,7 @@ on: branches: - main - feature/authorization-header + - feature/new-router jobs: deploy: runs-on: ubuntu-latest diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..eba06b5 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + "configurations": [ + + { + "name": "Wrangler", + "type": "node", + "request": "attach", + "port": 9229, + "cwd": "/", + "resolveSourceMapLocations": null, + "attachExistingChildren": false, + "autoAttachChildProcesses": false, + "sourceMaps": true // works with or without this line + } + ] + } + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 681f4a0..ee92aa1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,12 +8,13 @@ "name": "d1-tutorial", "version": "0.0.0", "dependencies": { - "@cfworker/jwt": "^4.0.6" + "@cfworker/jwt": "^4.0.6", + "hono": "^4.2.9" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20230419.0", + "@cloudflare/workers-types": "^4.20240423.0", "typescript": "^5.0.4", - "wrangler": "^3.23.0" + "wrangler": "^3.47.0" } }, "node_modules/@cfworker/jwt": { @@ -37,18 +38,21 @@ "integrity": "sha512-p9wWxB1n+D0Mm9YFYcU80Ea9rUqYDAkuYvSPjKezCSjFKGDm12x9S/8md33h7a4IAzxtO9pfOVoVLvVI2hpR9g==" }, "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.2.0.tgz", - "integrity": "sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.3.2.tgz", + "integrity": "sha512-EeEjMobfuJrwoctj7FA1y1KEbM0+Q1xSjobIEyie9k4haVEBB7vkDvsasw1pM3rO39mL2akxIAzLMUAtrMHZhA==", "dev": true, "dependencies": { "mime": "^3.0.0" + }, + "engines": { + "node": ">=16.13" } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20231218.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20231218.0.tgz", - "integrity": "sha512-547gOmTIVmRdDy7HNAGJUPELa+fSDm2Y0OCxqAtQOz0GLTDu1vX61xYmsb2rn91+v3xW6eMttEIpbYokKjtfJA==", + "version": "1.20240419.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20240419.0.tgz", + "integrity": "sha512-PGVe9sYWULHfvGhN0IZh8MsskNG/ufnBSqPbgFCxJHCTrVXLPuC35EoVaforyqjKRwj3U35XMyGo9KHcGnTeHQ==", "cpu": [ "x64" ], @@ -62,9 +66,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20231218.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20231218.0.tgz", - "integrity": "sha512-b39qrU1bKolCfmKFDAnX4vXcqzISkEUVE/V8sMBsFzxrIpNAbcUHBZAQPYmS/OHIGB94KjOVokvDi7J6UNurPw==", + "version": "1.20240419.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20240419.0.tgz", + "integrity": "sha512-z4etQSPiD5Gcjs962LiC7ZdmXnN6SGof5KrYoFiSI9X9kUvpuGH/lnjVVPd+NnVNeDU2kzmcAIgyZjkjTaqVXQ==", "cpu": [ "arm64" ], @@ -78,9 +82,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20231218.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20231218.0.tgz", - "integrity": "sha512-dMUF1wA+0mybm6hHNOCgY/WMNMwomPPs4I7vvYCgwHSkch0Q2Wb7TnxQZSt8d1PK/myibaBwadrlIxpjxmpz3w==", + "version": "1.20240419.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20240419.0.tgz", + "integrity": "sha512-lBwhg0j3sYTFMsEb4bOClbVje8nqrYOu0H3feQlX+Eks94JIhWPkf8ywK4at/BUc1comPMhCgzDHwc2OMPUGgg==", "cpu": [ "x64" ], @@ -94,9 +98,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20231218.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20231218.0.tgz", - "integrity": "sha512-2s5uc8IHt0QmWyKxAr1Fy+4b8Xy0b/oUtlPnm5MrKi2gDRlZzR7JvxENPJCpCnYENydS8lzvkMiAFECPBccmyQ==", + "version": "1.20240419.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20240419.0.tgz", + "integrity": "sha512-ZMY6wwWkxL+WPq8ydOp/irSYjAnMhBz1OC1+4z+OANtDs2beaZODmq7LEB3hb5WUAaTPY7DIjZh3DfDfty0nYg==", "cpu": [ "arm64" ], @@ -110,9 +114,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20231218.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20231218.0.tgz", - "integrity": "sha512-oN5hz6TXUDB5YKUN5N3QWAv6cYz9JjTZ9g16HVyoegVFEL6/zXU3tV19MBX2IvlE11ab/mRogEv9KXVIrHfKmA==", + "version": "1.20240419.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20240419.0.tgz", + "integrity": "sha512-YJjgaJN2yGTkV7Cr4K3i8N4dUwVQTclT3Pr3NpRZCcLjTszwlE53++XXDnHMKGXBbSguIizaVbmcU2EtmIXyeQ==", "cpu": [ "x64" ], @@ -126,9 +130,9 @@ } }, "node_modules/@cloudflare/workers-types": { - "version": "4.20230904.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20230904.0.tgz", - "integrity": "sha512-IX4oJCe14ctblSPZBlW64BVZ9nYLUo6sD2I5gu3hX0ywByYWm1OuoKm9Xb/Zpbj8Ph18Z7Ryii6u2/ocnncXdA==", + "version": "4.20240423.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20240423.0.tgz", + "integrity": "sha512-ssuccb3j+URp6mP2p0PcQE9vmS3YeKBQnALHF9P3yQfUAFozuhTsDTbqmL+zPrJvUcG7SL2xVQkNDF9QJeKDZw==", "dev": true }, "node_modules/@cspotcode/source-map-support": { @@ -518,18 +522,18 @@ } }, "node_modules/@fastify/busboy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", - "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", "dev": true, "engines": { "node": ">=14" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", - "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "engines": { "node": ">=6.0.0" @@ -832,6 +836,14 @@ "node": ">= 0.4" } }, + "node_modules/hono": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.2.9.tgz", + "integrity": "sha512-59FAv52UxDWUt/NlC0NzrRCjeVCThUnVlqlrKYm+k80XujBu6uJwBIa5gACKKZWobjA0MJ6Vds0I3URKf383Cw==", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -908,9 +920,9 @@ } }, "node_modules/miniflare": { - "version": "3.20231218.2", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20231218.2.tgz", - "integrity": "sha512-rCUI2OjqCf3fZVdmSX4DOZQRzSDvHp/oL2vjER/cvJEdWSYiqRxDp2oO7A7JcEo1/Y+kPa5VQ1pFfdZpjBcpFg==", + "version": "3.20240419.0", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20240419.0.tgz", + "integrity": "sha512-fIev1PP4H+fQp5FtvzHqRY2v5s+jxh/a0xAhvM5fBNXvxWX7Zod1OatXfXwYbse3hqO3KeVMhb0osVtrW0NwJg==", "dev": true, "dependencies": { "@cspotcode/source-map-support": "0.8.1", @@ -920,8 +932,8 @@ "exit-hook": "^2.2.1", "glob-to-regexp": "^0.4.1", "stoppable": "^1.1.0", - "undici": "^5.22.1", - "workerd": "1.20231218.0", + "undici": "^5.28.2", + "workerd": "1.20240419.0", "ws": "^8.11.0", "youch": "^3.2.2", "zod": "^3.20.6" @@ -1179,9 +1191,9 @@ } }, "node_modules/undici": { - "version": "5.28.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", - "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", "dev": true, "dependencies": { "@fastify/busboy": "^2.0.0" @@ -1191,9 +1203,9 @@ } }, "node_modules/workerd": { - "version": "1.20231218.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20231218.0.tgz", - "integrity": "sha512-AGIsDvqCrcwhoA9kb1hxOhVAe53/xJeaGZxL4FbYI9FvO17DZwrnqGq+6eqItJ6Cfw1ZLmf3BM+QdMWaL2bFWQ==", + "version": "1.20240419.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20240419.0.tgz", + "integrity": "sha512-9yV98KpkQgG+bdEsKEW8i1AYZgxns6NVSfdOVEB2Ue1pTMtIEYfUyqUE+O2amisRrfaC3Pw4EvjtTmVaoetfeg==", "dev": true, "hasInstallScript": true, "bin": { @@ -1203,26 +1215,26 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20231218.0", - "@cloudflare/workerd-darwin-arm64": "1.20231218.0", - "@cloudflare/workerd-linux-64": "1.20231218.0", - "@cloudflare/workerd-linux-arm64": "1.20231218.0", - "@cloudflare/workerd-windows-64": "1.20231218.0" + "@cloudflare/workerd-darwin-64": "1.20240419.0", + "@cloudflare/workerd-darwin-arm64": "1.20240419.0", + "@cloudflare/workerd-linux-64": "1.20240419.0", + "@cloudflare/workerd-linux-arm64": "1.20240419.0", + "@cloudflare/workerd-windows-64": "1.20240419.0" } }, "node_modules/wrangler": { - "version": "3.23.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.23.0.tgz", - "integrity": "sha512-mOWPL02/popoF6jjb4TT8au4yvo8VFu6O6/IwcRN74rlgwaGS0XvlkFtbofBGAdDUHAh5DXkuoPNS871mXNuJg==", + "version": "3.53.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.53.0.tgz", + "integrity": "sha512-JxkvCQekL9j8Mu4CEKM/HEVyDnymWzKQuMUuJH0yum1AilutD5HAP9kVVYmvu7BvwlRyRUAj8TI5OUxXnLCEpQ==", "dev": true, "dependencies": { - "@cloudflare/kv-asset-handler": "^0.2.0", + "@cloudflare/kv-asset-handler": "0.3.2", "@esbuild-plugins/node-globals-polyfill": "^0.2.3", "@esbuild-plugins/node-modules-polyfill": "^0.2.2", "blake3-wasm": "^2.1.5", "chokidar": "^3.5.3", "esbuild": "0.17.19", - "miniflare": "3.20231218.2", + "miniflare": "3.20240419.0", "nanoid": "^3.3.3", "path-to-regexp": "^6.2.0", "resolve": "^1.22.8", @@ -1240,12 +1252,20 @@ }, "optionalDependencies": { "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20240419.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } } }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", "dev": true, "engines": { "node": ">=10.0.0" @@ -1281,9 +1301,9 @@ } }, "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "version": "3.23.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.5.tgz", + "integrity": "sha512-fkwiq0VIQTksNNA131rDOsVJcns0pfVUjHzLrNBiF/O/Xxb5lQyEXkhZWcJ7npWsYlvs+h0jFWXXy4X46Em1JA==", "dev": true, "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 15beae8..3142081 100644 --- a/package.json +++ b/package.json @@ -7,11 +7,12 @@ "start": "wrangler dev" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20230419.0", + "@cloudflare/workers-types": "^4.20240423.0", "typescript": "^5.0.4", - "wrangler": "^3.23.0" + "wrangler": "^3.47.0" }, "dependencies": { - "@cfworker/jwt": "^4.0.6" + "@cfworker/jwt": "^4.0.6", + "hono": "^4.2.9" } } diff --git a/src/index.ts b/src/index.ts index 5289018..8ff959f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,302 +1,297 @@ import { parseJwt } from '@cfworker/jwt'; +import { Hono } from 'hono'; +import { cors } from 'hono/cors' +import { stream } from 'hono/streaming' -export interface Env { - Content: R2Bucket; - Data: R2Bucket; - Analytics: AnalyticsEngineDataset; - DB: D1Database; - apikey: string; - apihost: string; - gatewayKey: string; +type Env = { + Content: R2Bucket; + Data: R2Bucket; + Analytics: AnalyticsEngineDataset; + DB: D1Database; + apikey: string; + apihost: string; + gatewayKey: string; } -export default { - async fetch(request: Request, env: Env) { - const allowedOrigins: Array = [ - "https://cultpodcasts.com".toLowerCase(), - "http://localhost:4200".toLowerCase(), - "https://local.cultpodcasts.com:4200".toLowerCase() - ]; - let origin = request.headers.get("Origin"); - if (origin == null || allowedOrigins.indexOf(origin.toLowerCase()) == -1) { - origin = allowedOrigins[0]; - } - const leechHandlingActive = false; - const { pathname, searchParams } = new URL(request.url); - const homeRoute = "/homepage"; - const searchRoute = "/api"; - const submitRoute = "/submit"; - const corsHeaders = { - "Access-Control-Allow-Origin": origin, - "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", - "Access-Control-Max-Age": "86400", - "Access-Control-Allow-Headers": "content-type,authorization", - "Access-Control-Allow-Credentials": "true" - }; +const allowedOrigins: Array = [ + "https://cultpodcasts.com", + "http://localhost:4200", + "https://local.cultpodcasts.com:4200", + "https://localhost:4200", + "https://feature-api-authorisation.website-83e.pages.dev" +]; - if (request.method === "OPTIONS") { - return new Response(null, { - headers: { - ...corsHeaders, - }, - }); - } +function getOrigin(origin: string | null | undefined) { + if (origin == null || allowedOrigins.indexOf(origin.toLowerCase()) == -1) { + origin = allowedOrigins[0]; + } + return origin; +} - const jwt = request.headers.get('Authorization'); - if (jwt) { - const issuer = 'https://dev-q3x2z6aofdzbjkkf.us.auth0.com/'; - const audience = 'https://api.cultpodcasts.com/'; +const app = new Hono<{ Bindings: Env }>(); - const result = await parseJwt(jwt.slice(7), issuer, audience); - if (!result.valid) { - console.log(result.reason); // Invalid issuer/audience, expired, etc - } else { - console.log(result.payload); // { iss, sub, aud, iat, exp, ...claims } - } - } +app.use('/*', cors({ + origin: (origin, c) => { + return getOrigin(origin); + }, + allowHeaders: ['content-type', 'authorization'], + allowMethods: ['GET', 'HEAD', 'POST', 'OPTIONS'], + maxAge: 86400, + credentials: true, +})) - if (pathname.startsWith(homeRoute) && request.method === "GET") { - const object = await env.Content.get("homepage"); +app.get('/homepage', async (c) => { + let object: R2ObjectBody | null = null; + try { + object = await c.env.Content.get("homepage"); + } catch (e) { + } - if (object === null) { - return new Response("Object Not Found", { status: 404 }); - } + if (object === null) { + return new Response("Object Not Found", { status: 404 }); + } - const headers = new Headers(); - object.writeHttpMetadata(headers); - headers.set("etag", object.httpEtag); - headers.set("Cache-Control", "max-age=600"); - headers.set("Access-Control-Allow-Origin", origin); - headers.set("Access-Control-Allow-Methods", "GET,OPTIONS"); + c.header("etag", object.httpEtag); + c.header("Cache-Control", "max-age=600"); + c.header("Access-Control-Allow-Origin", getOrigin(c.req.header("Origin"))); + c.header("Access-Control-Allow-Methods", "GET,OPTIONS"); - return new Response(object.body, { headers }); - } + return stream(c, async (stream) => { + stream.onAbort(() => { + console.log('Aborted!') + }) + await stream.pipe(object.body) + }) +}); - if (pathname.startsWith(searchRoute) && request.method === "POST") { - const url = `${env.apihost}`; - let dataPoint: AnalyticsEngineDataPoint = { indexes: [], blobs: [] }; - let ipAddress: string = ""; - let asn: string = ""; - let city: string = ""; - if (request.cf) { - dataPoint.blobs!.push(request.cf.clientTrustScoretr as string); - asn = request.cf.asn as string; - dataPoint.blobs!.push(asn); - ipAddress = request.headers.get('cf-connecting-ip') as string - dataPoint.blobs!.push(ipAddress); - dataPoint.blobs!.push(request.headers.get('User-Agent') as string); - if (request.cf.city) { - city = request.cf.city as string; - dataPoint.blobs!.push(city); - } - if (request.cf.country) { - dataPoint.blobs!.push(request.cf.country as string); - } - } - let isLeech: boolean = false; +app.post("/search", async (c) => { + const leechHandlingActive: boolean = false; + const url = `${c.env.apihost}`; + let dataPoint: AnalyticsEngineDataPoint = { indexes: [], blobs: [] }; + let ipAddress: string = ""; + let asn: string = ""; + let city: string = ""; + if (c.req.raw.cf != undefined && c.req.raw.cf) { + dataPoint.blobs!.push(c.req.raw.cf.clientTrustScoretr as string); + asn = c.req.raw.cf.asn as string; + dataPoint.blobs!.push(asn); + ipAddress = c.req.header('cf-connecting-ip') as string + dataPoint.blobs!.push(ipAddress); + dataPoint.blobs!.push(c.req.header('User-Agent') as string); + if (c.req.raw.cf.city) { + city = c.req.raw.cf.city as string; + dataPoint.blobs!.push(city); + } + if (c.req.raw.cf.country) { + dataPoint.blobs!.push(c.req.raw.cf.country as string); + } + } + let isLeech: boolean = false; + + if (leechHandlingActive) { + const object = await c.env.Data.get("leeches"); - if (leechHandlingActive) { - const object = await env.Data.get("leeches"); + if (object != null) { + var leeches: string[] = await object.json(); + console.log(`ip-address: ${ipAddress} index: ${leeches.indexOf(ipAddress)} lookup: ${JSON.stringify(leeches)}`); + if (leeches.indexOf(ipAddress) >= 0) { + isLeech = true; + } + } + } - if (object != null) { - var leeches: string[] = await object.json(); - console.log(`ip-address: ${ipAddress} index: ${leeches.indexOf(ipAddress)} lookup: ${JSON.stringify(leeches)}`); - if (leeches.indexOf(ipAddress) >= 0) { - isLeech = true; - } - } + if (!isLeech) { + return c.req + .json() + .then(async (data: any) => { + let requestBody = JSON.stringify(data); + let index: string = ""; + if (data.search) { + index = data.search; + dataPoint.blobs!.push(data.search); + dataPoint.blobs!.push("search"); + } + if (data.filter) { + let filter: string = data.filter; + if (filter.indexOf("(podcastName eq '") == 0) { + let query = filter.slice(17, -2); + if (index) { + index += " podcast=" + query; + } else { + index = "podcast=" + query; } + dataPoint.blobs!.push(query); + dataPoint.blobs!.push("podcast"); + } else if (filter.indexOf("subjects/any(s: s eq '") == 0) { + let query = filter.slice(22, - 2); + if (index) { + index += " subject=" + query; + } else { + index = "subject=" + query; + } + dataPoint.blobs!.push(query); + dataPoint.blobs!.push("subject"); + } else { + console.log("Unrecognised search filter"); + } + } + dataPoint.indexes!.push(index); - if (!isLeech) { - return request - .json() - .then(async (data: any) => { - let requestBody = JSON.stringify(data); - let index: string = ""; - if (data.search) { - index = data.search; - dataPoint.blobs!.push(data.search); - dataPoint.blobs!.push("search"); - } - if (data.filter) { - let filter: string = data.filter; - if (filter.indexOf("(podcastName eq '") == 0) { - let query = filter.slice(17, -2); - if (index) { - index += " podcast=" + query; - } else { - index = "podcast=" + query; - } - dataPoint.blobs!.push(query); - dataPoint.blobs!.push("podcast"); - } else if (filter.indexOf("subjects/any(s: s eq '") == 0) { - let query = filter.slice(22, - 2); - if (index) { - index += " subject=" + query; - } else { - index = "subject=" + query; - } - dataPoint.blobs!.push(query); - dataPoint.blobs!.push("subject"); - } else { - console.log("Unrecognised search filter"); - } - } - dataPoint.indexes!.push(index); + if (!data.search && !data.filter) { + console.log("Unrecognised search request"); + } + if (dataPoint) { + dataPoint.blobs?.push(data.skip); + dataPoint.blobs?.push(data.orderby); + } - if (!data.search && !data.filter) { - console.log("Unrecognised search request"); - } - if (dataPoint) { - dataPoint.blobs?.push(data.skip); - dataPoint.blobs?.push(data.orderby); - } + let response = await fetch(url, { + cf: { + cacheEverything: true, + cacheTtl: 600 + }, + headers: { + "api-key": c.env.apikey, + "content-type": "application/json;charset=UTF-8", + }, + body: requestBody, + method: "POST" + }); + if (dataPoint) { + dataPoint.blobs?.push(response.status.toString()); + c.env.Analytics.writeDataPoint(dataPoint); + } + c.header("Cache-Control", "max-age=600"); + c.header("Content-Type", "application/json"); + c.header("Access-Control-Allow-Origin", getOrigin(c.req.header("Origin"))); + c.header("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); - let response = await fetch(url, { - cf: { - cacheEverything: true, - cacheTtl: 600 - }, - headers: { - "api-key": env.apikey, - "content-type": "application/json;charset=UTF-8", - }, - body: requestBody, - method: "POST" - }); - if (dataPoint) { - dataPoint.blobs?.push(response.status.toString()); - env.Analytics.writeDataPoint(dataPoint); - } + if (response.status != 200) { + return c.json(response.body, 400) + } - const headers = new Headers(); - headers.set("Cache-Control", "max-age=600"); - headers.set("Content-Type", "application/json"); - headers.set("Access-Control-Allow-Origin", origin!); - headers.set("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); + let body: any = await response.json(); + body["@odata.context"] = null; + return c.json(body, 200); + }); + } else { + const leechResponse = { + "@odata.context": null, + "@odata.count": 1, + "@search.facets": { + "subjects": [], + "podcastName": [] + }, + "value": [{ + "@search.score": 1.0, + "id": "00000000-0000-0000-0000-000000000000", + "episodeTitle": "Leech Detected", + "podcastName": "Leech Detected", + "episodeDescription": "Contact leeching@cultpodcasts.com.", + "release": "1970-01-01T00:00:00Z", + "duration": "01:00:00.0000000", + "explicit": false, + "spotify": null, + "apple": null, + "youtube": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "subjects": [] + }] + }; + c.header("Cache-Control", "max-age=600"); + c.header("Content-Type", "application/json"); + c.header("Access-Control-Allow-Origin", getOrigin(c.req.header("Origin"))); + c.header("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); - if (response.status != 200) { - return new Response(response.body, { headers, status: response.status }) - } + dataPoint.blobs!.push("Leech"); + c.env.Analytics.writeDataPoint(dataPoint); + return c.json(leechResponse, 200); + } +}); - let body: any = await response.json(); - body["@odata.context"] = null; - return new Response(JSON.stringify(body), { headers }) - }); - } else { - const leechResponse = { - "@odata.context": null, - "@odata.count": 1, - "@search.facets": { - "subjects": [], - "podcastName": [] - }, - "value": [{ - "@search.score": 1.0, - "id": "00000000-0000-0000-0000-000000000000", - "episodeTitle": "Leech Detected", - "podcastName": "Leech Detected", - "episodeDescription": "Contact leeching@cultpodcasts.com.", - "release": "1970-01-01T00:00:00Z", - "duration": "01:00:00.0000000", - "explicit": false, - "spotify": null, - "apple": null, - "youtube": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", - "subjects": [] - }] - }; - const headers = new Headers(); - headers.set("Cache-Control", "max-age=600"); - headers.set("Content-Type", "application/json"); - headers.set("Access-Control-Allow-Origin", origin); - headers.set("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); +app.get("/submit", async (c) => { + if (c.req.header("key") != c.env.gatewayKey) { + return c.json({ message: "Unauthorised" }, 401); + } + let submissionIds = c.env.DB + .prepare("SELECT id FROM urls WHERE state=0"); + let result = await submissionIds.all(); + if (result.success) { + const inClause = result.results + .map((urlId) => { + if (!Number.isInteger(urlId.id)) { throw Error("invalid id, expected an integer"); } + return urlId.id; + }) + .join(','); + let urls = "SELECT id, url, timestamp_date, ip_address, country, user_agent FROM urls WHERE id IN ($urlIds)"; + urls = urls.replace('$urlIds', inClause); + let urlResults = await c.env.DB + .prepare(urls) + .run(); + if (urlResults.success) { + let update = "UPDATE urls SET state=1 WHERE id IN ($urlIds)"; + update = update.replace('$urlIds', inClause); + let raiseState = await c.env.DB + .prepare(update) + .all(); + if (raiseState.success) { + return c.json(urlResults); + } else { + return c.text("Failure to raise state of new submissons in ids " + result.results.join(", "), 400); + } + } else { + return c.text("Unable to retrieve new submissions", 500); + } + } else { + return c.text(result.error!, 500); + } +}); - dataPoint.blobs!.push("Leech"); - env.Analytics.writeDataPoint(dataPoint); - var response = new Response(JSON.stringify(leechResponse), { headers }); - return response; - } - } +app.post("/submit", async (c) => { + c.header("Cache-Control", "max-age=600"); + c.header("Content-Type", "application/json"); + c.header("Access-Control-Allow-Origin", getOrigin(c.req.header("Origin"))); + c.header("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); - if (pathname.startsWith(submitRoute)) { - if (request.method === "POST") { + return c.req + .json() + .then(async (data: any) => { + let url: URL | undefined; + let urlParam = data.url; + if (urlParam == null) { + return c.json({ error: "Missing url param." }, 400); + } + try { + url = new URL(urlParam); + } catch { + return c.json({ error: `Invalid url '${data.url}'.` }, 400); + } + let insert = c.env.DB + .prepare("INSERT INTO urls (url, timestamp, timestamp_date, ip_address, country, user_agent) VALUES (?, ?, ?, ?, ?, ?)") + .bind(url.toString(), Date.now(), new Date().toLocaleString(), c.req.header("CF-Connecting-IP"), c.req.header("CF-IPCountry"), c.req.header("User-Agent")); + let result = await insert.run(); - const headers = new Headers(); - headers.set("Cache-Control", "max-age=600"); - headers.set("Content-Type", "application/json"); - headers.set("Access-Control-Allow-Origin", origin); - headers.set("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); + if (result.success) { + return c.json({ success: "Submitted" }); + } else { + return c.json({ error: "Unable to accept" }, 400); + } + }); +}); - return request - .json() - .then(async (data: any) => { - let url: URL | undefined; - let urlParam = data.url; - if (urlParam == null) { - return new Response(JSON.stringify({ error: "Missing url param." }), { headers, status: 400 }); - } - try { - url = new URL(urlParam); - } catch { - return new Response(JSON.stringify({ error: `Invalid url '${data.url}'.` }), { headers, status: 400 }); - } - let insert = env.DB - .prepare("INSERT INTO urls (url, timestamp, timestamp_date, ip_address, country, user_agent) VALUES (?, ?, ?, ?, ?, ?)") - .bind(url.toString(), Date.now(), new Date().toLocaleString(), request.headers.get("CF-Connecting-IP"), request.headers.get("CF-IPCountry"), request.headers.get("User-Agent")); - let result = await insert.run(); +export default app; - if (result.success) { - return new Response(JSON.stringify({ success: "Submitted" }), { headers }); - } else { - return new Response(JSON.stringify({ error: "Unable to accept" }), { headers, status: 400 }); - } - }); - } else if (request.method === "GET" && request.headers.get("key") === env.gatewayKey) { - let submissionIds = env.DB - .prepare("SELECT id FROM urls WHERE state=0"); - let result = await submissionIds.all(); - if (result.success) { - const inClause = result.results - .map((urlId) => { - if (!Number.isInteger(urlId.id)) { throw Error("invalid id, expected an integer"); } - return urlId.id; - }) - .join(','); - let urls = "SELECT id, url, timestamp_date, ip_address, country, user_agent FROM urls WHERE id IN ($urlIds)"; - urls = urls.replace('$urlIds', inClause); - let urlResults = await env.DB - .prepare(urls) - .run(); - if (urlResults.success) { - let update = "UPDATE urls SET state=1 WHERE id IN ($urlIds)"; - update = update.replace('$urlIds', inClause); - let raiseState = await env.DB - .prepare(update) - .run(); - if (raiseState.success) { - return new Response(JSON.stringify(urlResults.results)); - } else { - return new Response("Failure to raise state of new submissons in ids " + result.results.join(", "), { status: 400 }) - } - } else { - return new Response("Unable to retrieve new submissions", { status: 500 }); - } - } else { - return new Response(result.error, { status: 500 }); - } - } - } +async function auth(request: Request) { + const jwt = request.headers.get('Authorization'); + if (jwt) { + const issuer = 'https://dev-q3x2z6aofdzbjkkf.us.auth0.com/'; + const audience = 'https://api.cultpodcasts.com/'; - return new Response( - `Call ${homeRoute} to get the last 7-days of new releases -${pathname}`, - { - headers: { - ...corsHeaders, - }, - } - ); - }, -}; + const result = await parseJwt(jwt.slice(7), issuer, audience); + if (!result.valid) { + console.log(result.reason); + } else { + console.log(result.payload); // { iss, sub, aud, iat, exp, ...claims } + } + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 2cb9189..bd842c4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,101 +1,19 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Projects */ - // "incremental": true, /* Enable incremental compilation */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - - /* Language and Environment */ "target": "es2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, "lib": ["es2021"] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, "jsx": "react" /* Specify what JSX code is generated. */, - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ - // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - - /* Modules */ "module": "es2022" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ - "types": ["@cloudflare/workers-types"] /* Specify type package names to be included without being referenced in a source file. */, - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "types": [ "@cloudflare/workers-types/2023-07-01"] /* Specify type package names to be included without being referenced in a source file. */, "resolveJsonModule": true /* Enable importing .json files */, - // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ - - /* JavaScript Support */ "allowJs": true /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */, "checkJs": false /* Enable error reporting in type-checked JavaScript files. */, - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ - - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ "noEmit": true /* Disable emitting files from a compilation. */, - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - - /* Interop Constraints */ "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, "allowSyntheticDefaultImports": true /* Allow 'import x from y' when a module doesn't have a default export. */, - // "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, - - /* Type Checking */ "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ - // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ - // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ } } diff --git a/wrangler.toml b/wrangler.toml index 2952aba..e55680d 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -1,10 +1,10 @@ name = "api" main = "src/index.ts" -compatibility_date = "2023-09-04" +compatibility_date = "2024-05-02" r2_buckets = [ - { binding = "Content", bucket_name = "content", preview_bucket_name = "" }, - { binding = "Data", bucket_name = "data", preview_bucket_name = "" } + { binding = "Content", bucket_name = "content", preview_bucket_name = "content" }, + { binding = "Data", bucket_name = "data", preview_bucket_name = "data" } ] [[d1_databases]]