Skip to content

Commit

Permalink
feat(api): ratelimiting
Browse files Browse the repository at this point in the history
  • Loading branch information
taskylizard committed Jan 1, 2025
1 parent 26213d9 commit 1148023
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 150 deletions.
17 changes: 17 additions & 0 deletions api/middleware/ratelimit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default defineEventHandler(async (event) => {
const { cloudflare } = event.context

// FIXME: THIS IS NOT RECOMMENDED. BUT I WILL USE IT FOR NOW
// Not recommended: many users may share a single IP, especially on mobile networks
// or when using privacy-enabling proxies
const ipAddress = getHeader(event, 'CF-Connecting-IP') ?? ''

const { success } = await // KILL YOURSELF
(cloudflare.env as unknown as Env).RATE_LIMITER.limit({
key: ipAddress
})

if (!success) {
throw createError('Failure – global rate limit exceeded')
}
})
136 changes: 49 additions & 87 deletions api/routes/single-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,99 +13,61 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const files = (
[
'adblockvpnguide.md',
'ai.md',
'android-iosguide.md',
'audiopiracyguide.md',
'beginners-guide.md',
'devtools.md',
'downloadpiracyguide.md',
'edupiracyguide.md',
'file-tools.md',
'gaming-tools.md',
'gamingpiracyguide.md',
'img-tools.md',
'internet-tools.md',
'linuxguide.md',
'miscguide.md',
'non-english.md',
'readingpiracyguide.md',
'social-media-tools.md',
'storage.md',
'system-tools.md',
'text-tools.md',
'torrentpiracyguide.md',
'unsafesites.md',
'video-tools.md',
'videopiracyguide.md'
] as const
).map((file) => ({
name: file,
url: `https://raw.githubusercontent.com/fmhy/edit/main/docs/${file}`
}))

