Skip to content
Closed
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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@ Response: {
}
```

#### Download File

Returns the file contents with `Content-Disposition: attachment`, which makes
browsers save the file instead of rendering it. Binary files are served as
`application/octet-stream`, text files as `text/plain`.

```http
GET /files/download?file_id=1
# or
GET /files/download?file_path=path/to/file.txt
# or
GET /files/download/path/to/file.txt

Response: raw file contents
```

#### List Files

```http
Expand Down
2 changes: 2 additions & 0 deletions routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ This is a simple file server API, it has the following API:

/health - Health check
/files/get?file_path=... - Get a file
/files/download?file_path=... - Download a file (attachment)
/files/download/<file_path> - Download a file by path
/files/list - List all files
/files/upsert - Upsert a file

Expand Down
56 changes: 56 additions & 0 deletions tests/routes/files-download.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { test, expect } from "bun:test"
import { getTestServer } from "tests/fixtures/get-test-server"

test("download by file_id", async () => {
const { axios } = await getTestServer()

const upsertRes = await axios.post("/files/upsert", {
file_path: "/by-id.txt",
text_content: "Downloaded by id",
})
const fileId = upsertRes.data.file.file_id

const downloadRes = await axios.get("/files/download", {
params: { file_id: fileId },
})
expect(downloadRes.status).toBe(200)
expect(downloadRes.data).toBe("Downloaded by id")
expect(downloadRes.headers.get("content-disposition")).toBe(
'attachment; filename="by-id.txt"',
)
})

test("download nested path via path form", async () => {
const { axios } = await getTestServer()

await axios.post("/files/upsert", {
file_path: "/nested/dir/file.txt",
text_content: "Nested content",
})

const downloadRes = await axios.get("/files/download/nested/dir/file.txt")
expect(downloadRes.status).toBe(200)
expect(downloadRes.data).toBe("Nested content")
expect(downloadRes.headers.get("content-disposition")).toBe(
'attachment; filename="file.txt"',
)
})

test("nested path form returns 404 for missing file", async () => {
const { axios } = await getTestServer()

expect(
axios.get("/files/download/some/missing/file.txt"),
).rejects.toMatchObject({
status: 404,
data: "File not found",
})
})

test("index page lists download endpoints", async () => {
const { axios } = await getTestServer()

const indexRes = await axios.get("/")
expect(indexRes.status).toBe(200)
expect(indexRes.data).toContain("/files/download")
})
Loading