diff --git a/deno.json b/deno.json index 8f94438..81843f8 100644 --- a/deno.json +++ b/deno.json @@ -27,8 +27,6 @@ "@std/encoding": "jsr:@std/encoding@^1.0.10", "@std/fmt": "jsr:@std/fmt@^1.0.10", "@std/fs": "jsr:@std/fs@^1.0.23", - "@std/path": "jsr:@std/path@^1.1.4", - "@std/streams": "jsr:@std/streams@^1.1.0", "@std/ulid": "jsr:@std/ulid@^1.0.0", "@types/node": "npm:@types/node@^25.6.0", "arkenv": "npm:arkenv@~0.11.0", diff --git a/deno.lock b/deno.lock index 5862514..8abb723 100644 --- a/deno.lock +++ b/deno.lock @@ -9,7 +9,6 @@ "jsr:@standard-schema/spec@1": "1.1.0", "jsr:@std/assert@^1.0.19": "1.0.19", "jsr:@std/async@^1.3.0": "1.3.0", - "jsr:@std/bytes@^1.0.6": "1.0.6", "jsr:@std/cache@~0.2.3": "0.2.3", "jsr:@std/collections@^1.1.7": "1.1.7", "jsr:@std/data-structures@^1.0.11": "1.0.11", @@ -19,7 +18,6 @@ "jsr:@std/fs@^1.0.23": "1.0.23", "jsr:@std/internal@^1.0.12": "1.0.13", "jsr:@std/path@^1.1.4": "1.1.4", - "jsr:@std/streams@^1.1.0": "1.1.0", "jsr:@std/ulid@1": "1.0.0", "npm:@types/node@^25.6.0": "25.6.0", "npm:arkenv@0.11": "0.11.0_arktype@2.2.0", @@ -63,9 +61,6 @@ "jsr:@std/data-structures" ] }, - "@std/bytes@1.0.6": { - "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" - }, "@std/cache@0.2.3": { "integrity": "4e0bcab2e61f7c5637937bfe2bb13ccdd15e4dc3092beb14b78726bea8c49916" }, @@ -100,12 +95,6 @@ "jsr:@std/internal" ] }, - "@std/streams@1.1.0": { - "integrity": "2f7024d841f343fd478afe0c958a3f0f068ef2a0d2bcc954f550f97ac1fa22e3", - "dependencies": [ - "jsr:@std/bytes" - ] - }, "@std/ulid@1.0.0": { "integrity": "d41c3d27a907714413649fee864b7cde8d42ee68437d22b79d5de4f81d808780" } @@ -655,8 +644,6 @@ "jsr:@std/encoding@^1.0.10", "jsr:@std/fmt@^1.0.10", "jsr:@std/fs@^1.0.23", - "jsr:@std/path@^1.1.4", - "jsr:@std/streams@^1.1.0", "jsr:@std/ulid@1", "npm:@types/node@^25.6.0", "npm:arkenv@0.11", diff --git a/rolldown.deno.ts b/rolldown.deno.ts index 5f12b24..b3986aa 100644 --- a/rolldown.deno.ts +++ b/rolldown.deno.ts @@ -2,6 +2,8 @@ // vibecode resolver // based on: https://github.com/denoland/deno-rolldown-plugin +import { fileURLToPath } from "node:url"; + import { type Loader, type LoadResponse, @@ -11,7 +13,6 @@ import { Workspace, type WorkspaceOptions } from "@deno/loader"; -import { fromFileUrl } from "@std/path"; const MARegex = /.*/; @@ -193,7 +194,7 @@ export function deno(pluginOptions: DenoPluginOptions = {}): DenoPlugin { } if (specifier.startsWith("file:///")) { - specifier = fromFileUrl(specifier); + specifier = fileURLToPath(specifier); } modules.set(specifier, { diff --git a/src/endpoints/legacy/v2/documents/access.route.ts b/src/endpoints/legacy/v2/documents/access.route.ts deleted file mode 100644 index 57123f3..0000000 --- a/src/endpoints/legacy/v2/documents/access.route.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { toText } from "@std/streams"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { verifyHash } from "#util/crypto.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsRead } from "#util/fs.ts"; -import { validatorDocumentName, validatorDocumentPassword } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaHeader = type({ - "password?": validatorDocumentPassword -}); - -const schemaBodyResponse = await resolver( - type({ - key: type.string.configure({ - description: "The document name (formerly key)", - examples: ["abc123"] - }), - data: type.string.configure({ - description: "The document data", - examples: ["Hello, World!"] - }), - url: type.string.configure({ - deprecated: true, - description: "The document URL", - examples: ["https://jspaste.eu/abc123"] - }), - expirationTimestamp: type.number.configure({ - deprecated: true, - description: "The document expiration timestamp (always will be 0)", - examples: [0] - }) - }) -).toOpenAPISchema(); - -export default new Hono().get( - "/:name", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Get document", - responses: { - 200: { - content: { - "application/json": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - validator("header", schemaHeader, validatorHandler), - async (ctx) => { - // https://github.com/honojs/hono/issues/1130 - if (ctx.req.method === "HEAD") { - return ctx.body(null); - } - - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - // @ts-expect-error upstream - const header = ctx.req.valid("header") as typeof schemaHeader.infer; - - const document = mutableDatabase.document.get("name", param.name); - if (!document?.id) { - return errorThrow(ErrorCode.DocumentNotFound); - } - if (document.password) { - if (!header.password) { - return errorThrow(ErrorCode.DocumentPasswordNeeded); - } - - if (!verifyHash(header.password, document.password)) { - return errorThrow(ErrorCode.DocumentInvalidPassword); - } - } - - return ctx.json({ - key: param.name, - data: await toText(await fsRead(ctx, document, true)), - url: new URL(ctx.req.url).host.concat("/", param.name), - expirationTimestamp: 0 - }); - } -); diff --git a/src/endpoints/legacy/v2/documents/accessRaw.route.ts b/src/endpoints/legacy/v2/documents/accessRaw.route.ts deleted file mode 100644 index 5d16c13..0000000 --- a/src/endpoints/legacy/v2/documents/accessRaw.route.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { verifyHash } from "#util/crypto.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsRead } from "#util/fs.ts"; -import { validatorDocumentName, validatorDocumentPassword } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaHeader = type({ - "password?": validatorDocumentPassword -}); - -const schemaQuery = type({ - "p?": validatorDocumentPassword -}); - -const schemaBodyResponse = await resolver(type.unknown).toOpenAPISchema(); - -export default new Hono().get( - "/:name/raw", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Get document data", - responses: { - 200: { - content: { - "text/plain": { - schema: schemaBodyResponse.schema - }, - "application/octet-stream": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - validator("header", schemaHeader, validatorHandler), - validator("query", schemaQuery, validatorHandler), - async (ctx) => { - // https://github.com/honojs/hono/issues/1130 - if (ctx.req.method === "HEAD") { - return ctx.body(null); - } - - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - // @ts-expect-error upstream - const header = ctx.req.valid("header") as typeof schemaHeader.infer; - // @ts-expect-error upstream - const query = ctx.req.valid("query") as typeof schemaQuery.infer; - const options = { - password: header.password || query.p - }; - - const document = mutableDatabase.document.get("name", param.name); - if (!document?.id) { - return errorThrow(ErrorCode.DocumentNotFound); - } - if (document.password) { - if (!options.password) { - return errorThrow(ErrorCode.DocumentPasswordNeeded); - } - - if (!verifyHash(options.password, document.password)) { - return errorThrow(ErrorCode.DocumentInvalidPassword); - } - } - - ctx.res.headers.set("content-type", "text/plain"); - ctx.res.headers.set("transfer-encoding", "chunked"); - - return ctx.body(await fsRead(ctx, document, true)); - } -); diff --git a/src/endpoints/legacy/v2/documents/edit.route.ts b/src/endpoints/legacy/v2/documents/edit.route.ts deleted file mode 100644 index 93167f3..0000000 --- a/src/endpoints/legacy/v2/documents/edit.route.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { bodyStream } from "#http/middleware/bodyStream.ts"; -import { env } from "#util/env.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsWrite } from "#util/fs.ts"; -import { validatorDocumentName } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaBody = await resolver( - type.string.configure({ - description: "Data to replace in the document", - examples: ["Hello world!"] - }) -).toOpenAPISchema(); - -const schemaBodyResponse = await resolver( - type({ - edited: type.boolean.configure({ - description: "Confirmation of edition", - examples: [true] - }) - }) -).toOpenAPISchema(); - -export default new Hono().patch( - "/:name", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Edit document", - requestBody: { - content: { - "text/plain": { - schema: schemaBody.schema - } - } - }, - responses: { - 200: { - content: { - "application/json": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - bodyStream, - async (ctx) => { - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - - const document = mutableDatabase.document.get("name", param.name); - if (!document?.id || document.user_id) { - return errorThrow(ErrorCode.DocumentNotFound); - } - - mutableDatabase.document.update("name", param.name, "version", env.JSPB_DOCUMENT_COMPRESSION); - await fsWrite(ctx, document); - - return ctx.json({ - edited: true - }); - } -); diff --git a/src/endpoints/legacy/v2/documents/exists.route.ts b/src/endpoints/legacy/v2/documents/exists.route.ts deleted file mode 100644 index aa91185..0000000 --- a/src/endpoints/legacy/v2/documents/exists.route.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { genericErrorResponse } from "#util/error.ts"; -import { validatorDocumentName } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaBodyResponse = await resolver(type.boolean).toOpenAPISchema(); - -export default new Hono().get( - "/:name/exists", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Check document", - responses: { - 200: { - content: { - "text/plain": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - (ctx) => { - // https://github.com/honojs/hono/issues/1130 - if (ctx.req.method === "HEAD") { - return ctx.body(null); - } - - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - - return ctx.text(mutableDatabase.document.get("name", param.name)?.name ? "true" : "false"); - } -); diff --git a/src/endpoints/legacy/v2/documents/index.ts b/src/endpoints/legacy/v2/documents/index.ts deleted file mode 100644 index 25ec8cf..0000000 --- a/src/endpoints/legacy/v2/documents/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Hono } from "hono/tiny"; - -import type { Env } from "#http/handler.ts"; - -import access from "./access.route.ts"; -import accessRaw from "./accessRaw.route.ts"; -import edit from "./edit.route.ts"; -import exists from "./exists.route.ts"; -import publish from "./publish.route.ts"; -import remove from "./remove.route.ts"; - -export const v2LegacyDocumentHandler = new Hono(); - -v2LegacyDocumentHandler.route("/", access); -v2LegacyDocumentHandler.route("/", accessRaw); -v2LegacyDocumentHandler.route("/", edit); -v2LegacyDocumentHandler.route("/", exists); -v2LegacyDocumentHandler.route("/", publish); -v2LegacyDocumentHandler.route("/", remove); diff --git a/src/endpoints/legacy/v2/documents/publish.route.ts b/src/endpoints/legacy/v2/documents/publish.route.ts deleted file mode 100644 index 581386c..0000000 --- a/src/endpoints/legacy/v2/documents/publish.route.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { monotonicUlid } from "@std/ulid"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { - constantDocumentNameLengthMax, - constantDocumentNameLengthMin, - constantHttpStatusCodes, - mutableDatabase -} from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { bodyStream } from "#http/middleware/bodyStream.ts"; -import { generateHash } from "#util/crypto.ts"; -import { generateName } from "#util/document.ts"; -import { env } from "#util/env.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsWrite } from "#util/fs.ts"; -import { validatorDocumentName, validatorDocumentPassword } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaBody = await resolver( - type.string.configure({ - description: "Data to replace in the document", - examples: ["Hello world!"] - }) -).toOpenAPISchema(); - -const schemaHeader = type({ - "password?": validatorDocumentPassword, - "key?": validatorDocumentName, - "keylength?": type.number.atLeast(constantDocumentNameLengthMin).atMost(constantDocumentNameLengthMax).configure({ - description: "The document name length" - }) -}); - -const schemaBodyResponse = await resolver( - type({ - key: type.string.configure({ - description: "The document name (formerly key)", - examples: ["abc123"] - }) - }) -).toOpenAPISchema(); - -export default new Hono().post( - "/", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Publish document", - requestBody: { - content: { - "text/plain": { - schema: schemaBody.schema - } - } - }, - responses: { - 200: { - content: { - "application/json": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("header", schemaHeader, validatorHandler), - bodyStream, - async (ctx) => { - const { - password, - key: name, - keylength: nameLength - // @ts-expect-error upstream - } = ctx.req.valid("header") as typeof schemaHeader.infer; - - let setName: string; - if (name) { - if (mutableDatabase.document.get("name", name)?.name) { - return errorThrow(ErrorCode.DocumentNameAlreadyExists); - } - - setName = name; - } else { - setName = generateName(nameLength); - } - - const id = monotonicUlid(); - - let hashCombo: string | null; - if (password) { - hashCombo = generateHash(password).combo; - } else { - hashCombo = null; - } - - mutableDatabase.document.create({ - id: id, - user_id: null, - version: env.JSPB_DOCUMENT_COMPRESSION, - name: setName, - password: hashCombo - }); - await fsWrite(ctx, { id: id }); - - return ctx.json({ - key: setName, - secret: "", - url: new URL(ctx.req.url).host.concat("/", setName), - expirationTimestamp: 0 - }); - } -); diff --git a/src/endpoints/legacy/v2/documents/remove.route.ts b/src/endpoints/legacy/v2/documents/remove.route.ts deleted file mode 100644 index d45f0a7..0000000 --- a/src/endpoints/legacy/v2/documents/remove.route.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { describeRoute, resolver, validator } from "@hono/openapi"; -import { type } from "arktype"; -import { Hono } from "hono/tiny"; - -import { constantHttpStatusCodes, mutableDatabase } from "#/global.ts"; -import type { Env } from "#http/handler.ts"; -import { ErrorCode, errorThrow, genericErrorResponse } from "#util/error.ts"; -import { fsDelete } from "#util/fs.ts"; -import { validatorDocumentName } from "#util/validator/document.ts"; -import { validatorHandler } from "#util/validator/handler.ts"; - -const schemaParam = type({ - name: validatorDocumentName -}); - -const schemaBodyResponse = await resolver( - type({ - removed: type.true.configure({ - description: "Confirmation of deletion", - examples: [true] - }) - }) -).toOpenAPISchema(); - -export default new Hono().delete( - "/:name", - describeRoute({ - deprecated: true, - tags: ["DOCUMENT (legacy)"], - summary: "Remove document", - responses: { - 200: { - content: { - "application/json": { - schema: schemaBodyResponse.schema - } - }, - description: constantHttpStatusCodes[200] - }, - 400: { ...genericErrorResponse, description: constantHttpStatusCodes[400] }, - 404: { ...genericErrorResponse, description: constantHttpStatusCodes[404] } - } - }), - validator("param", schemaParam, validatorHandler), - (ctx) => { - // @ts-expect-error upstream - const param = ctx.req.valid("param") as typeof schemaParam.infer; - - const document = mutableDatabase.document.get("name", param.name); - if (!document?.id || document.user_id) { - return errorThrow(ErrorCode.DocumentNotFound); - } - - mutableDatabase.document.delete("name", param.name); - void fsDelete(document); - - return ctx.json({ removed: true }); - } -); diff --git a/src/http/handler.ts b/src/http/handler.ts index 919ea92..ee9d10c 100644 --- a/src/http/handler.ts +++ b/src/http/handler.ts @@ -4,7 +4,6 @@ import { HTTPException } from "hono/http-exception"; import { Hono } from "hono/tiny"; import { v1DocumentHandler } from "#endpoint/document/v1/index.ts"; -import { v2LegacyDocumentHandler } from "#endpoint/legacy/v2/documents/index.ts"; import { v1UserHandler } from "#endpoint/user/v1/index.ts"; import { Logger } from "#util/console.ts"; import { env } from "#util/env.ts"; @@ -117,16 +116,8 @@ Each instance can impose restrictions to the API usage. These restrictions may i }) ); - // deprecated - handler.get("/documents/*", (ctx) => { - return ctx.redirect(ctx.req.path.replace(/\/documents\//g, "/v2/documents/"), 307); - }); - handler.route("/document/v1", v1DocumentHandler); handler.route("/user/v1", v1UserHandler); - // deprecated - handler.route("/v2/documents", v2LegacyDocumentHandler); - return handler; }; diff --git a/src/utils/fs.ts b/src/utils/fs.ts index 75f5b0d..47d80ae 100644 --- a/src/utils/fs.ts +++ b/src/utils/fs.ts @@ -60,17 +60,14 @@ export const fsDelete = async ({ id }: Pick): Promise => { export const fsRead = async ( ctx: Context, - { id, version }: Pick, - clientIgnoreCapabilities = false + { id, version }: Pick ): Promise> => { const handle = await Deno.open(constantPathStructStorageData + id); - const hasClientDeflate = clientIgnoreCapabilities ? false : ctx.req.header("accept-encoding")?.includes("deflate"); - let stream: ReadableStream; switch (version) { case documentVersionV1: { - if (hasClientDeflate) { + if (ctx.req.header("accept-encoding")?.includes("deflate")) { ctx.res.headers.set("content-encoding", "deflate"); stream = handle.readable; } else {