feat(api): ratelimiting

This commit is contained in:
taskylizard 2025-01-01 11:25:05 +00:00
parent 26213d9f91
commit 1148023b1a
No known key found for this signature in database
GPG key ID: 1820131ED1A24120
10 changed files with 139 additions and 150 deletions

View file

@ -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')
}
})

View file

@ -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 */
}
)

View file

@ -1,3 +1,8 @@
{
"extends": "../.nitro/types/tsconfig.json"
"extends": "../.nitro/types/tsconfig.json",
"compilerOptions": {
"types": [
"@cloudflare/workers-types"
]
}
}

6
api/worker-configuration.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
// Generated by Wrangler by running `wrangler types api/worker-configuration.d.ts`
interface Env {
STORAGE: KVNamespace;
RATE_LIMITER: RateLimit;
}