import { fetcher } from 'itty-fetcher'
import { createStorage } from 'unstorage'
import cloudflareKVBindingDriver from 'unstorage/drivers/cloudflare-kv-binding'
export default defineCachedEventHandler(
async (event) => {
let body = '<!-- This is autogenerated content, do not edit manually. -->\n'

// Look inside the docs directory
const GITHUB_REPO = 'https://api.github.com/repos/fmhy/edit/contents/docs/'
const EXCLUDE_FILES = [
'README.md',
'index.md',
'feedback.md',
'posts.md',
'sandbox.md'
]
const EXCLUDE_DIRECTORIES = ['posts/']

interface File {
name: string
path: string
sha: string
size: number
url: string
html_url: string
git_url: string
download_url: string | null
type: string
_links: {
self: string
git: string
html: string
}
}

export default defineEventHandler(async (event) => {
const markdownStorage = createStorage({
driver: cloudflareKVBindingDriver({ binding: 'STORAGE' })
})

let body = '<!-- This is autogenerated content, do not edit manually. -->\n'
const f = fetcher({
headers: {
'User-Agent': 'taskylizard'
}
})

try {
// Fetch the list of files in the repository
const indexCacheKey = "INDEX"
let files = await markdownStorage.getItem<File[]>(indexCacheKey)

if (!files) {
files = await f.get(GITHUB_REPO)
await markdownStorage.setItem(indexCacheKey, files, { ttl: 60 * 60 * 24 * 7 })
}

// Filter out the excluded files and non-markdown files
const markdownFiles = files.filter((file: File) => {
const isExcludedFile = EXCLUDE_FILES.includes(file.name)
const isInExcludedDirectory = EXCLUDE_DIRECTORIES.some((dir) =>
file.path.startsWith(dir)
)
const isMarkdownFile = file.name.endsWith('.md')

return isMarkdownFile && !isExcludedFile && !isInExcludedDirectory
})

// Fetch and concatenate the contents of the markdown files with caching
const contents = await Promise.all(
markdownFiles.map(async (file: File) => {
const cached = await markdownStorage.getItem(file.name)
if (cached) return cached

const content = await f.get<string>(file.download_url)
if (content) {
await markdownStorage.setItem(file.name, content, { ttl: 60 * 60 })
}
files.map(async (file) => {
const content = await $fetch<string>(file.url)

return content
})
)

body += contents.join('\n\n')
} catch (error) {
return {
status: 500,
body: `Error fetching markdown files: ${error.message}`
}
}

// biome-ignore lint/correctness/noUndeclaredVariables: <explanation>
appendResponseHeaders(event, {
'content-type': 'text/markdown;charset=utf-8',
'cache-control': 'public, max-age=3600'
})
return body
})
appendResponseHeaders(event, {
'content-type': 'text/markdown;charset=utf-8',
'cache-control': 'public, max-age=7200'
})
return body
},
{
maxAge: 60 * 60,
name: 'single-page',
getKey: () => 'default' /* Can be extended in the future */
}
)
7 changes: 6 additions & 1 deletion api/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{
"extends": "../.nitro/types/tsconfig.json"
"extends": "../.nitro/types/tsconfig.json",
"compilerOptions": {
"types": [
"@cloudflare/workers-types"
]
}
}
6 changes: 6 additions & 0 deletions api/worker-configuration.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Generated by Wrangler by running `wrangler types api/worker-configuration.d.ts`

interface Env {
STORAGE: KVNamespace;
RATE_LIMITER: RateLimit;
}
8 changes: 7 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"extends": ["@taskylizard/biome-config", "./.cache/imports.json"],
"extends": [
"@taskylizard/biome-config",
"./.cache/imports.json"
],
"files": {
"ignore": [
"docs/.vitepress/**/*.vue",
Expand Down Expand Up @@ -42,6 +45,9 @@
},
"linter": {
"rules": {
"correctness": {
"noUndeclaredVariables": "off"
},
"style": {
"useFilenamingConvention": "off",
"noDefaultExport": "off"
Expand Down
3 changes: 3 additions & 0 deletions docs/.vitepress/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
> [!NOTE]
> The website is no longer getting new features. It is now in maintenance mode. Please do not open issues or PRs.
This is the website source code to be used with [VitePress](https://vitepress.dev/).

Licensed under the Apache License v2.0, see [LICENSE](./LICENSE) for more information.
6 changes: 5 additions & 1 deletion nitro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//https://nitro.unjs.io/config

import nitroCloudflareBindings from 'nitro-cloudflare-dev'
import { defineNitroConfig } from 'nitropack/config'

export default defineNitroConfig({
modules: [nitroCloudflareBindings],
preset: 'cloudflare_module',
compatibilityDate: '2024-11-01',
runtimeConfig: {
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"api:dev": "nitropack dev",
"api:prepare": "nitropack prepare",
"api:preview": "node .output/server/index.mjs",
"api:typegen": "wrangler types api/worker-configuration.d.ts",
"docs:build": "vitepress build docs/",
"docs:dev": "vitepress dev docs/",
"docs:preview": "vitepress preview docs/",
Expand Down Expand Up @@ -41,6 +42,7 @@
},
"devDependencies": {
"@biomejs/biome": "^1.9.3",
"@cloudflare/workers-types": "^4.20241230.0",
"@ianvs/prettier-plugin-sort-imports": "^4.3.1",
"@iconify-json/carbon": "^1.2.3",
"@iconify-json/heroicons-solid": "^1.2.0",
Expand All @@ -51,6 +53,7 @@
"@taskylizard/biome-config": "^1.0.5",
"@types/node": "^20.16.12",
"@types/nprogress": "^0.2.3",
"nitro-cloudflare-dev": "^0.2.1",
"prettier": "^3.3.3",
"prettier-plugin-pkgsort": "^0.2.1",
"prettier-plugin-tailwindcss": "^0.6.8",
Expand Down
Loading

0 comments on commit 1148023

Please sign in to comment.