mirror of
https://github.com/fmhy/edit.git
synced 2025-07-29 23:32:17 +10:00
feat(api): ratelimiting
This commit is contained in:
parent
26213d9f91
commit
1148023b1a
10 changed files with 139 additions and 150 deletions
17
api/middleware/ratelimit.ts
Normal file
17
api/middleware/ratelimit.ts
Normal 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')
|
||||
}
|
||||
})
|
|
@ -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 */
|
||||
}
|
||||
)
|
||||
|
|
|
@ -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
6
api/worker-configuration.d.ts
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Generated by Wrangler by running `wrangler types api/worker-configuration.d.ts`
|
||||
|
||||
interface Env {
|
||||
STORAGE: KVNamespace;
|
||||
RATE_LIMITER: RateLimit;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue