@riffyh/server 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +47 -4
- package/dist/index.mjs +15 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Elysia } from "elysia";
|
|
2
|
-
import * as _$_riffyh_commons0 from "@riffyh/commons";
|
|
3
2
|
|
|
4
3
|
//#region src/index.d.ts
|
|
5
4
|
declare const server: Elysia<"", {
|
|
@@ -110,14 +109,15 @@ declare const server: Elysia<"", {
|
|
|
110
109
|
name: string;
|
|
111
110
|
id: string;
|
|
112
111
|
slug: string;
|
|
113
|
-
type:
|
|
112
|
+
type: import("@riffyh/commons").TagType;
|
|
114
113
|
}[];
|
|
115
114
|
key: string;
|
|
115
|
+
id: string;
|
|
116
116
|
title: {
|
|
117
117
|
display: string;
|
|
118
118
|
original: string | null;
|
|
119
119
|
};
|
|
120
|
-
|
|
120
|
+
language: "en" | "jp" | "cn" | "kr" | "th" | null;
|
|
121
121
|
cover: {
|
|
122
122
|
src: string;
|
|
123
123
|
width: number;
|
|
@@ -157,11 +157,54 @@ declare const server: Elysia<"", {
|
|
|
157
157
|
200: {
|
|
158
158
|
galleries: {
|
|
159
159
|
key: string;
|
|
160
|
+
id: string;
|
|
160
161
|
title: {
|
|
161
162
|
display: string;
|
|
162
163
|
original: string | null;
|
|
163
164
|
};
|
|
165
|
+
language: "en" | "jp" | "cn" | "kr" | "th" | null;
|
|
166
|
+
cover: {
|
|
167
|
+
src: string;
|
|
168
|
+
width: number;
|
|
169
|
+
height: number;
|
|
170
|
+
};
|
|
171
|
+
}[];
|
|
172
|
+
currentPage: number;
|
|
173
|
+
maximumPages: number;
|
|
174
|
+
};
|
|
175
|
+
422: {
|
|
176
|
+
type: "validation";
|
|
177
|
+
on: string;
|
|
178
|
+
summary?: string;
|
|
179
|
+
message?: string;
|
|
180
|
+
found?: unknown;
|
|
181
|
+
property?: string;
|
|
182
|
+
expected?: string;
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
};
|
|
187
|
+
} & {
|
|
188
|
+
tag: {
|
|
189
|
+
get: {
|
|
190
|
+
body: unknown;
|
|
191
|
+
params: {};
|
|
192
|
+
query: {
|
|
193
|
+
page: number;
|
|
194
|
+
id: string;
|
|
195
|
+
dataSource: never;
|
|
196
|
+
};
|
|
197
|
+
headers: unknown;
|
|
198
|
+
response: {
|
|
199
|
+
200: {
|
|
200
|
+
galleries: {
|
|
201
|
+
key: string;
|
|
164
202
|
id: string;
|
|
203
|
+
title: {
|
|
204
|
+
display: string;
|
|
205
|
+
original: string | null;
|
|
206
|
+
};
|
|
207
|
+
language: "en" | "jp" | "cn" | "kr" | "th" | null;
|
|
165
208
|
cover: {
|
|
166
209
|
src: string;
|
|
167
210
|
width: number;
|
|
@@ -240,7 +283,7 @@ declare const server: Elysia<"", {
|
|
|
240
283
|
type: "page" | "cover";
|
|
241
284
|
url: string;
|
|
242
285
|
dataSource: never;
|
|
243
|
-
format: "
|
|
286
|
+
format: "webp" | "jpeg";
|
|
244
287
|
};
|
|
245
288
|
headers: unknown;
|
|
246
289
|
response: {
|
package/dist/index.mjs
CHANGED
|
@@ -92,6 +92,20 @@ const server = new Elysia().use(swagger({ exclude: ["/_image"] })).use(toon()).u
|
|
|
92
92
|
dataSource: dataSourceKeys
|
|
93
93
|
}),
|
|
94
94
|
response: listingResultModel
|
|
95
|
+
}).get("/tag", async ({ query }) => {
|
|
96
|
+
const dataSource = config.dataSources.find((o) => o.key === query.dataSource);
|
|
97
|
+
if (dataSource === void 0) throw new Error(`data source ${query.dataSource} not found`);
|
|
98
|
+
return dataSource.getTagListing({
|
|
99
|
+
id: query.id,
|
|
100
|
+
page: query.page
|
|
101
|
+
});
|
|
102
|
+
}, {
|
|
103
|
+
query: t.Object({
|
|
104
|
+
id: t.String(),
|
|
105
|
+
page: t.Number(),
|
|
106
|
+
dataSource: dataSourceKeys
|
|
107
|
+
}),
|
|
108
|
+
response: listingResultModel
|
|
95
109
|
}).post("/collection/export", ({ body }) => upload(body, config.secretboxKey), { body: t.String() }).get("/collection/import", async ({ query }) => download(query.key, config.secretboxKey), { query: t.Object({ key: t.String() }) }).get("/image", async ({ query }) => {
|
|
96
110
|
const cacheKeys = [
|
|
97
111
|
query.dataSource,
|
|
@@ -116,11 +130,7 @@ const server = new Elysia().use(swagger({ exclude: ["/_image"] })).use(toon()).u
|
|
|
116
130
|
} });
|
|
117
131
|
}, { query: t.Object({
|
|
118
132
|
url: t.String(),
|
|
119
|
-
format: t.Union([
|
|
120
|
-
t.Literal("avif"),
|
|
121
|
-
t.Literal("webp"),
|
|
122
|
-
t.Literal("jpg")
|
|
123
|
-
]),
|
|
133
|
+
format: t.Union([t.Literal("webp"), t.Literal("jpeg")]),
|
|
124
134
|
type: t.Union([t.Literal("cover"), t.Literal("page")]),
|
|
125
135
|
dataSource: dataSourceKeys
|
|
126
136
|
}) }).listen({
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/bytebin.ts","../src/index.ts"],"sourcesContent":["import { secretbox, randomBytes } from \"tweetnacl\";\nimport { decodeUTF8, encodeUTF8, encodeBase64, decodeBase64 } from \"tweetnacl-util\";\n\nexport const upload = async (content: string, key: string): Promise<string> => {\n const nonce = randomBytes(secretbox.nonceLength);\n const keyUint8Array = decodeBase64(key);\n const messageUint8 = decodeUTF8(content);\n const box = secretbox(messageUint8, nonce, keyUint8Array);\n\n const fullMessage = new Uint8Array(nonce.length + box.length);\n fullMessage.set(nonce);\n fullMessage.set(box, nonce.length);\n\n const base64FullMessage = encodeBase64(fullMessage);\n const response = await fetch(\"https://api.pastes.dev/post\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"text/plain\",\n \"User-Agent\": \"RiffyH (github.com/rayriffy/rayriffy-h)\",\n },\n body: base64FullMessage,\n });\n\n if (!response.ok) throw new Error(\"failed to upload content\");\n\n const data = response.json() as Promise<{ key: string }>;\n return (await data).key;\n};\n\nexport const download = async (code: string, key: string): Promise<string> => {\n const response = await fetch(`https://api.pastes.dev/${code}`);\n\n if (!response.ok) throw new Error(`failed to download content with code ${code}`);\n\n const encryptedContent = await response.text();\n\n const keyUint8Array = decodeBase64(key);\n const messageWithNonceAsUint8Array = decodeBase64(encryptedContent);\n const nonce = messageWithNonceAsUint8Array.slice(0, secretbox.nonceLength);\n const message = messageWithNonceAsUint8Array.slice(\n secretbox.nonceLength,\n encryptedContent.length,\n );\n\n const decrypted = secretbox.open(message, nonce, keyUint8Array);\n\n if (!decrypted) throw new Error(\"could not decrypt message\");\n\n return encodeUTF8(decrypted);\n};\n","#!/usr/bin/env bun\n\nimport { t, Elysia } from \"elysia\";\nimport { swagger } from \"@elysiajs/swagger\";\nimport { cors } from \"@elysiajs/cors\";\nimport { toon } from \"@toon-tools/elysia\";\n\nimport { defineCacheInstance } from \"@rayriffy/filesystem\";\nimport sharp from \"sharp\";\nimport { galleryModel, listingResultModel, type Config } from \"@riffyh/commons\";\nimport debug from \"debug\";\nimport path from \"node:path\";\nimport { download, upload } from \"./bytebin\";\nimport { secretbox, randomBytes } from \"tweetnacl\";\nimport { encodeBase64, decodeBase64 } from \"tweetnacl-util\";\n\nconst log = debug(\"riffyh:server\");\nconst cache = defineCacheInstance();\n\nlog(\"warming up server...\");\n\nconst configFile = process.env.RIFFYH_CONFIG_PATH || \"./riffyh.config.ts\";\nconst configPath = path.resolve(configFile);\nlog(`resolving config file at ${configPath}...`);\nconst config: Config = await import(configPath).then((o) => o.default);\n\ntry {\n if (\n typeof config.secretboxKey !== \"string\" ||\n decodeBase64(config.secretboxKey).length !== secretbox.keyLength\n )\n throw new Error(\"key length mismatch\");\n // oxlint-disable-next-line no-unused-vars\n} catch (_) {\n const generatedKey = encodeBase64(randomBytes(secretbox.keyLength));\n console.error(\n `unable to parse secret key. please either generate key via https://tweetnacl.js.org/#/secretbox or copy following key to configuration: ${generatedKey}`,\n );\n process.exit(1);\n}\n\nlog(`loaded configuration with ${config.dataSources.length} data sources`);\n\nconst dataSourceKeys = t.Union(config.dataSources.map((o) => t.Literal(o.key)));\n\nconst server = new Elysia()\n .use(\n swagger({\n exclude: [\"/_image\"],\n }),\n )\n .use(toon())\n .use(cors())\n .get(\"/\", ({ redirect }) => redirect(\"/swagger\"))\n .get(\"/health\", () => \"healthy\")\n .get(\n \"/dataSources\",\n () =>\n config.dataSources.map((o) => ({\n key: o.key,\n name: o.name,\n iconUrl: o.iconUrl,\n })),\n {\n response: t.Array(\n t.Object({\n key: t.String(),\n name: t.String(),\n iconUrl: t.String(),\n }),\n ),\n },\n )\n .get(\n \"/gallery\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getGallery({\n id: query.id,\n });\n },\n {\n query: t.Object({\n id: t.String(),\n dataSource: dataSourceKeys,\n }),\n response: galleryModel,\n },\n )\n .get(\n \"/listing\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getListing({\n searchQuery: query.query || null,\n page: query.page,\n });\n },\n {\n query: t.Object({\n query: t.Optional(t.String()),\n page: t.Number(),\n dataSource: dataSourceKeys,\n }),\n response: listingResultModel,\n },\n )\n .post(\"/collection/export\", ({ body }) => upload(body, config.secretboxKey), {\n body: t.String(),\n })\n .get(\"/collection/import\", async ({ query }) => download(query.key, config.secretboxKey), {\n query: t.Object({\n key: t.String(),\n }),\n })\n .get(\n \"/image\",\n async ({ query }) => {\n const cacheKeys = [query.dataSource, query.url, query.format, query.type];\n\n const cachedImage = await cache.read<Buffer>(cacheKeys);\n if (cachedImage !== null)\n return new Response(Buffer.from(cachedImage.data), {\n headers: {\n \"Content-Type\": `image/${query.format}`,\n \"Cache-Control\": \"public, max-age=86400000\",\n },\n });\n\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n const fetchedImage = await dataSource.getImage({\n url: query.url,\n });\n const resizedImage = await sharp(fetchedImage)\n .resize({\n width: query.type === \"cover\" ? 640 : 1280,\n })\n .toFormat(query.format, {\n quality: 72,\n })\n .toBuffer();\n await cache.write(\n cacheKeys,\n resizedImage,\n 86_400_000, // 1 month\n );\n\n return new Response(resizedImage, {\n headers: {\n \"Content-Type\": `image/${query.format}`,\n \"Cache-Control\": \"public, max-age=86400\",\n \"CDN-Cache-Control\": \"public, max-age=2592000\",\n \"Cloudflare-CDN-Cache-Control\": \"public, max-age=2592000\",\n },\n });\n },\n {\n query: t.Object({\n url: t.String(),\n format: t.Union([t.Literal(\"avif\"), t.Literal(\"webp\"), t.Literal(\"jpg\")]),\n type: t.Union([t.Literal(\"cover\"), t.Literal(\"page\")]),\n dataSource: dataSourceKeys,\n }),\n },\n )\n .listen({\n hostname: config.hostname ?? \"0.0.0.0\",\n port: config.port ?? 3000,\n });\n\nexport type Server = typeof server;\n\nconsole.log(`🦊 Elysia is running at http://${server.server?.hostname}:${server.server?.port}`);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/bytebin.ts","../src/index.ts"],"sourcesContent":["import { secretbox, randomBytes } from \"tweetnacl\";\nimport { decodeUTF8, encodeUTF8, encodeBase64, decodeBase64 } from \"tweetnacl-util\";\n\nexport const upload = async (content: string, key: string): Promise<string> => {\n const nonce = randomBytes(secretbox.nonceLength);\n const keyUint8Array = decodeBase64(key);\n const messageUint8 = decodeUTF8(content);\n const box = secretbox(messageUint8, nonce, keyUint8Array);\n\n const fullMessage = new Uint8Array(nonce.length + box.length);\n fullMessage.set(nonce);\n fullMessage.set(box, nonce.length);\n\n const base64FullMessage = encodeBase64(fullMessage);\n const response = await fetch(\"https://api.pastes.dev/post\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"text/plain\",\n \"User-Agent\": \"RiffyH (github.com/rayriffy/rayriffy-h)\",\n },\n body: base64FullMessage,\n });\n\n if (!response.ok) throw new Error(\"failed to upload content\");\n\n const data = response.json() as Promise<{ key: string }>;\n return (await data).key;\n};\n\nexport const download = async (code: string, key: string): Promise<string> => {\n const response = await fetch(`https://api.pastes.dev/${code}`);\n\n if (!response.ok) throw new Error(`failed to download content with code ${code}`);\n\n const encryptedContent = await response.text();\n\n const keyUint8Array = decodeBase64(key);\n const messageWithNonceAsUint8Array = decodeBase64(encryptedContent);\n const nonce = messageWithNonceAsUint8Array.slice(0, secretbox.nonceLength);\n const message = messageWithNonceAsUint8Array.slice(\n secretbox.nonceLength,\n encryptedContent.length,\n );\n\n const decrypted = secretbox.open(message, nonce, keyUint8Array);\n\n if (!decrypted) throw new Error(\"could not decrypt message\");\n\n return encodeUTF8(decrypted);\n};\n","#!/usr/bin/env bun\n\nimport { t, Elysia } from \"elysia\";\nimport { swagger } from \"@elysiajs/swagger\";\nimport { cors } from \"@elysiajs/cors\";\nimport { toon } from \"@toon-tools/elysia\";\n\nimport { defineCacheInstance } from \"@rayriffy/filesystem\";\nimport sharp from \"sharp\";\nimport { galleryModel, listingResultModel, type Config } from \"@riffyh/commons\";\nimport debug from \"debug\";\nimport path from \"node:path\";\nimport { download, upload } from \"./bytebin\";\nimport { secretbox, randomBytes } from \"tweetnacl\";\nimport { encodeBase64, decodeBase64 } from \"tweetnacl-util\";\n\nconst log = debug(\"riffyh:server\");\nconst cache = defineCacheInstance();\n\nlog(\"warming up server...\");\n\nconst configFile = process.env.RIFFYH_CONFIG_PATH || \"./riffyh.config.ts\";\nconst configPath = path.resolve(configFile);\nlog(`resolving config file at ${configPath}...`);\nconst config: Config = await import(configPath).then((o) => o.default);\n\ntry {\n if (\n typeof config.secretboxKey !== \"string\" ||\n decodeBase64(config.secretboxKey).length !== secretbox.keyLength\n )\n throw new Error(\"key length mismatch\");\n // oxlint-disable-next-line no-unused-vars\n} catch (_) {\n const generatedKey = encodeBase64(randomBytes(secretbox.keyLength));\n console.error(\n `unable to parse secret key. please either generate key via https://tweetnacl.js.org/#/secretbox or copy following key to configuration: ${generatedKey}`,\n );\n process.exit(1);\n}\n\nlog(`loaded configuration with ${config.dataSources.length} data sources`);\n\nconst dataSourceKeys = t.Union(config.dataSources.map((o) => t.Literal(o.key)));\n\nconst server = new Elysia()\n .use(\n swagger({\n exclude: [\"/_image\"],\n }),\n )\n .use(toon())\n .use(cors())\n .get(\"/\", ({ redirect }) => redirect(\"/swagger\"))\n .get(\"/health\", () => \"healthy\")\n .get(\n \"/dataSources\",\n () =>\n config.dataSources.map((o) => ({\n key: o.key,\n name: o.name,\n iconUrl: o.iconUrl,\n })),\n {\n response: t.Array(\n t.Object({\n key: t.String(),\n name: t.String(),\n iconUrl: t.String(),\n }),\n ),\n },\n )\n .get(\n \"/gallery\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getGallery({\n id: query.id,\n });\n },\n {\n query: t.Object({\n id: t.String(),\n dataSource: dataSourceKeys,\n }),\n response: galleryModel,\n },\n )\n .get(\n \"/listing\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getListing({\n searchQuery: query.query || null,\n page: query.page,\n });\n },\n {\n query: t.Object({\n query: t.Optional(t.String()),\n page: t.Number(),\n dataSource: dataSourceKeys,\n }),\n response: listingResultModel,\n },\n )\n .get(\n \"/tag\",\n async ({ query }) => {\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n return dataSource.getTagListing({\n id: query.id,\n page: query.page,\n });\n },\n {\n query: t.Object({\n id: t.String(),\n page: t.Number(),\n dataSource: dataSourceKeys,\n }),\n response: listingResultModel,\n },\n )\n .post(\"/collection/export\", ({ body }) => upload(body, config.secretboxKey), {\n body: t.String(),\n })\n .get(\"/collection/import\", async ({ query }) => download(query.key, config.secretboxKey), {\n query: t.Object({\n key: t.String(),\n }),\n })\n .get(\n \"/image\",\n async ({ query }) => {\n const cacheKeys = [query.dataSource, query.url, query.format, query.type];\n\n const cachedImage = await cache.read<Buffer>(cacheKeys);\n if (cachedImage !== null)\n return new Response(Buffer.from(cachedImage.data), {\n headers: {\n \"Content-Type\": `image/${query.format}`,\n \"Cache-Control\": \"public, max-age=86400000\",\n },\n });\n\n const dataSource = config.dataSources.find((o) => o.key === query.dataSource);\n if (dataSource === undefined) throw new Error(`data source ${query.dataSource} not found`);\n\n const fetchedImage = await dataSource.getImage({\n url: query.url,\n });\n const resizedImage = await sharp(fetchedImage)\n .resize({\n width: query.type === \"cover\" ? 640 : 1280,\n })\n .toFormat(query.format, {\n quality: 72,\n })\n .toBuffer();\n await cache.write(\n cacheKeys,\n resizedImage,\n 86_400_000, // 1 month\n );\n\n return new Response(resizedImage, {\n headers: {\n \"Content-Type\": `image/${query.format}`,\n \"Cache-Control\": \"public, max-age=86400\",\n \"CDN-Cache-Control\": \"public, max-age=2592000\",\n \"Cloudflare-CDN-Cache-Control\": \"public, max-age=2592000\",\n },\n });\n },\n {\n query: t.Object({\n url: t.String(),\n format: t.Union([t.Literal(\"webp\"), t.Literal(\"jpeg\")]),\n type: t.Union([t.Literal(\"cover\"), t.Literal(\"page\")]),\n dataSource: dataSourceKeys,\n }),\n },\n )\n .listen({\n hostname: config.hostname ?? \"0.0.0.0\",\n port: config.port ?? 3000,\n });\n\nexport type Server = typeof server;\n\nconsole.log(`🦊 Elysia is running at http://${server.server?.hostname}:${server.server?.port}`);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@riffyh/server",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/rayriffy/rayriffy-h.git"
|
|
@@ -16,21 +16,21 @@
|
|
|
16
16
|
"registry": "https://registry.npmjs.org"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@elysiajs/cors": "1.4.
|
|
19
|
+
"@elysiajs/cors": "1.4.2",
|
|
20
20
|
"@elysiajs/swagger": "1.3.1",
|
|
21
21
|
"@rayriffy/filesystem": "1.0.1",
|
|
22
22
|
"@sinclair/typebox": "0.34.49",
|
|
23
23
|
"@toon-tools/elysia": "1.1.0",
|
|
24
24
|
"debug": "4.4.3",
|
|
25
|
-
"elysia": "1.4.
|
|
26
|
-
"sharp": "0.
|
|
25
|
+
"elysia": "1.4.29",
|
|
26
|
+
"sharp": "0.35.2",
|
|
27
27
|
"tweetnacl": "1.0.3",
|
|
28
28
|
"tweetnacl-util": "0.15.1",
|
|
29
|
-
"@riffyh/commons": "2.
|
|
29
|
+
"@riffyh/commons": "2.2.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/debug": "4.1.13",
|
|
33
|
-
"tsdown": "0.
|
|
33
|
+
"tsdown": "0.22.3"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
|
36
36
|
"dev": "tsdown --watch",
|