Initial commit
This commit is contained in:
72
server/api/drops/[id]/chunks/[index].get.ts
Normal file
72
server/api/drops/[id]/chunks/[index].get.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
createError,
|
||||
defineEventHandler,
|
||||
getRouterParam,
|
||||
sendStream,
|
||||
setHeaders,
|
||||
} from "h3";
|
||||
import { dropStore, DropStoreError } from "../../../../utils/drop-store";
|
||||
import { bearerToken, publicHeaders } from "../../../../utils/http";
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
publicHeaders(event);
|
||||
const token = bearerToken(event);
|
||||
if (!token)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "A valid lease is required.",
|
||||
});
|
||||
const id = String(getRouterParam(event, "id") || "").toUpperCase();
|
||||
let handle:
|
||||
| Awaited<ReturnType<typeof dropStore.readChunk>>["handle"]
|
||||
| undefined;
|
||||
let stream:
|
||||
| ReturnType<NonNullable<typeof handle>["createReadStream"]>
|
||||
| undefined;
|
||||
let closed = false;
|
||||
const close = async () => {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
stream?.destroy();
|
||||
await handle?.close().catch(() => undefined);
|
||||
};
|
||||
const abort = () => {
|
||||
if (!event.node.res.writableEnded && stream && !stream.destroyed)
|
||||
stream.destroy(new Error("Client disconnected."));
|
||||
void close();
|
||||
};
|
||||
try {
|
||||
const chunk = await dropStore.readChunk(
|
||||
id,
|
||||
Number(getRouterParam(event, "index")),
|
||||
token,
|
||||
);
|
||||
handle = chunk.handle;
|
||||
stream = handle.createReadStream();
|
||||
event.node.res.once("close", abort);
|
||||
event.node.res.once("finish", close);
|
||||
setHeaders(event, {
|
||||
"content-type": "application/octet-stream",
|
||||
"content-length": chunk.size,
|
||||
"cache-control": "private, no-store",
|
||||
});
|
||||
return await sendStream(event, stream);
|
||||
} catch (error) {
|
||||
await close();
|
||||
if (error instanceof DropStoreError)
|
||||
throw createError({
|
||||
statusCode:
|
||||
error.code === "expired"
|
||||
? 410
|
||||
: error.code === "not-found"
|
||||
? 404
|
||||
: 403,
|
||||
statusMessage: error.message,
|
||||
});
|
||||
throw error;
|
||||
} finally {
|
||||
event.node.res.off("close", abort);
|
||||
event.node.res.off("finish", close);
|
||||
await close();
|
||||
}
|
||||
});
|
||||
30
server/api/drops/[id]/chunks/[index].put.ts
Normal file
30
server/api/drops/[id]/chunks/[index].put.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
createError,
|
||||
defineEventHandler,
|
||||
getRouterParam,
|
||||
setResponseStatus,
|
||||
} from "h3";
|
||||
import { requireAuth } from "../../../../utils/auth";
|
||||
import { dropStore, DropStoreError } from "../../../../utils/drop-store";
|
||||
import { readBounded, requireContentType } from "../../../../utils/http";
|
||||
export default defineEventHandler(async (event) => {
|
||||
requireAuth(event);
|
||||
requireContentType(event, "application/octet-stream");
|
||||
const id = String(getRouterParam(event, "id") || "").toUpperCase();
|
||||
const index = Number(getRouterParam(event, "index"));
|
||||
try {
|
||||
const result = await dropStore.putChunk(
|
||||
id,
|
||||
index,
|
||||
await readBounded(event, 8388624),
|
||||
);
|
||||
setResponseStatus(event, result === "created" ? 201 : 204);
|
||||
} catch (error) {
|
||||
if (error instanceof DropStoreError) {
|
||||
const status =
|
||||
error.code === "not-found" ? 404 : error.code === "expired" ? 410 : 409;
|
||||
throw createError({ statusCode: status, statusMessage: error.message });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
32
server/api/drops/[id]/complete.post.ts
Normal file
32
server/api/drops/[id]/complete.post.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import {
|
||||
createError,
|
||||
defineEventHandler,
|
||||
getRouterParam,
|
||||
setResponseStatus,
|
||||
} from "h3";
|
||||
import { dropStore, DropStoreError } from "../../../utils/drop-store";
|
||||
import { bearerToken, publicHeaders, readBounded } from "../../../utils/http";
|
||||
export default defineEventHandler(async (event) => {
|
||||
publicHeaders(event);
|
||||
if ((await readBounded(event, 0)).byteLength)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Completion body must be empty.",
|
||||
});
|
||||
const token = bearerToken(event);
|
||||
if (!token)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "A valid lease is required.",
|
||||
});
|
||||
try {
|
||||
await dropStore.complete(
|
||||
String(getRouterParam(event, "id") || "").toUpperCase(),
|
||||
token,
|
||||
);
|
||||
} catch (error) {
|
||||
if (!(error instanceof DropStoreError))
|
||||
throw error; /* Completion is deliberately idempotent: an already completed, expired, or unknown lease has the desired state. */
|
||||
}
|
||||
setResponseStatus(event, 204);
|
||||
});
|
||||
37
server/api/drops/[id]/finalize.post.ts
Normal file
37
server/api/drops/[id]/finalize.post.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { createError, defineEventHandler, getRouterParam } from "h3";
|
||||
import { requireAuth } from "../../../utils/auth";
|
||||
import { dropStore, DropStoreError } from "../../../utils/drop-store";
|
||||
import { readBounded, requireContentType } from "../../../utils/http";
|
||||
import { validateManifestBodyLength } from "../../../../shared/utils/drop-contract";
|
||||
export default defineEventHandler(async (event) => {
|
||||
requireAuth(event);
|
||||
requireContentType(event, "application/octet-stream");
|
||||
const body = await readBounded(event, 65552);
|
||||
try {
|
||||
validateManifestBodyLength(body.byteLength);
|
||||
} catch {
|
||||
throw createError({ statusCode: 400, statusMessage: "Invalid manifest." });
|
||||
}
|
||||
const id = String(getRouterParam(event, "id") || "").toUpperCase();
|
||||
try {
|
||||
const result = await dropStore.finalize(id, body);
|
||||
return {
|
||||
id,
|
||||
expiresAt: new Date(result.expiresAt!).toISOString(),
|
||||
maxReads: result.maxReads,
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof DropStoreError) {
|
||||
const status =
|
||||
error.code === "not-found"
|
||||
? 404
|
||||
: error.code === "expired"
|
||||
? 410
|
||||
: error.code === "incomplete"
|
||||
? 409
|
||||
: 409;
|
||||
throw createError({ statusCode: status, statusMessage: error.message });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
39
server/api/drops/[id]/leases.post.ts
Normal file
39
server/api/drops/[id]/leases.post.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
createError,
|
||||
defineEventHandler,
|
||||
getRequestHeader,
|
||||
getRouterParam,
|
||||
setResponseStatus,
|
||||
} from "h3";
|
||||
import { dropStore, DropStoreError } from "../../../utils/drop-store";
|
||||
import { bearerToken, publicHeaders, readBounded } from "../../../utils/http";
|
||||
export default defineEventHandler(async (event) => {
|
||||
publicHeaders(event);
|
||||
if ((await readBounded(event, 0)).byteLength)
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: "Lease claim body must be empty.",
|
||||
});
|
||||
const id = String(getRouterParam(event, "id") || "").toUpperCase();
|
||||
const access = getRequestHeader(event, "x-quickdrop-access") || "";
|
||||
try {
|
||||
const result = await dropStore.claim(id, access, bearerToken(event));
|
||||
setResponseStatus(event, result.reused ? 200 : 201);
|
||||
return {
|
||||
token: result.token,
|
||||
leaseExpiresAt: new Date(result.leaseExpiresAt).toISOString(),
|
||||
expiresAt: new Date(result.expiresAt).toISOString(),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error instanceof DropStoreError) {
|
||||
const status =
|
||||
error.code === "exhausted" || error.code === "expired"
|
||||
? 410
|
||||
: error.code === "forbidden"
|
||||
? 403
|
||||
: 404;
|
||||
throw createError({ statusCode: status, statusMessage: error.message });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user