Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ POSTGRESQL_DB=framna-docs
REPOSITORY_NAME_SUFFIX=-openapi
HIDDEN_REPOSITORIES=
NEW_PROJECT_TEMPLATE_REPOSITORY=shapehq/starter-openapi
PROXY_API_MAXIMUM_FILE_SIZE_IN_MEGABYTES = 10
PROXY_API_TIMEOUT_IN_SECONDS = 30
GITHUB_WEBHOOK_SECRET=preshared secret also put in app configuration in GitHub
GITHUB_WEBHOK_REPOSITORY_ALLOWLIST=
GITHUB_WEBHOK_REPOSITORY_DISALLOWLIST=
Expand Down
68 changes: 65 additions & 3 deletions src/app/api/proxy/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { NextRequest, NextResponse } from "next/server"
import { makeAPIErrorResponse, makeUnauthenticatedAPIErrorResponse } from "@/common"
import { env, makeAPIErrorResponse, makeUnauthenticatedAPIErrorResponse } from "@/common"
import { session } from "@/composition"

const ErrorName = {
MAX_FILE_SIZE_EXCEEDED: "MaxFileSizeExceededError",
TIMEOUT: "TimeoutError"
}

export async function GET(req: NextRequest) {
const isAuthenticated = await session.getIsAuthenticated()
if (!isAuthenticated) {
Expand All @@ -17,6 +22,63 @@ export async function GET(req: NextRequest) {
} catch {
return makeAPIErrorResponse(400, "Invalid \"url\" query parameter.")
}
const file = await fetch(url).then(r => r.blob())
return new NextResponse(file, { status: 200 })
try {
const maxMegabytes = Number(env.getOrThrow("PROXY_API_MAXIMUM_FILE_SIZE_IN_MEGABYTES"))
const timeoutInSeconds = Number(env.getOrThrow("PROXY_API_TIMEOUT_IN_SECONDS"))
const maxBytes = maxMegabytes * 1024 * 1024
const file = await downloadFile({ url, maxBytes, timeoutInSeconds })
return new NextResponse(file, { status: 200 })
} catch (error) {
if (error instanceof Error == false) {
return makeAPIErrorResponse(500, "An unknown error occurred.")
}
if (error.name === ErrorName.MAX_FILE_SIZE_EXCEEDED) {
return makeAPIErrorResponse(413, "The operation was aborted.")
} else if (error.name === ErrorName.TIMEOUT) {
return makeAPIErrorResponse(408, "The operation timed out.")
} else {
return makeAPIErrorResponse(500, error.message)
}
}
}

async function downloadFile(params: {
url: URL,
maxBytes: number,
timeoutInSeconds: number
}): Promise<Blob> {
const { url, maxBytes, timeoutInSeconds } = params
const abortController = new AbortController()
const timeoutSignal = AbortSignal.timeout(timeoutInSeconds * 1000)
const response = await fetch(url, {
signal: AbortSignal.any([abortController.signal, timeoutSignal])
})
if (!response.body) {
throw new Error("Response body unavailable")
}
let totalBytes = 0
let didExceedMaxBytes = false
const reader = response.body.getReader()
const chunks: Uint8Array[] = []
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
const { done, value } = await reader.read()
if (done) {
break
}
totalBytes += value.length
chunks.push(value)
if (totalBytes >= maxBytes) {
didExceedMaxBytes = true
abortController.abort()
break
}
}
if (didExceedMaxBytes) {
const error = new Error("Maximum file size exceeded")
error.name = ErrorName.MAX_FILE_SIZE_EXCEEDED
throw error
}
return new Blob(chunks)
}
Loading