Compare commits
10 Commits
00ab308280
...
chore/depe
| Author | SHA1 | Date | |
|---|---|---|---|
| f27ba4922a | |||
| 4e89e9783c | |||
| 455cb53aa8 | |||
| 418a553429 | |||
| e25c715fb7 | |||
| 9fefe5c65d | |||
| e2944fd828 | |||
| a4e6a5784a | |||
| 241078316c | |||
| 85b6f8df2c |
3
.github/workflows/deploy-homelab.yml
vendored
3
.github/workflows/deploy-homelab.yml
vendored
@@ -39,6 +39,9 @@ jobs:
|
|||||||
export TRUST_PROXY=true
|
export TRUST_PROXY=true
|
||||||
export COOKIE_SECURE=true
|
export COOKIE_SECURE=true
|
||||||
export JWT_SECRET=${{ secrets.JWT_SECRET }}
|
export JWT_SECRET=${{ secrets.JWT_SECRET }}
|
||||||
|
export PORT=${{ vars.PORT }}
|
||||||
|
export BASE_URL=${{ vars.BASE_URL }}
|
||||||
|
export MAX_FILE_SIZE=${{ vars.MAX_FILE_SIZE }}
|
||||||
docker compose -f docker-compose.yml down
|
docker compose -f docker-compose.yml down
|
||||||
docker compose -f docker-compose.yml up -d --build
|
docker compose -f docker-compose.yml up -d --build
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -141,3 +141,6 @@ dist
|
|||||||
# Project
|
# Project
|
||||||
data/
|
data/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
# Claude Code (personal, machine-specific)
|
||||||
|
.claude/settings.local.json
|
||||||
|
|||||||
13
CLAUDE.md
13
CLAUDE.md
@@ -26,3 +26,16 @@ Simple file-sharing platform. TypeScript + Fastify + SQLite.
|
|||||||
- Break large functions into smaller ones, extract duplicate code.
|
- Break large functions into smaller ones, extract duplicate code.
|
||||||
- Search for duplicated code in tests and extract into reusable helpers.
|
- Search for duplicated code in tests and extract into reusable helpers.
|
||||||
- Commit after every logical set of changes. Keep commits small and focused.
|
- Commit after every logical set of changes. Keep commits small and focused.
|
||||||
|
|
||||||
|
## Autonomous Commits (Standing Authorization)
|
||||||
|
|
||||||
|
Any Claude Code instance working on this project has standing authorization to commit work without asking. Apply this proactively, not when reminded.
|
||||||
|
|
||||||
|
- After every logical set of changes that builds and tests cleanly (`npm run build && npm test`), create a commit immediately. Do not wait for the user to ask.
|
||||||
|
- Use Conventional Commits (`feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`, `perf:`, `ci:`) with a concise subject. Add a body only when the *why* isn't obvious from the diff.
|
||||||
|
- One logical change per commit. If you touched multiple unrelated things, split into separate commits.
|
||||||
|
- After committing, also push to the tracked remote (`git push`) — standard `git push` to the current branch's upstream is authorized. **Never** force-push (`--force`, `--force-with-lease`) and **never** push to `main` from a feature branch directly without an explicit request.
|
||||||
|
- **Never** stage `.env`, `data/`, secrets, or anything matching a credential pattern. Prefer `git add <specific files>` over `git add -A`/`git add .`.
|
||||||
|
- If the build or tests fail, do **not** commit. Fix or revert, then either commit a working state or report the failure.
|
||||||
|
- Skip the commit if there are no changes (don't create empty commits).
|
||||||
|
- If you find yourself amending or rewriting prior commits, stop and ask first — autonomy covers new commits, not history rewrites.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ services:
|
|||||||
nanodrop:
|
nanodrop:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
- "${PORT:-3000}:3000"
|
- "127.0.0.1:${PORT:-3000}:${PORT:-3000}"
|
||||||
environment: { <<: *env }
|
environment: { <<: *env }
|
||||||
volumes:
|
volumes:
|
||||||
- nanodrop-data:/app/data
|
- nanodrop-data:/app/data
|
||||||
|
|||||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -729,9 +729,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@fastify/static": {
|
"node_modules/@fastify/static": {
|
||||||
"version": "9.0.0",
|
"version": "9.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@fastify/static/-/static-9.1.3.tgz",
|
||||||
"integrity": "sha512-r64H8Woe/vfilg5RTy7lwWlE8ZZcTrc3kebYFMEUBrMqlydhQyoiExQXdYAy2REVpST/G35+stAM8WYp1WGmMA==",
|
"integrity": "sha512-aXrYtsiryLhRxRNaxNqsn7FUISeb7rB9q4eHUPIot5aeQBLNahnz1m6thzm7JWC1poSGXS9XrX8DvuMivp2hkQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -1471,9 +1471,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/brace-expansion": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "5.0.4",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^4.0.2"
|
"balanced-match": "^4.0.2"
|
||||||
@@ -1738,15 +1738,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fast-jwt": {
|
"node_modules/fast-jwt": {
|
||||||
"version": "6.1.0",
|
"version": "6.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/fast-jwt/-/fast-jwt-6.2.4.tgz",
|
||||||
"integrity": "sha512-cGK/TXlud8INL49Iv7yRtZy0PHzNJId1shfqNCqdF0gOlWiy+1FPgjxX+ZHp/CYxFYDaoNnxeYEGzcXSkahUEQ==",
|
"integrity": "sha512-IoQa53wI6TbARU2yelb0L44ggFQnP2qVcwswCSYHbCAWuwpr70icDb3QjG0v01I8Tt01rVGDkN/rRvpk0lKFTA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lukeed/ms": "^2.0.2",
|
"@lukeed/ms": "^2.0.2",
|
||||||
"asn1.js": "^5.4.1",
|
"asn1.js": "^5.4.1",
|
||||||
"ecdsa-sig-formatter": "^1.0.11",
|
"ecdsa-sig-formatter": "^1.0.11",
|
||||||
"mnemonist": "^0.40.0"
|
"mnemonist": "^0.40.0",
|
||||||
|
"safe-regex2": "^5.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
@@ -1790,9 +1791,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fastify": {
|
"node_modules/fastify": {
|
||||||
"version": "5.7.4",
|
"version": "5.8.5",
|
||||||
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.5.tgz",
|
||||||
"integrity": "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA==",
|
"integrity": "sha512-Yqptv59pQzPgQUSIm87hMqHJmdkb1+GPxdE6vW6FRyVE9G86mt7rOghitiU4JHRaTyDUk9pfeKmDeu70lAwM4Q==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -1814,7 +1815,7 @@
|
|||||||
"fast-json-stringify": "^6.0.0",
|
"fast-json-stringify": "^6.0.0",
|
||||||
"find-my-way": "^9.0.0",
|
"find-my-way": "^9.0.0",
|
||||||
"light-my-request": "^6.0.0",
|
"light-my-request": "^6.0.0",
|
||||||
"pino": "^10.1.0",
|
"pino": "^9.14.0 || ^10.1.0",
|
||||||
"process-warning": "^5.0.0",
|
"process-warning": "^5.0.0",
|
||||||
"rfdc": "^1.3.1",
|
"rfdc": "^1.3.1",
|
||||||
"secure-json-parse": "^4.0.0",
|
"secure-json-parse": "^4.0.0",
|
||||||
@@ -2304,9 +2305,9 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -2354,9 +2355,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.8",
|
"version": "8.5.13",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
|
||||||
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
|
"integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -2608,9 +2609,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/safe-regex2": {
|
"node_modules/safe-regex2": {
|
||||||
"version": "5.0.0",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz",
|
||||||
"integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==",
|
"integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -2624,6 +2625,9 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ret": "~0.5.0"
|
"ret": "~0.5.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"safe-regex2": "bin/safe-regex2.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/safe-stable-stringify": {
|
"node_modules/safe-stable-stringify": {
|
||||||
@@ -2977,9 +2981,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.3.1",
|
"version": "7.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
|
||||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ export interface FileRow {
|
|||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateFileParams {
|
interface CreateFileParams {
|
||||||
id: string;
|
id: string;
|
||||||
userId: number;
|
userId: number;
|
||||||
originalName: string;
|
originalName: string;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import type Database from 'better-sqlite3';
|
import type Database from 'better-sqlite3';
|
||||||
|
|
||||||
export interface UserRow {
|
interface UserRow {
|
||||||
id: number;
|
id: number;
|
||||||
username: string;
|
username: string;
|
||||||
password_hash: string;
|
password_hash: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateUserParams {
|
interface CreateUserParams {
|
||||||
username: string;
|
username: string;
|
||||||
passwordHash: string;
|
passwordHash: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { FastifyPluginAsync } from 'fastify';
|
import type { FastifyPluginAsync } from 'fastify';
|
||||||
import { extname } from 'path';
|
import { extname } from 'path';
|
||||||
import { readFile } from 'fs/promises';
|
import { createReadStream } from 'fs';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
import type Database from 'better-sqlite3';
|
import type Database from 'better-sqlite3';
|
||||||
import type { Config } from '../config.ts';
|
import type { Config } from '../config.ts';
|
||||||
@@ -23,6 +23,29 @@ interface Deps {
|
|||||||
logger: Logger;
|
logger: Logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseRangeHeader(header: string, fileSize: number): { start: number; end: number } | null {
|
||||||
|
const match = header.match(/^bytes=(\d*)-(\d*)$/);
|
||||||
|
if (!match) return null;
|
||||||
|
|
||||||
|
const [, rawStart, rawEnd] = match;
|
||||||
|
|
||||||
|
let start: number;
|
||||||
|
let end: number;
|
||||||
|
|
||||||
|
if (rawStart === '') {
|
||||||
|
// Suffix range: bytes=-N (last N bytes)
|
||||||
|
const suffix = parseInt(rawEnd, 10);
|
||||||
|
start = Math.max(0, fileSize - suffix);
|
||||||
|
end = fileSize - 1;
|
||||||
|
} else {
|
||||||
|
start = parseInt(rawStart, 10);
|
||||||
|
end = rawEnd === '' ? fileSize - 1 : parseInt(rawEnd, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start > end || end >= fileSize) return null;
|
||||||
|
return { start, end };
|
||||||
|
}
|
||||||
|
|
||||||
export const pageRoutes: FastifyPluginAsync<{ deps: Deps }> = async (app, { deps }) => {
|
export const pageRoutes: FastifyPluginAsync<{ deps: Deps }> = async (app, { deps }) => {
|
||||||
const { db, config, logger } = deps;
|
const { db, config, logger } = deps;
|
||||||
|
|
||||||
@@ -140,7 +163,7 @@ export const pageRoutes: FastifyPluginAsync<{ deps: Deps }> = async (app, { deps
|
|||||||
reply.type('text/html').send(fileViewPage(file, isOwner));
|
reply.type('text/html').send(fileViewPage(file, isOwner));
|
||||||
});
|
});
|
||||||
|
|
||||||
// GET /f/:id/raw — serve raw file
|
// GET /f/:id/raw — serve raw file with range request support
|
||||||
app.get<{ Params: { id: string } }>('/f/:id/raw', async (request, reply) => {
|
app.get<{ Params: { id: string } }>('/f/:id/raw', async (request, reply) => {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
const file = getFileById(db, id);
|
const file = getFileById(db, id);
|
||||||
@@ -151,12 +174,37 @@ export const pageRoutes: FastifyPluginAsync<{ deps: Deps }> = async (app, { deps
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filePath = getFilePath(config.uploadDir, file.stored_name);
|
const filePath = getFilePath(config.uploadDir, file.stored_name);
|
||||||
const data = await readFile(filePath);
|
|
||||||
const safeFilename = file.original_name.replace(/["\\\r\n]/g, '_');
|
const safeFilename = file.original_name.replace(/["\\\r\n]/g, '_');
|
||||||
|
|
||||||
reply
|
reply
|
||||||
.header('Content-Type', file.mime_type)
|
.header('Content-Type', file.mime_type)
|
||||||
.header('Content-Disposition', `inline; filename="${safeFilename}"`)
|
.header('Content-Disposition', `inline; filename="${safeFilename}"`)
|
||||||
.send(data);
|
.header('Accept-Ranges', 'bytes');
|
||||||
|
|
||||||
|
const rangeHeader = request.headers['range'];
|
||||||
|
|
||||||
|
if (!rangeHeader) {
|
||||||
|
reply.header('Content-Length', file.size);
|
||||||
|
return reply.send(createReadStream(filePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = parseRangeHeader(rangeHeader, file.size);
|
||||||
|
|
||||||
|
if (!range) {
|
||||||
|
return reply
|
||||||
|
.status(416)
|
||||||
|
.header('Content-Range', `bytes */${file.size}`)
|
||||||
|
.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { start, end } = range;
|
||||||
|
const chunkSize = end - start + 1;
|
||||||
|
|
||||||
|
return reply
|
||||||
|
.status(206)
|
||||||
|
.header('Content-Range', `bytes ${start}-${end}/${file.size}`)
|
||||||
|
.header('Content-Length', chunkSize)
|
||||||
|
.send(createReadStream(filePath, { start, end }));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export async function loginAs(app: FastifyInstance, username: string, password:
|
|||||||
return cookie.split(';')[0].replace('token=', '');
|
return cookie.split(';')[0].replace('token=', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MultipartFile {
|
interface MultipartFile {
|
||||||
filename: string;
|
filename: string;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
data: Buffer;
|
data: Buffer;
|
||||||
|
|||||||
@@ -125,6 +125,51 @@ describe('GET /f/:id and GET /f/:id/raw', () => {
|
|||||||
const res = await ctx.app.inject({ method: 'GET', url: `/f/${fileId}/raw` });
|
const res = await ctx.app.inject({ method: 'GET', url: `/f/${fileId}/raw` });
|
||||||
expect(res.statusCode).toBe(200);
|
expect(res.statusCode).toBe(200);
|
||||||
expect(res.body).toBe('hello!');
|
expect(res.body).toBe('hello!');
|
||||||
|
expect(res.headers['accept-ranges']).toBe('bytes');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 206 for a byte range request', async () => {
|
||||||
|
const res = await ctx.app.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/f/${fileId}/raw`,
|
||||||
|
headers: { range: 'bytes=0-3' },
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(206);
|
||||||
|
expect(res.headers['content-range']).toBe('bytes 0-3/6');
|
||||||
|
expect(res.headers['content-length']).toBe('4');
|
||||||
|
expect(res.body).toBe('hell');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 206 for an open-ended range request', async () => {
|
||||||
|
const res = await ctx.app.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/f/${fileId}/raw`,
|
||||||
|
headers: { range: 'bytes=2-' },
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(206);
|
||||||
|
expect(res.headers['content-range']).toBe('bytes 2-5/6');
|
||||||
|
expect(res.body).toBe('llo!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 206 for a suffix range request', async () => {
|
||||||
|
const res = await ctx.app.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/f/${fileId}/raw`,
|
||||||
|
headers: { range: 'bytes=-3' },
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(206);
|
||||||
|
expect(res.headers['content-range']).toBe('bytes 3-5/6');
|
||||||
|
expect(res.body).toBe('lo!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns 416 for an unsatisfiable range', async () => {
|
||||||
|
const res = await ctx.app.inject({
|
||||||
|
method: 'GET',
|
||||||
|
url: `/f/${fileId}/raw`,
|
||||||
|
headers: { range: 'bytes=100-200' },
|
||||||
|
});
|
||||||
|
expect(res.statusCode).toBe(416);
|
||||||
|
expect(res.headers['content-range']).toBe('bytes */6');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns 404 for unknown file', async () => {
|
it('returns 404 for unknown file', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user