mirror of
https://github.com/lifegpc/eh-downloader.git
synced 2026-06-06 05:38:44 +08:00
feat: Add Swagger UI integration and API documentation support
This commit is contained in:
@@ -117,6 +117,7 @@ COPY ./static/*.css ./static/
|
|||||||
COPY ./static/*.ts ./static/
|
COPY ./static/*.ts ./static/
|
||||||
COPY ./static/*.ico ./static/
|
COPY ./static/*.ico ./static/
|
||||||
COPY ./static/*.svg ./static/
|
COPY ./static/*.svg ./static/
|
||||||
|
COPY ./static/*.js ./static/
|
||||||
COPY ./tasks ./tasks
|
COPY ./tasks ./tasks
|
||||||
COPY ./thumbnail ./thumbnail
|
COPY ./thumbnail ./thumbnail
|
||||||
COPY ./translation ./translation
|
COPY ./translation ./translation
|
||||||
@@ -126,6 +127,7 @@ COPY ./deno.json ./
|
|||||||
COPY ./import_map.json ./
|
COPY ./import_map.json ./
|
||||||
COPY ./LICENSE ./
|
COPY ./LICENSE ./
|
||||||
COPY ./docker_entrypoint.sh ./
|
COPY ./docker_entrypoint.sh ./
|
||||||
|
COPY ./api.yml ./
|
||||||
|
|
||||||
ENV LD_LIBRARY_PATH=/app/lib
|
ENV LD_LIBRARY_PATH=/app/lib
|
||||||
ENV PATH=/app/bin:$PATH
|
ENV PATH=/app/bin:$PATH
|
||||||
@@ -136,7 +138,8 @@ ENV DENO_SQLITE_PATH=/app/lib/libsqlite3.so
|
|||||||
ENV DENO_LIBZIP_PATH=/app/lib/libzip.so
|
ENV DENO_LIBZIP_PATH=/app/lib/libzip.so
|
||||||
ENV LC_ALL=C.utf8
|
ENV LC_ALL=C.utf8
|
||||||
|
|
||||||
RUN deno task server-build && deno task prebuild && \
|
RUN deno task download_swagger && \
|
||||||
|
deno task server-build && deno task prebuild && \
|
||||||
deno task cache && rm -rf ~/.cache && \
|
deno task cache && rm -rf ~/.cache && \
|
||||||
mkdir -p ./thumbnails && chmod 777 ./thumbnails && \
|
mkdir -p ./thumbnails && chmod 777 ./thumbnails && \
|
||||||
mkdir -p ./downloads && chmod 777 ./downloads && \
|
mkdir -p ./downloads && chmod 777 ./downloads && \
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"gen_meili_server_key": "deno run --allow-net scripts/gen_meili_server_key.ts",
|
"gen_meili_server_key": "deno run --allow-net scripts/gen_meili_server_key.ts",
|
||||||
"server-build": "deno run -A server-dev.ts build",
|
"server-build": "deno run -A server-dev.ts build",
|
||||||
"prebuild": "deno run -A scripts/prebuild.ts",
|
"prebuild": "deno run -A scripts/prebuild.ts",
|
||||||
"download_ffi": "deno run --allow-read=./ --allow-write=./lib --allow-net scripts/download_ffi.ts"
|
"download_ffi": "deno run --allow-read=./ --allow-write=./lib --allow-net scripts/download_ffi.ts",
|
||||||
|
"download_swagger": "deno run --allow-read=./ --allow-net --allow-write=./static/swagger scripts/download_swagger.ts"
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"indentWidth": 4,
|
"indentWidth": 4,
|
||||||
@@ -25,7 +26,8 @@
|
|||||||
"api.yml",
|
"api.yml",
|
||||||
"docker-compose.yml",
|
"docker-compose.yml",
|
||||||
".github/workflows/deno.yml",
|
".github/workflows/deno.yml",
|
||||||
".github/workflows/docker.yaml"
|
".github/workflows/docker.yaml",
|
||||||
|
"static/swagger-initializer.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
@@ -37,7 +39,7 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"tags": ["fresh", "recommended"]
|
"tags": ["fresh", "recommended"]
|
||||||
},
|
},
|
||||||
"exclude": ["_fresh", "static/sw.js"]
|
"exclude": ["_fresh", "static/sw.js", "static/swagger-initializer.js"]
|
||||||
},
|
},
|
||||||
"unstable": ["ffi"]
|
"unstable": ["ffi"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
"@std/jsonc": "jsr:/@std/[email protected]",
|
"@std/jsonc": "jsr:/@std/[email protected]",
|
||||||
"@std/path": "jsr:/@std/[email protected]",
|
"@std/path": "jsr:/@std/[email protected]",
|
||||||
"@std/semver": "jsr:/@std/[email protected]",
|
"@std/semver": "jsr:/@std/[email protected]",
|
||||||
|
"@std/yaml/": "jsr:/@std/[email protected]/",
|
||||||
"deno_dom/": "jsr:/@b-fuze/[email protected]/",
|
"deno_dom/": "jsr:/@b-fuze/[email protected]/",
|
||||||
"sqlite/": "https://deno.land/x/[email protected]/",
|
"sqlite/": "https://deno.land/x/[email protected]/",
|
||||||
"zipjs/": "https://deno.land/x/[email protected]/",
|
"zipjs/": "https://deno.land/x/[email protected]/",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { FreshContext } from "$fresh/server.ts";
|
import { FreshContext } from "$fresh/server.ts";
|
||||||
import { join } from "@std/path";
|
import { basename, join } from "@std/path";
|
||||||
import {
|
import {
|
||||||
get_file_response,
|
get_file_response,
|
||||||
GetFileResponseOptions,
|
GetFileResponseOptions,
|
||||||
@@ -13,9 +13,12 @@ import { initDOMParser } from "../utils.ts";
|
|||||||
import { DOMParser } from "deno_dom/wasm-noinit";
|
import { DOMParser } from "deno_dom/wasm-noinit";
|
||||||
import { get_host, return_error } from "../server/utils.ts";
|
import { get_host, return_error } from "../server/utils.ts";
|
||||||
import { base_logger } from "../utils/logger.ts";
|
import { base_logger } from "../utils/logger.ts";
|
||||||
|
import { parse as parseYaml } from "@std/yaml/parse";
|
||||||
|
|
||||||
const STATIC_FILES = ["/common.css", "/scrollBar.css", "/sw.js", "/sw.js.map"];
|
const STATIC_FILES = ["/common.css", "/scrollBar.css", "/sw.js", "/sw.js.map"];
|
||||||
|
|
||||||
|
const logger = base_logger.get_logger("middleware");
|
||||||
|
|
||||||
async function default_handler(req: Request, ctx: FreshContext) {
|
async function default_handler(req: Request, ctx: FreshContext) {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
const m = get_task_manager();
|
const m = get_task_manager();
|
||||||
@@ -171,6 +174,80 @@ async function default_handler(req: Request, ctx: FreshContext) {
|
|||||||
opts.if_unmodified_since = req.headers.get("If-Unmodified-Since");
|
opts.if_unmodified_since = req.headers.get("If-Unmodified-Since");
|
||||||
return await get_file_response(p, opts);
|
return await get_file_response(p, opts);
|
||||||
}
|
}
|
||||||
|
if (url.pathname == "/swagger" || url.pathname.startsWith("/swagger/")) {
|
||||||
|
let swagger_base = import.meta.resolve("../static/swagger").slice(7);
|
||||||
|
if (Deno.build.os === "windows") {
|
||||||
|
swagger_base = swagger_base.slice(1);
|
||||||
|
}
|
||||||
|
const u = new URL(req.url);
|
||||||
|
let p = join(swagger_base, u.pathname.slice(9));
|
||||||
|
if (basename(p) == "swagger-initializer.js") {
|
||||||
|
p = join(swagger_base, "../swagger-initializer.js");
|
||||||
|
}
|
||||||
|
if (!(await exists(p)) || p === swagger_base) {
|
||||||
|
p = join(swagger_base, "/index.html");
|
||||||
|
}
|
||||||
|
if (basename(p) == "index.html") {
|
||||||
|
const html = await Deno.readTextFile(p);
|
||||||
|
await initDOMParser();
|
||||||
|
try {
|
||||||
|
const dom = (new DOMParser()).parseFromString(
|
||||||
|
html,
|
||||||
|
"text/html",
|
||||||
|
);
|
||||||
|
const doc = dom.documentElement!;
|
||||||
|
const head = doc.querySelector("head");
|
||||||
|
if (!head) {
|
||||||
|
throw new Error("head not found");
|
||||||
|
}
|
||||||
|
const base = dom.createElement("base");
|
||||||
|
base.setAttribute("href", "/swagger/");
|
||||||
|
head?.append(base);
|
||||||
|
const css_links = doc.querySelectorAll("link[rel=stylesheet]");
|
||||||
|
for (const link of css_links) {
|
||||||
|
const href = link.getAttribute("href");
|
||||||
|
if (href) {
|
||||||
|
if (href.startsWith("/")) continue;
|
||||||
|
link.setAttribute("href", `/swagger/${href}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Response(
|
||||||
|
"<!DOCTYPE html>\n" + doc.outerHTML,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/html; charset=UTF-8",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn("Failed to handle swagger index.html:", e);
|
||||||
|
if (u.pathname == "/swagger") {
|
||||||
|
return Response.redirect(
|
||||||
|
`${get_host(req)}/swagger/index.html`,
|
||||||
|
302,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const opts: GetFileResponseOptions = {};
|
||||||
|
opts.range = req.headers.get("range");
|
||||||
|
opts.if_modified_since = req.headers.get("If-Modified-Since");
|
||||||
|
opts.if_unmodified_since = req.headers.get("If-Unmodified-Since");
|
||||||
|
return await get_file_response(p, opts);
|
||||||
|
}
|
||||||
|
if (url.pathname == "/api.json") {
|
||||||
|
let filepath = import.meta.resolve("../api.yml").slice(7);
|
||||||
|
if (Deno.build.os === "windows") {
|
||||||
|
filepath = filepath.slice(1);
|
||||||
|
}
|
||||||
|
const data = <Record<string, unknown>> parseYaml(
|
||||||
|
await Deno.readTextFile(filepath),
|
||||||
|
);
|
||||||
|
data["servers"] = [{ url: "/api", description: "API Server" }];
|
||||||
|
return new Response(JSON.stringify(data), {
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
const res = await ctx.next();
|
const res = await ctx.next();
|
||||||
if (enable_server_timing) {
|
if (enable_server_timing) {
|
||||||
if (res.status === 101) return res;
|
if (res.status === 101) return res;
|
||||||
|
|||||||
64
scripts/download_swagger.ts
Normal file
64
scripts/download_swagger.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import { join } from "@std/path";
|
||||||
|
import { configure, HttpReader, ZipReader } from "zipjs/index.js";
|
||||||
|
import { sure_dir } from "../utils.ts";
|
||||||
|
|
||||||
|
async function get_latest_version() {
|
||||||
|
const re = await fetch(
|
||||||
|
"https://api.github.com/repos/swagger-api/swagger-ui/releases/latest",
|
||||||
|
);
|
||||||
|
if (!re.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch latest version: ${re.status} ${re.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const json = await re.json();
|
||||||
|
return json.tag_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function get_download_url() {
|
||||||
|
const version = await get_latest_version();
|
||||||
|
return `https://github.com/swagger-api/swagger-ui/archive/refs/tags/${version}.zip`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DIST = /swagger-ui-[1-9\.]+\/dist/;
|
||||||
|
|
||||||
|
async function unzip(url: string) {
|
||||||
|
const zip_reader = new ZipReader(new HttpReader(url));
|
||||||
|
const entries = await zip_reader.getEntries();
|
||||||
|
let swagger_base = import.meta.resolve("../static/swagger").slice(7);
|
||||||
|
if (Deno.build.os === "windows") {
|
||||||
|
swagger_base = swagger_base.slice(1);
|
||||||
|
}
|
||||||
|
await sure_dir(swagger_base);
|
||||||
|
for (const entry of entries) {
|
||||||
|
const m = entry.filename.match(DIST);
|
||||||
|
if (!m) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const filename = entry.filename.replace(DIST, "");
|
||||||
|
if (filename.endsWith("/")) {
|
||||||
|
const path = join(swagger_base, filename);
|
||||||
|
await sure_dir(path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!entry.getData) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const path = join(swagger_base, filename);
|
||||||
|
console.log("Extracting", entry.filename, "to", path);
|
||||||
|
const file = await Deno.open(path, { write: true, create: true });
|
||||||
|
try {
|
||||||
|
await entry.getData(file.writable);
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
file.close();
|
||||||
|
} catch (_) {
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
configure({ useWebWorkers: false });
|
||||||
|
const download_url = await get_download_url();
|
||||||
|
await unzip(download_url);
|
||||||
1
static/.gitignore
vendored
1
static/.gitignore
vendored
@@ -3,3 +3,4 @@ flutter/
|
|||||||
sw.js
|
sw.js
|
||||||
sw.js.map
|
sw.js.map
|
||||||
sw.meta.json
|
sw.meta.json
|
||||||
|
swagger/
|
||||||
|
|||||||
21
static/swagger-initializer.js
Normal file
21
static/swagger-initializer.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
window.onload = function() {
|
||||||
|
//<editor-fold desc="Changeable Configuration Block">
|
||||||
|
|
||||||
|
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
|
||||||
|
window.ui = SwaggerUIBundle({
|
||||||
|
url: "/api.json",
|
||||||
|
dom_id: '#swagger-ui',
|
||||||
|
deepLinking: true,
|
||||||
|
presets: [
|
||||||
|
SwaggerUIBundle.presets.apis,
|
||||||
|
SwaggerUIStandalonePreset
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
SwaggerUIBundle.plugins.DownloadUrl
|
||||||
|
],
|
||||||
|
layout: "StandaloneLayout",
|
||||||
|
validatorUrl: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
//</editor-fold>
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user