From edc6104a099156f84d19631f56f7b62897ca9736 Mon Sep 17 00:00:00 2001 From: monoid Date: Thu, 1 Jun 2023 14:18:53 +0900 Subject: [PATCH] add: dprint fmt --- LICENSE.md | 2 +- README.md | 6 +- app.ts | 196 ++-- dprint.json | 23 + gen_conf_schema.ts | 52 +- knexfile.js | 4 +- migrations/initial.ts | 54 +- package.json | 2 + plan.md | 6 +- pnpm-lock.yaml | 1133 ++++++++++++---------- preload.ts | 12 +- src/SettingConfig.schema.json | 124 +-- src/SettingConfig.ts | 70 +- src/client/accessor/document.ts | 88 +- src/client/accessor/util.ts | 45 +- src/client/app.tsx | 47 +- src/client/build.ts | 23 +- src/client/component/contentinfo.tsx | 275 +++--- src/client/component/headline.tsx | 377 +++---- src/client/component/loading.tsx | 16 +- src/client/component/mod.ts | 10 +- src/client/component/navlist.tsx | 87 +- src/client/component/tagchip.tsx | 137 +-- src/client/package.json | 44 +- src/client/page/404.tsx | 20 +- src/client/page/contentinfo.tsx | 88 +- src/client/page/difference.tsx | 196 ++-- src/client/page/gallery.tsx | 139 +-- src/client/page/login.tsx | 124 ++- src/client/page/mod.ts | 16 +- src/client/page/profile.tsx | 219 +++-- src/client/page/reader/comic.tsx | 77 +- src/client/page/reader/reader.tsx | 95 +- src/client/page/reader/video.tsx | 13 +- src/client/page/setting.tsx | 20 +- src/client/page/tags.tsx | 88 +- src/client/state.tsx | 75 +- src/config.ts | 34 +- src/content/comic.ts | 81 +- src/content/file.ts | 78 +- src/content/mod.ts | 6 +- src/content/video.ts | 12 +- src/database.ts | 38 +- src/db/doc.ts | 231 ++--- src/db/mod.ts | 6 +- src/db/tag.ts | 56 +- src/db/user.ts | 79 +- src/diff/content_handler.ts | 41 +- src/diff/content_list.ts | 46 +- src/diff/diff.ts | 38 +- src/diff/mod.ts | 4 +- src/diff/router.ts | 88 +- src/diff/watcher.ts | 34 +- src/diff/watcher/ComicConfig.schema.json | 13 +- src/diff/watcher/ComicConfig.ts | 11 +- src/diff/watcher/comic_watcher.ts | 29 +- src/diff/watcher/common_watcher.ts | 53 +- src/diff/watcher/compositer.ts | 23 +- src/diff/watcher/recursive_watcher.ts | 76 +- src/diff/watcher/util.ts | 39 +- src/diff/watcher/watcher_filter.ts | 51 +- src/login.ts | 386 ++++---- src/model/doc.ts | 110 +-- src/model/mod.ts | 6 +- src/model/tag.ts | 20 +- src/model/user.ts | 70 +- src/permission/permission.ts | 80 +- src/route/all.ts | 98 +- src/route/comic.ts | 151 ++- src/route/contents.ts | 220 +++-- src/route/context.ts | 12 +- src/route/error_handler.ts | 43 +- src/route/tags.ts | 49 +- src/route/util.ts | 38 +- src/route/video.ts | 67 +- src/search/indexer.ts | 19 +- src/search/tokenizer.ts | 9 +- src/server.ts | 295 +++--- src/types/db.d.ts | 62 +- src/types/json.ts | 9 +- src/util/configRW.ts | 50 +- src/util/type_check.ts | 13 +- src/util/zipwrap.ts | 38 +- tsconfig.json | 32 +- 84 files changed, 3674 insertions(+), 3373 deletions(-) create mode 100644 dprint.json diff --git a/LICENSE.md b/LICENSE.md index 1421f76..4aa511c 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -6,4 +6,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index e793b9f..71d77b2 100644 --- a/README.md +++ b/README.md @@ -4,25 +4,29 @@ Content File Management Program. For study about nodejs, typescript and react. ### deployment + ```bash pnpm run app:build ``` ### test + ```bash $ pnpm run app ``` ### server build + ```bash $ pnpm run compile ``` ### client build + ```bash $ pnpm run build ``` ## License -MIT License \ No newline at end of file +MIT License diff --git a/app.ts b/app.ts index 3aad477..27c6636 100644 --- a/app.ts +++ b/app.ts @@ -1,115 +1,113 @@ -import { app, BrowserWindow, session, dialog } from "electron"; -import { get_setting } from "./src/SettingConfig"; -import { create_server } from "./src/server"; -import { getAdminAccessTokenValue,getAdminRefreshTokenValue, accessTokenName, refreshTokenName } from "./src/login"; +import { app, BrowserWindow, dialog, session } from "electron"; +import { ipcMain } from "electron"; import { join } from "path"; -import { ipcMain } from 'electron'; +import { accessTokenName, getAdminAccessTokenValue, getAdminRefreshTokenValue, refreshTokenName } from "./src/login"; import { UserAccessor } from "./src/model/mod"; +import { create_server } from "./src/server"; +import { get_setting } from "./src/SettingConfig"; -function registerChannel(cntr: UserAccessor){ - ipcMain.handle('reset_password', async(event,username:string,password:string)=>{ - const user = await cntr.findUser(username); - if(user === undefined){ - return false; - } - user.reset_password(password); - return true; - }); +function registerChannel(cntr: UserAccessor) { + ipcMain.handle("reset_password", async (event, username: string, password: string) => { + const user = await cntr.findUser(username); + if (user === undefined) { + return false; + } + user.reset_password(password); + return true; + }); } const setting = get_setting(); if (!setting.cli) { - let wnd: BrowserWindow | null = null; + let wnd: BrowserWindow | null = null; - const createWindow = async () => { - wnd = new BrowserWindow({ - width: 800, - height: 600, - center: true, - useContentSize: true, - webPreferences:{ - preload:join(__dirname,'preload.js'), - contextIsolation:true, - } - }); - await wnd.loadURL(`data:text/html;base64,`+Buffer.from(loading_html).toString('base64')); - //await wnd.loadURL('../loading.html'); - //set admin cookies. - await session.defaultSession.cookies.set({ - url:`http://localhost:${setting.port}`, - name:accessTokenName, - value:getAdminAccessTokenValue(), - httpOnly: true, - secure: false, - sameSite:"strict" - }); - await session.defaultSession.cookies.set({ - url:`http://localhost:${setting.port}`, - name:refreshTokenName, - value:getAdminRefreshTokenValue(), - httpOnly: true, - secure: false, - sameSite:"strict" - }); - try{ - const server = await create_server(); - const app = server.start_server(); - registerChannel(server.userController); - await wnd.loadURL(`http://localhost:${setting.port}`); - } - catch(e){ - if(e instanceof Error){ - await dialog.showMessageBox({ - type: "error", - title:"error!", - message:e.message, + const createWindow = async () => { + wnd = new BrowserWindow({ + width: 800, + height: 600, + center: true, + useContentSize: true, + webPreferences: { + preload: join(__dirname, "preload.js"), + contextIsolation: true, + }, }); - } - else{ - await dialog.showMessageBox({ - type: "error", - title:"error!", - message:String(e), + await wnd.loadURL(`data:text/html;base64,` + Buffer.from(loading_html).toString("base64")); + // await wnd.loadURL('../loading.html'); + // set admin cookies. + await session.defaultSession.cookies.set({ + url: `http://localhost:${setting.port}`, + name: accessTokenName, + value: getAdminAccessTokenValue(), + httpOnly: true, + secure: false, + sameSite: "strict", }); - } + await session.defaultSession.cookies.set({ + url: `http://localhost:${setting.port}`, + name: refreshTokenName, + value: getAdminRefreshTokenValue(), + httpOnly: true, + secure: false, + sameSite: "strict", + }); + try { + const server = await create_server(); + const app = server.start_server(); + registerChannel(server.userController); + await wnd.loadURL(`http://localhost:${setting.port}`); + } catch (e) { + if (e instanceof Error) { + await dialog.showMessageBox({ + type: "error", + title: "error!", + message: e.message, + }); + } else { + await dialog.showMessageBox({ + type: "error", + title: "error!", + message: String(e), + }); + } + } + wnd.on("closed", () => { + wnd = null; + }); + }; + + const isPrimary = app.requestSingleInstanceLock(); + if (!isPrimary) { + app.quit(); // exit window + app.exit(); } - wnd.on("closed", () => { - wnd = null; + app.on("second-instance", () => { + if (wnd != null) { + if (wnd.isMinimized()) { + wnd.restore(); + } + wnd.focus(); + } + }); + app.on("ready", (event, info) => { + createWindow(); }); - }; - const isPrimary = app.requestSingleInstanceLock(); - if (!isPrimary) { - app.quit(); //exit window - app.exit(); - } - app.on("second-instance", () => { - if (wnd != null) { - if (wnd.isMinimized()) { - wnd.restore(); - } - wnd.focus(); - } - }); - app.on("ready", (event, info) => { - createWindow(); - }); + app.on("window-all-closed", () => { // quit when all windows are closed + if (process.platform != "darwin") app.quit(); // (except leave MacOS app active until Cmd+Q) + }); - app.on("window-all-closed", () => { // quit when all windows are closed - if (process.platform != "darwin") app.quit(); // (except leave MacOS app active until Cmd+Q) - }); - - app.on("activate", () => { // re-recreate window when dock icon is clicked and no other windows open - if (wnd == null) createWindow(); - }); + app.on("activate", () => { // re-recreate window when dock icon is clicked and no other windows open + if (wnd == null) createWindow(); + }); } else { - (async () => { - try { - const server = await create_server(); - server.start_server(); - } catch (error) { - console.log(error); - } - })(); + (async () => { + try { + const server = await create_server(); + server.start_server(); + } catch (error) { + console.log(error); + } + })(); } const loading_html = ` @@ -142,4 +140,4 @@ h1 {

Loading...

-`; \ No newline at end of file +`; diff --git a/dprint.json b/dprint.json new file mode 100644 index 0000000..3911cdd --- /dev/null +++ b/dprint.json @@ -0,0 +1,23 @@ +{ + "incremental": true, + "typescript": { + "indentWidth": 2 + }, + "json": { + }, + "markdown": { + }, + "includes": ["**/*.{ts,tsx,js,jsx,cjs,mjs,json,md}"], + "excludes": [ + "**/node_modules", + "**/*-lock.json", + "**/dist", + "build/", + "app/" + ], + "plugins": [ + "https://plugins.dprint.dev/typescript-0.84.4.wasm", + "https://plugins.dprint.dev/json-0.17.2.wasm", + "https://plugins.dprint.dev/markdown-0.15.2.wasm" + ] +} diff --git a/gen_conf_schema.ts b/gen_conf_schema.ts index 5a2071d..460665b 100644 --- a/gen_conf_schema.ts +++ b/gen_conf_schema.ts @@ -1,48 +1,48 @@ -import { promises } from 'fs'; +import { promises } from "fs"; const { readdir, writeFile } = promises; -import {createGenerator} from 'ts-json-schema-generator'; -import {dirname,join} from 'path'; +import { dirname, join } from "path"; +import { createGenerator } from "ts-json-schema-generator"; -async function genSchema(path:string,typename:string){ +async function genSchema(path: string, typename: string) { const gen = createGenerator({ - path:path, - type:typename, - tsconfig:"tsconfig.json" + path: path, + type: typename, + tsconfig: "tsconfig.json", }); - const schema = gen.createSchema(typename); - if(schema.definitions != undefined){ + const schema = gen.createSchema(typename); + if (schema.definitions != undefined) { const definitions = schema.definitions; const definition = definitions[typename]; - if(typeof definition == "object" ){ + if (typeof definition == "object") { let property = definition.properties; - if(property){ - property['$schema'] = { - type:"string" + if (property) { + property["$schema"] = { + type: "string", }; } } } const text = JSON.stringify(schema); - await writeFile(join(dirname(path),`${typename}.schema.json`),text); + await writeFile(join(dirname(path), `${typename}.schema.json`), text); } -function capitalize(s:string){ +function capitalize(s: string) { return s.charAt(0).toUpperCase() + s.slice(1); } -async function setToALL(path:string) { - console.log(`scan ${path}`) - const direntry = await readdir(path,{withFileTypes:true}); - const works = direntry.filter(x=>x.isFile()&&x.name.endsWith("Config.ts")).map(x=>{ +async function setToALL(path: string) { + console.log(`scan ${path}`); + const direntry = await readdir(path, { withFileTypes: true }); + const works = direntry.filter(x => x.isFile() && x.name.endsWith("Config.ts")).map(x => { const name = x.name; const m = /(.+)\.ts/.exec(name); - if(m !== null){ + if (m !== null) { const typename = m[1]; - return genSchema(join(path,typename),capitalize(typename)); + return genSchema(join(path, typename), capitalize(typename)); } - }) + }); await Promise.all(works); - const subdir = direntry.filter(x=>x.isDirectory()).map(x=>x.name); - for(const x of subdir){ - await setToALL(join(path,x)); + const subdir = direntry.filter(x => x.isDirectory()).map(x => x.name); + for (const x of subdir) { + await setToALL(join(path, x)); } } -setToALL("src") \ No newline at end of file +setToALL("src"); diff --git a/knexfile.js b/knexfile.js index 54a887a..76f47c9 100644 --- a/knexfile.js +++ b/knexfile.js @@ -1,5 +1,5 @@ -require('ts-node').register(); -const {Knex} = require('./src/config'); +require("ts-node").register(); +const { Knex } = require("./src/config"); // Update with your config settings. module.exports = Knex.config; diff --git a/migrations/initial.ts b/migrations/initial.ts index 15ce14e..816926d 100644 --- a/migrations/initial.ts +++ b/migrations/initial.ts @@ -1,54 +1,54 @@ -import {Knex} from 'knex'; +import { Knex } from "knex"; -export async function up(knex:Knex) { - await knex.schema.createTable("schema_migration",(b)=>{ +export async function up(knex: Knex) { + await knex.schema.createTable("schema_migration", (b) => { b.string("version"); b.boolean("dirty"); }); - await knex.schema.createTable("users",(b)=>{ + await knex.schema.createTable("users", (b) => { b.string("username").primary().comment("user's login id"); - b.string("password_hash",64).notNullable(); - b.string("password_salt",64).notNullable(); + b.string("password_hash", 64).notNullable(); + b.string("password_salt", 64).notNullable(); }); - await knex.schema.createTable("document",(b)=>{ + await knex.schema.createTable("document", (b) => { b.increments("id").primary(); b.string("title").notNullable(); - b.string("content_type",16).notNullable(); - b.string("basepath",256).notNullable().comment("directory path for resource"); - b.string("filename",256).notNullable().comment("filename"); + b.string("content_type", 16).notNullable(); + b.string("basepath", 256).notNullable().comment("directory path for resource"); + b.string("filename", 256).notNullable().comment("filename"); b.string("content_hash").nullable(); b.json("additional").nullable(); b.integer("created_at").notNullable(); b.integer("modified_at").notNullable(); b.integer("deleted_at"); - b.index("content_type","content_type_index"); + b.index("content_type", "content_type_index"); }); - await knex.schema.createTable("tags", (b)=>{ + await knex.schema.createTable("tags", (b) => { b.string("name").primary(); b.text("description"); }); - await knex.schema.createTable("doc_tag_relation",(b)=>{ + await knex.schema.createTable("doc_tag_relation", (b) => { b.integer("doc_id").unsigned().notNullable(); b.string("tag_name").notNullable(); b.foreign("doc_id").references("document.id"); b.foreign("tag_name").references("tags.name"); - b.primary(["doc_id","tag_name"]); + b.primary(["doc_id", "tag_name"]); }); - await knex.schema.createTable("permissions",b=>{ - b.string('username').notNullable(); + await knex.schema.createTable("permissions", b => { + b.string("username").notNullable(); b.string("name").notNullable(); - b.primary(["username","name"]); - b.foreign('username').references('users.username'); + b.primary(["username", "name"]); + b.foreign("username").references("users.username"); }); - //create admin account. + // create admin account. await knex.insert({ - username:"admin", - password_hash:"unchecked", - password_salt:"unchecked" - }).into('users'); -}; + username: "admin", + password_hash: "unchecked", + password_salt: "unchecked", + }).into("users"); +} -export async function down(knex:Knex) { - throw new Error('Downward migrations are not supported. Restore from backup.'); -}; +export async function down(knex: Knex) { + throw new Error("Downward migrations are not supported. Restore from backup."); +} diff --git a/package.json b/package.json index 309ab81..2d0c444 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "compile:watch": "tsc -w", "build": "cd src/client && pnpm run build:prod", "build:watch": "cd src/client && pnpm run build:watch", + "fmt": "dprint fmt", "app": "electron build/app.js", "app:build": "electron-builder", "app:pack": "electron-builder --dir", @@ -56,6 +57,7 @@ "@louislam/sqlite3": "^6.0.1", "@types/koa-compose": "^3.2.5", "chokidar": "^3.5.3", + "dprint": "^0.36.1", "jsonschema": "^1.4.1", "jsonwebtoken": "^8.5.1", "knex": "^0.95.15", diff --git a/plan.md b/plan.md index aeed2b4..32b8dd1 100644 --- a/plan.md +++ b/plan.md @@ -3,6 +3,7 @@ ## Routing ### server routing + - content - \d+ - comic @@ -31,6 +32,7 @@ - profile ## TODO + - server push - ~~permission~~ - diff @@ -44,6 +46,6 @@ add URL Render page 바꾸기 add modified_time -add support robots.txt +add support robots.txt -add vite ssr \ No newline at end of file +add vite ssr diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6dc1960..b4db00e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,76 +1,102 @@ -lockfileVersion: 5.4 - -specifiers: - '@louislam/sqlite3': ^6.0.1 - '@types/jsonwebtoken': ^8.5.8 - '@types/koa': ^2.13.4 - '@types/koa-bodyparser': ^4.3.7 - '@types/koa-compose': ^3.2.5 - '@types/koa-router': ^7.4.4 - '@types/node': ^14.18.21 - '@types/tiny-async-pool': ^1.0.1 - chokidar: ^3.5.3 - electron: ^11.5.0 - electron-builder: ^22.14.13 - jsonschema: ^1.4.1 - jsonwebtoken: ^8.5.1 - knex: ^0.95.15 - koa: ^2.13.4 - koa-bodyparser: ^4.3.0 - koa-compose: ^4.1.0 - koa-router: ^10.1.1 - natural-orderby: ^2.0.3 - node-stream-zip: ^1.15.0 - sqlite3: ^5.0.8 - tiny-async-pool: ^1.3.0 - ts-json-schema-generator: ^0.82.0 - ts-node: ^9.1.1 - typescript: ^4.7.4 +lockfileVersion: '6.0' dependencies: - '@louislam/sqlite3': 6.0.1 - '@types/koa-compose': 3.2.5 - chokidar: 3.5.3 - jsonschema: 1.4.1 - jsonwebtoken: 8.5.1 - knex: 0.95.15_sqlite3@5.0.8 - koa: 2.13.4 - koa-bodyparser: 4.3.0 - koa-compose: 4.1.0 - koa-router: 10.1.1 - natural-orderby: 2.0.3 - node-stream-zip: 1.15.0 - sqlite3: 5.0.8 - tiny-async-pool: 1.3.0 + '@louislam/sqlite3': + specifier: ^6.0.1 + version: 6.0.1 + '@types/koa-compose': + specifier: ^3.2.5 + version: 3.2.5 + chokidar: + specifier: ^3.5.3 + version: 3.5.3 + dprint: + specifier: ^0.36.1 + version: 0.36.1 + jsonschema: + specifier: ^1.4.1 + version: 1.4.1 + jsonwebtoken: + specifier: ^8.5.1 + version: 8.5.1 + knex: + specifier: ^0.95.15 + version: 0.95.15(sqlite3@5.0.8) + koa: + specifier: ^2.13.4 + version: 2.13.4 + koa-bodyparser: + specifier: ^4.3.0 + version: 4.3.0 + koa-compose: + specifier: ^4.1.0 + version: 4.1.0 + koa-router: + specifier: ^10.1.1 + version: 10.1.1 + natural-orderby: + specifier: ^2.0.3 + version: 2.0.3 + node-stream-zip: + specifier: ^1.15.0 + version: 1.15.0 + sqlite3: + specifier: ^5.0.8 + version: 5.0.8 + tiny-async-pool: + specifier: ^1.3.0 + version: 1.3.0 devDependencies: - '@types/jsonwebtoken': 8.5.8 - '@types/koa': 2.13.4 - '@types/koa-bodyparser': 4.3.7 - '@types/koa-router': 7.4.4 - '@types/node': 14.18.21 - '@types/tiny-async-pool': 1.0.1 - electron: 11.5.0 - electron-builder: 22.14.13 - ts-json-schema-generator: 0.82.0 - ts-node: 9.1.1_typescript@4.7.4 - typescript: 4.7.4 + '@types/jsonwebtoken': + specifier: ^8.5.8 + version: 8.5.8 + '@types/koa': + specifier: ^2.13.4 + version: 2.13.4 + '@types/koa-bodyparser': + specifier: ^4.3.7 + version: 4.3.7 + '@types/koa-router': + specifier: ^7.4.4 + version: 7.4.4 + '@types/node': + specifier: ^14.18.21 + version: 14.18.21 + '@types/tiny-async-pool': + specifier: ^1.0.1 + version: 1.0.1 + electron: + specifier: ^11.5.0 + version: 11.5.0 + electron-builder: + specifier: ^22.14.13 + version: 22.14.13 + ts-json-schema-generator: + specifier: ^0.82.0 + version: 0.82.0 + ts-node: + specifier: ^9.1.1 + version: 9.1.1(typescript@4.7.4) + typescript: + specifier: ^4.7.4 + version: 4.7.4 packages: - /7zip-bin/5.1.1: + /7zip-bin@5.1.1: resolution: {integrity: sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==} dev: true - /@develar/schema-utils/2.6.5: + /@develar/schema-utils@2.6.5: resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} engines: {node: '>= 8.9.0'} dependencies: ajv: 6.12.6 - ajv-keywords: 3.5.2_ajv@6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) dev: true - /@electron/get/1.14.1: + /@electron/get@1.14.1: resolution: {integrity: sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==} engines: {node: '>=8.6'} dependencies: @@ -88,7 +114,7 @@ packages: - supports-color dev: true - /@electron/universal/1.0.5: + /@electron/universal@1.0.5: resolution: {integrity: sha512-zX9O6+jr2NMyAdSkwEUlyltiI4/EBLu2Ls/VD3pUQdi3cAYeYfdQnT2AJJ38HE4QxLccbU13LSpccw1IWlkyag==} engines: {node: '>=8.6'} dependencies: @@ -101,12 +127,12 @@ packages: - supports-color dev: true - /@gar/promisify/1.1.3: + /@gar/promisify@1.1.3: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} dev: false optional: true - /@louislam/sqlite3/6.0.1: + /@louislam/sqlite3@6.0.1: resolution: {integrity: sha512-QGLj5bjQ+O4YSPj/qxtEAArbIqW9wNzBUamlIcRbvFjFiNokItwdubqL2Gl5iX0q1mUn3Z6NoFO1rrAZ/qqlsA==} requiresBuild: true peerDependenciesMeta: @@ -118,18 +144,19 @@ packages: optionalDependencies: node-gyp: 8.4.1 transitivePeerDependencies: + - bluebird - encoding - supports-color dev: false - /@malept/cross-spawn-promise/1.1.1: + /@malept/cross-spawn-promise@1.1.1: resolution: {integrity: sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==} engines: {node: '>= 10'} dependencies: cross-spawn: 7.0.3 dev: true - /@malept/flatpak-bundler/0.4.0: + /@malept/flatpak-bundler@0.4.0: resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} engines: {node: '>= 10.0.0'} dependencies: @@ -141,7 +168,7 @@ packages: - supports-color dev: true - /@mapbox/node-pre-gyp/1.0.9: + /@mapbox/node-pre-gyp@1.0.9: resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==} hasBin: true dependencies: @@ -159,7 +186,7 @@ packages: - supports-color dev: false - /@npmcli/fs/1.1.1: + /@npmcli/fs@1.1.1: resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} dependencies: '@gar/promisify': 1.1.3 @@ -167,7 +194,7 @@ packages: dev: false optional: true - /@npmcli/move-file/1.1.2: + /@npmcli/move-file@1.1.2: resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} engines: {node: '>=10'} dependencies: @@ -176,49 +203,49 @@ packages: dev: false optional: true - /@sindresorhus/is/0.14.0: + /@sindresorhus/is@0.14.0: resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} engines: {node: '>=6'} dev: true - /@szmarczak/http-timer/1.1.2: + /@szmarczak/http-timer@1.1.2: resolution: {integrity: sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==} engines: {node: '>=6'} dependencies: defer-to-connect: 1.1.3 dev: true - /@tootallnate/once/1.1.2: + /@tootallnate/once@1.1.2: resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} engines: {node: '>= 6'} dev: false optional: true - /@tootallnate/once/2.0.0: + /@tootallnate/once@2.0.0: resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} engines: {node: '>= 10'} dev: true - /@types/accepts/1.3.5: + /@types/accepts@1.3.5: resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==} dependencies: '@types/node': 14.18.21 - /@types/body-parser/1.19.2: + /@types/body-parser@1.19.2: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 '@types/node': 14.18.21 - /@types/connect/3.4.35: + /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: '@types/node': 14.18.21 - /@types/content-disposition/0.5.5: + /@types/content-disposition@0.5.5: resolution: {integrity: sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==} - /@types/cookies/0.7.7: + /@types/cookies@0.7.7: resolution: {integrity: sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==} dependencies: '@types/connect': 3.4.35 @@ -226,20 +253,20 @@ packages: '@types/keygrip': 1.0.2 '@types/node': 14.18.21 - /@types/debug/4.1.7: + /@types/debug@4.1.7: resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==} dependencies: '@types/ms': 0.7.31 dev: true - /@types/express-serve-static-core/4.17.29: + /@types/express-serve-static-core@4.17.29: resolution: {integrity: sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==} dependencies: '@types/node': 14.18.21 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 - /@types/express/4.17.13: + /@types/express@4.17.13: resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==} dependencies: '@types/body-parser': 1.19.2 @@ -247,13 +274,13 @@ packages: '@types/qs': 6.9.7 '@types/serve-static': 1.13.10 - /@types/fs-extra/9.0.13: + /@types/fs-extra@9.0.13: resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} dependencies: '@types/node': 14.18.21 dev: true - /@types/glob/7.2.0: + /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} requiresBuild: true dependencies: @@ -262,43 +289,49 @@ packages: dev: true optional: true - /@types/http-assert/1.5.3: + /@types/http-assert@1.5.3: resolution: {integrity: sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==} - /@types/http-errors/1.8.2: + /@types/http-errors@1.8.2: resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==} - /@types/json-schema/7.0.11: + /@types/json-schema@7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true - /@types/jsonwebtoken/8.5.8: + /@types/jsonwebtoken@8.5.8: resolution: {integrity: sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==} dependencies: '@types/node': 14.18.21 dev: true - /@types/keygrip/1.0.2: + /@types/keygrip@1.0.2: resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==} - /@types/koa-bodyparser/4.3.7: + /@types/keyv@3.1.4: + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + dependencies: + '@types/node': 14.18.21 + dev: true + + /@types/koa-bodyparser@4.3.7: resolution: {integrity: sha512-21NhEp7LjZm4zbNV5alHHmrNY4J+S7B8lYTO6CzRL8ShTMnl20Gd14dRgVhAxraLaW5iZMofox+BycbuiDvj2Q==} dependencies: '@types/koa': 2.13.4 dev: true - /@types/koa-compose/3.2.5: + /@types/koa-compose@3.2.5: resolution: {integrity: sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==} dependencies: '@types/koa': 2.13.4 - /@types/koa-router/7.4.4: + /@types/koa-router@7.4.4: resolution: {integrity: sha512-3dHlZ6CkhgcWeF6wafEUvyyqjWYfKmev3vy1PtOmr0mBc3wpXPU5E8fBBd4YQo5bRpHPfmwC5yDaX7s4jhIN6A==} dependencies: '@types/koa': 2.13.4 dev: true - /@types/koa/2.13.4: + /@types/koa@2.13.4: resolution: {integrity: sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==} dependencies: '@types/accepts': 1.3.5 @@ -310,26 +343,26 @@ packages: '@types/koa-compose': 3.2.5 '@types/node': 14.18.21 - /@types/mime/1.3.2: + /@types/mime@1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} - /@types/minimatch/3.0.5: + /@types/minimatch@3.0.5: resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} dev: true optional: true - /@types/ms/0.7.31: + /@types/ms@0.7.31: resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} dev: true - /@types/node/12.20.55: + /@types/node@12.20.55: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true - /@types/node/14.18.21: + /@types/node@14.18.21: resolution: {integrity: sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==} - /@types/plist/3.0.2: + /@types/plist@3.0.2: resolution: {integrity: sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==} requiresBuild: true dependencies: @@ -338,43 +371,49 @@ packages: dev: true optional: true - /@types/qs/6.9.7: + /@types/qs@6.9.7: resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} - /@types/range-parser/1.2.4: + /@types/range-parser@1.2.4: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - /@types/serve-static/1.13.10: + /@types/responselike@1.0.0: + resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} + dependencies: + '@types/node': 14.18.21 + dev: true + + /@types/serve-static@1.13.10: resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} dependencies: '@types/mime': 1.3.2 '@types/node': 14.18.21 - /@types/tiny-async-pool/1.0.1: + /@types/tiny-async-pool@1.0.1: resolution: {integrity: sha512-7msTTzXOAI9zS/u4DJLMAfAT/sz1laaZTFYVB83h0L78q4yB8RzRyxcqUlfkg12/hMC6G/cG//vbDwmdTpZ+gQ==} dev: true - /@types/verror/1.10.5: + /@types/verror@1.10.5: resolution: {integrity: sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw==} requiresBuild: true dev: true optional: true - /@types/yargs-parser/21.0.0: + /@types/yargs-parser@21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true - /@types/yargs/17.0.10: + /@types/yargs@17.0.10: resolution: {integrity: sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA==} dependencies: '@types/yargs-parser': 21.0.0 dev: true - /abbrev/1.1.1: + /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: false - /accepts/1.3.8: + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} dependencies: @@ -382,7 +421,7 @@ packages: negotiator: 0.6.3 dev: false - /agent-base/6.0.2: + /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: @@ -390,7 +429,7 @@ packages: transitivePeerDependencies: - supports-color - /agentkeepalive/4.2.1: + /agentkeepalive@4.2.1: resolution: {integrity: sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==} engines: {node: '>= 8.0.0'} dependencies: @@ -402,7 +441,7 @@ packages: dev: false optional: true - /aggregate-error/3.1.0: + /aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} dependencies: @@ -411,7 +450,7 @@ packages: dev: false optional: true - /ajv-keywords/3.5.2_ajv@6.12.6: + /ajv-keywords@3.5.2(ajv@6.12.6): resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: ajv: ^6.9.1 @@ -419,7 +458,7 @@ packages: ajv: 6.12.6 dev: true - /ajv/6.12.6: + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 @@ -428,24 +467,24 @@ packages: uri-js: 4.4.1 dev: true - /ansi-align/3.0.1: + /ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} dependencies: string-width: 4.2.3 dev: true - /ansi-regex/5.0.1: + /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-styles/4.3.0: + /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} dependencies: color-convert: 2.0.1 dev: true - /anymatch/3.1.2: + /anymatch@3.1.2: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} engines: {node: '>= 8'} dependencies: @@ -453,18 +492,18 @@ packages: picomatch: 2.3.1 dev: false - /app-builder-bin/3.7.1: + /app-builder-bin@3.7.1: resolution: {integrity: sha512-ql93vEUq6WsstGXD+SBLSIQw6SNnhbDEM0swzgugytMxLp3rT24Ag/jcC80ZHxiPRTdew1niuR7P3/FCrDqIjw==} dev: true - /app-builder-lib/22.14.13: + /app-builder-lib@22.14.13: resolution: {integrity: sha512-SufmrtxU+D0Tn948fjEwAOlCN9757UXLkzzTWXMwZKR/5hisvgqeeBepWfphMIE6OkDGz0fbzEhL1P2Pty4XMg==} engines: {node: '>=14.0.0'} dependencies: + 7zip-bin: 5.1.1 '@develar/schema-utils': 2.6.5 '@electron/universal': 1.0.5 '@malept/flatpak-bundler': 0.4.0 - 7zip-bin: 5.1.1 async-exit-hook: 2.0.1 bluebird-lst: 1.0.9 builder-util: 22.14.13 @@ -490,11 +529,11 @@ packages: - supports-color dev: true - /aproba/2.0.0: + /aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} dev: false - /are-we-there-yet/2.0.0: + /are-we-there-yet@2.0.0: resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} engines: {node: '>=10'} dependencies: @@ -502,7 +541,7 @@ packages: readable-stream: 3.6.0 dev: false - /are-we-there-yet/3.0.0: + /are-we-there-yet@3.0.0: resolution: {integrity: sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16} dependencies: @@ -511,15 +550,15 @@ packages: dev: false optional: true - /arg/4.1.3: + /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true - /argparse/2.0.1: + /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true - /asar/3.1.0: + /asar@3.1.0: resolution: {integrity: sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==} engines: {node: '>=10.12.0'} hasBin: true @@ -532,67 +571,67 @@ packages: '@types/glob': 7.2.0 dev: true - /assert-plus/1.0.0: + /assert-plus@1.0.0: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} requiresBuild: true dev: true optional: true - /astral-regex/2.0.0: + /astral-regex@2.0.0: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} requiresBuild: true dev: true optional: true - /async-exit-hook/2.0.1: + /async-exit-hook@2.0.1: resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} engines: {node: '>=0.12.0'} dev: true - /async/3.2.4: + /async@3.2.4: resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} dev: true - /asynckit/0.4.0: + /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} dev: true - /at-least-node/1.0.0: + /at-least-node@1.0.0: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} dev: true - /balanced-match/1.0.2: + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - /base64-js/1.5.1: + /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} requiresBuild: true dev: true - /binary-extensions/2.2.0: + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} dev: false - /bluebird-lst/1.0.9: + /bluebird-lst@1.0.9: resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==} dependencies: bluebird: 3.7.2 dev: true - /bluebird/3.7.2: + /bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} dev: true - /boolean/3.2.0: + /boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} dev: true optional: true - /boxen/5.1.2: + /boxen@5.1.2: resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} engines: {node: '>=10'} dependencies: @@ -606,58 +645,57 @@ packages: wrap-ansi: 7.0.0 dev: true - /brace-expansion/1.1.11: + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - /brace-expansion/2.0.1: + /brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 dev: true - /braces/3.0.2: + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} dependencies: fill-range: 7.0.1 dev: false - /buffer-alloc-unsafe/1.1.0: + /buffer-alloc-unsafe@1.1.0: resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} dev: true - /buffer-alloc/1.2.0: + /buffer-alloc@1.2.0: resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} dependencies: buffer-alloc-unsafe: 1.1.0 buffer-fill: 1.0.0 dev: true - /buffer-crc32/0.2.13: + /buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - dev: true - /buffer-equal-constant-time/1.0.1: + /buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} dev: false - /buffer-equal/1.0.0: + /buffer-equal@1.0.0: resolution: {integrity: sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==} engines: {node: '>=0.4.0'} dev: true - /buffer-fill/1.0.0: + /buffer-fill@1.0.0: resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} dev: true - /buffer-from/1.1.2: + /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true - /buffer/5.7.1: + /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} requiresBuild: true dependencies: @@ -666,7 +704,7 @@ packages: dev: true optional: true - /builder-util-runtime/8.9.2: + /builder-util-runtime@8.9.2: resolution: {integrity: sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==} engines: {node: '>=12.0.0'} dependencies: @@ -676,12 +714,12 @@ packages: - supports-color dev: true - /builder-util/22.14.13: + /builder-util@22.14.13: resolution: {integrity: sha512-oePC/qrrUuerhmH5iaCJzPRAKlSBylrhzuAJmRQClTyWnZUv6jbaHh+VoHMbEiE661wrj2S2aV7/bQh12cj1OA==} dependencies: + 7zip-bin: 5.1.1 '@types/debug': 4.1.7 '@types/fs-extra': 9.0.13 - 7zip-bin: 5.1.1 app-builder-bin: 3.7.1 bluebird-lst: 1.0.9 builder-util-runtime: 8.9.2 @@ -700,12 +738,12 @@ packages: - supports-color dev: true - /bytes/3.1.2: + /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} dev: false - /cacache/15.3.0: + /cacache@15.3.0: resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} engines: {node: '>= 10'} dependencies: @@ -727,10 +765,12 @@ packages: ssri: 8.0.1 tar: 6.1.11 unique-filename: 1.1.1 + transitivePeerDependencies: + - bluebird dev: false optional: true - /cache-content-type/1.0.1: + /cache-content-type@1.0.1: resolution: {integrity: sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==} engines: {node: '>= 6.0.0'} dependencies: @@ -738,7 +778,7 @@ packages: ylru: 1.3.2 dev: false - /cacheable-request/6.1.0: + /cacheable-request@6.1.0: resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} engines: {node: '>=8'} dependencies: @@ -751,19 +791,19 @@ packages: responselike: 1.0.2 dev: true - /call-bind/1.0.2: + /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} dependencies: function-bind: 1.1.1 get-intrinsic: 1.1.2 dev: false - /camelcase/6.3.0: + /camelcase@6.3.0: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} dev: true - /chalk/4.1.2: + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} dependencies: @@ -771,7 +811,7 @@ packages: supports-color: 7.2.0 dev: true - /chokidar/3.5.3: + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} dependencies: @@ -786,35 +826,35 @@ packages: fsevents: 2.3.2 dev: false - /chownr/2.0.0: + /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} dev: false - /chromium-pickle-js/0.2.0: + /chromium-pickle-js@0.2.0: resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} dev: true - /ci-info/2.0.0: + /ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} dev: true - /ci-info/3.3.2: + /ci-info@3.3.2: resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==} dev: true - /clean-stack/2.2.0: + /clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} dev: false optional: true - /cli-boxes/2.2.1: + /cli-boxes@2.2.1: resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} engines: {node: '>=6'} dev: true - /cli-truncate/2.1.0: + /cli-truncate@2.1.0: resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} engines: {node: '>=8'} requiresBuild: true @@ -824,7 +864,7 @@ packages: dev: true optional: true - /cliui/7.0.4: + /cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 @@ -832,13 +872,13 @@ packages: wrap-ansi: 7.0.0 dev: true - /clone-response/1.0.2: + /clone-response@1.0.2: resolution: {integrity: sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==} dependencies: mimic-response: 1.0.1 dev: true - /co-body/6.1.0: + /co-body@6.1.0: resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==} dependencies: inflation: 2.0.0 @@ -847,74 +887,74 @@ packages: type-is: 1.6.18 dev: false - /co/4.6.0: + /co@4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} dev: false - /color-convert/2.0.1: + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 dev: true - /color-name/1.1.4: + /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - /color-support/1.1.3: + /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true dev: false - /colorette/2.0.16: + /colorette@2.0.16: resolution: {integrity: sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==} dev: false - /colors/1.0.3: + /colors@1.0.3: resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==} engines: {node: '>=0.1.90'} dev: true - /combined-stream/1.0.8: + /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 dev: true - /commander/2.9.0: + /commander@2.9.0: resolution: {integrity: sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==} engines: {node: '>= 0.6.x'} dependencies: graceful-readlink: 1.0.1 dev: true - /commander/5.1.0: + /commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} dev: true - /commander/6.2.1: + /commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} dev: true - /commander/7.2.0: + /commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} dev: false - /compare-version/0.1.2: + /compare-version@0.1.2: resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} engines: {node: '>=0.10.0'} dev: true - /concat-map/0.0.1: + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - /concat-stream/1.6.2: + /concat-stream@1.6.2: resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} engines: {'0': node >= 0.8} dependencies: @@ -924,7 +964,7 @@ packages: typedarray: 0.0.6 dev: true - /config-chain/1.1.13: + /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} dependencies: ini: 1.3.8 @@ -932,7 +972,7 @@ packages: dev: true optional: true - /configstore/5.0.1: + /configstore@5.0.1: resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} engines: {node: '>=8'} dependencies: @@ -944,23 +984,23 @@ packages: xdg-basedir: 4.0.0 dev: true - /console-control-strings/1.1.0: + /console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} dev: false - /content-disposition/0.5.4: + /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} dependencies: safe-buffer: 5.2.1 dev: false - /content-type/1.0.4: + /content-type@1.0.4: resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} engines: {node: '>= 0.6'} dev: false - /cookies/0.8.0: + /cookies@0.8.0: resolution: {integrity: sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==} engines: {node: '>= 0.8'} dependencies: @@ -968,21 +1008,21 @@ packages: keygrip: 1.1.0 dev: false - /copy-to/2.0.1: + /copy-to@2.0.1: resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} dev: false - /core-util-is/1.0.2: + /core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} requiresBuild: true dev: true optional: true - /core-util-is/1.0.3: + /core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true - /crc/3.8.0: + /crc@3.8.0: resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} requiresBuild: true dependencies: @@ -990,11 +1030,11 @@ packages: dev: true optional: true - /create-require/1.1.1: + /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true - /cross-spawn/7.0.3: + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} dependencies: @@ -1003,18 +1043,23 @@ packages: which: 2.0.2 dev: true - /crypto-random-string/2.0.0: + /crypto-random-string@2.0.0: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} dev: true - /debug/2.6.9: + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.0.0 dev: true - /debug/4.3.2: + /debug@4.3.2: resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} engines: {node: '>=6.0'} peerDependencies: @@ -1026,7 +1071,7 @@ packages: ms: 2.1.2 dev: false - /debug/4.3.4: + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} peerDependencies: @@ -1037,27 +1082,27 @@ packages: dependencies: ms: 2.1.2 - /decompress-response/3.3.0: + /decompress-response@3.3.0: resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} engines: {node: '>=4'} dependencies: mimic-response: 1.0.1 dev: true - /deep-equal/1.0.1: + /deep-equal@1.0.1: resolution: {integrity: sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==} dev: false - /deep-extend/0.6.0: + /deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} dev: true - /defer-to-connect/1.1.3: + /defer-to-connect@1.1.3: resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} dev: true - /define-properties/1.1.4: + /define-properties@1.1.4: resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} engines: {node: '>= 0.4'} dependencies: @@ -1066,46 +1111,46 @@ packages: dev: true optional: true - /delayed-stream/1.0.0: + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} dev: true - /delegates/1.0.0: + /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: false - /depd/1.1.2: + /depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} dev: false - /depd/2.0.0: + /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} dev: false - /destroy/1.2.0: + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dev: false - /detect-libc/2.0.1: + /detect-libc@2.0.1: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} dev: false - /detect-node/2.1.0: + /detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} dev: true optional: true - /diff/4.0.2: + /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} dev: true - /dir-compare/2.4.0: + /dir-compare@2.4.0: resolution: {integrity: sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==} hasBin: true dependencies: @@ -1115,7 +1160,7 @@ packages: minimatch: 3.0.4 dev: true - /dmg-builder/22.14.13: + /dmg-builder@22.14.13: resolution: {integrity: sha512-xNOugB6AbIRETeU2uID15sUfjdZZcKdxK8xkFnwIggsM00PJ12JxpLNPTjcRoUnfwj3WrPjilrO64vRMwNItQg==} dependencies: app-builder-lib: 22.14.13 @@ -1130,7 +1175,7 @@ packages: - supports-color dev: true - /dmg-license/1.0.11: + /dmg-license@1.0.11: resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} engines: {node: '>=8'} os: [darwin] @@ -1149,37 +1194,48 @@ packages: dev: true optional: true - /dot-prop/5.3.0: + /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} dependencies: is-obj: 2.0.0 dev: true - /dotenv-expand/5.1.0: + /dotenv-expand@5.1.0: resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} dev: true - /dotenv/9.0.2: + /dotenv@9.0.2: resolution: {integrity: sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==} engines: {node: '>=10'} dev: true - /duplexer3/0.1.4: + /dprint@0.36.1: + resolution: {integrity: sha512-F8TaWlieYtJc4ktqr0tjNwg/HN4wNPTU6rTq29MdoIKXi9gQmh7Ibqr0oyW1hsLpqBuAXjjwEYIos9plYkwENQ==} + hasBin: true + requiresBuild: true + dependencies: + https-proxy-agent: 5.0.1 + yauzl: 2.10.0 + transitivePeerDependencies: + - supports-color + dev: false + + /duplexer3@0.1.4: resolution: {integrity: sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==} dev: true - /ecdsa-sig-formatter/1.0.11: + /ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: safe-buffer: 5.2.1 dev: false - /ee-first/1.1.1: + /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false - /ejs/3.1.8: + /ejs@3.1.8: resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} engines: {node: '>=0.10.0'} hasBin: true @@ -1187,7 +1243,7 @@ packages: jake: 10.8.5 dev: true - /electron-builder/22.14.13: + /electron-builder@22.14.13: resolution: {integrity: sha512-3fgLxqF2TXVKiUPeg74O4V3l0l3j7ERLazo8sUbRkApw0+4iVAf2BJkHsHMaXiigsgCoEzK/F4/rB5rne/VAnw==} engines: {node: '>=14.0.0'} hasBin: true @@ -1208,7 +1264,7 @@ packages: - supports-color dev: true - /electron-osx-sign/0.5.0: + /electron-osx-sign@0.5.0: resolution: {integrity: sha512-icoRLHzFz/qxzDh/N4Pi2z4yVHurlsCAYQvsCSG7fCedJ4UJXBS6PoQyGH71IfcqKupcKeK7HX/NkyfG+v6vlQ==} engines: {node: '>=4.0.0'} hasBin: true @@ -1219,9 +1275,11 @@ packages: isbinaryfile: 3.0.3 minimist: 1.2.6 plist: 3.0.5 + transitivePeerDependencies: + - supports-color dev: true - /electron-publish/22.14.13: + /electron-publish@22.14.13: resolution: {integrity: sha512-0oP3QiNj3e8ewOaEpEJV/o6Zrmy2VarVvZ/bH7kyO/S/aJf9x8vQsKVWpsdmSiZ5DJEHgarFIXrnO0ZQf0P9iQ==} dependencies: '@types/fs-extra': 9.0.13 @@ -1235,7 +1293,7 @@ packages: - supports-color dev: true - /electron/11.5.0: + /electron@11.5.0: resolution: {integrity: sha512-WjNDd6lGpxyiNjE3LhnFCAk/D9GIj1rU3GSDealVShhkkkPR3Vh4q8ErXGDl1OAO/faomVa10KoFPUN/pLbNxg==} engines: {node: '>= 8.6'} hasBin: true @@ -1248,14 +1306,14 @@ packages: - supports-color dev: true - /emoji-regex/8.0.0: + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - /encodeurl/1.0.2: + /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} - /encoding/0.1.13: + /encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} requiresBuild: true dependencies: @@ -1263,51 +1321,51 @@ packages: dev: false optional: true - /end-of-stream/1.4.4: + /end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 dev: true - /env-paths/2.2.1: + /env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - /err-code/2.0.3: + /err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} dev: false optional: true - /es6-error/4.1.1: + /es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} dev: true optional: true - /escalade/3.1.1: + /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} - /escape-goat/2.1.1: + /escape-goat@2.1.1: resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} engines: {node: '>=8'} dev: true - /escape-html/1.0.3: + /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} dev: false - /escape-string-regexp/4.0.0: + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} dev: true optional: true - /esm/3.2.25: + /esm@3.2.25: resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} engines: {node: '>=6'} dev: false - /extract-zip/1.7.0: + /extract-zip@1.7.0: resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==} hasBin: true dependencies: @@ -1315,44 +1373,45 @@ packages: debug: 2.6.9 mkdirp: 0.5.6 yauzl: 2.10.0 + transitivePeerDependencies: + - supports-color dev: true - /extsprintf/1.4.1: + /extsprintf@1.4.1: resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} engines: {'0': node >=0.6.0} requiresBuild: true dev: true optional: true - /fast-deep-equal/3.1.3: + /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} requiresBuild: true dev: true - /fast-json-stable-stringify/2.1.0: + /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /fd-slicer/1.1.0: + /fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} dependencies: pend: 1.2.0 - dev: true - /filelist/1.0.4: + /filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} dependencies: minimatch: 5.1.0 dev: true - /fill-range/7.0.1: + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 dev: false - /form-data/4.0.0: + /form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} dependencies: @@ -1361,12 +1420,12 @@ packages: mime-types: 2.1.35 dev: true - /fresh/0.5.2: + /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} dev: false - /fs-extra/10.1.0: + /fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} engines: {node: '>=12'} dependencies: @@ -1375,7 +1434,7 @@ packages: universalify: 2.0.0 dev: true - /fs-extra/8.1.0: + /fs-extra@8.1.0: resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} engines: {node: '>=6 <7 || >=8'} dependencies: @@ -1384,7 +1443,7 @@ packages: universalify: 0.1.2 dev: true - /fs-extra/9.1.0: + /fs-extra@9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} dependencies: @@ -1394,17 +1453,17 @@ packages: universalify: 2.0.0 dev: true - /fs-minipass/2.1.0: + /fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} dependencies: minipass: 3.3.3 dev: false - /fs.realpath/1.0.0: + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents/2.3.2: + /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] @@ -1412,10 +1471,10 @@ packages: dev: false optional: true - /function-bind/1.1.1: + /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - /gauge/3.0.2: + /gauge@3.0.2: resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} engines: {node: '>=10'} dependencies: @@ -1430,7 +1489,7 @@ packages: wide-align: 1.1.5 dev: false - /gauge/4.0.4: + /gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: @@ -1445,44 +1504,44 @@ packages: dev: false optional: true - /get-caller-file/2.0.5: + /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} dev: true - /get-intrinsic/1.1.2: + /get-intrinsic@1.1.2: resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==} dependencies: function-bind: 1.1.1 has: 1.0.3 has-symbols: 1.0.3 - /get-stream/4.1.0: + /get-stream@4.1.0: resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} engines: {node: '>=6'} dependencies: pump: 3.0.0 dev: true - /get-stream/5.2.0: + /get-stream@5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} dependencies: pump: 3.0.0 dev: true - /getopts/2.2.5: + /getopts@2.2.5: resolution: {integrity: sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==} dev: false - /glob-parent/5.1.2: + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 dev: false - /glob/7.2.3: + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: fs.realpath: 1.0.0 @@ -1492,7 +1551,7 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 - /global-agent/3.0.0: + /global-agent@3.0.0: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} requiresBuild: true @@ -1506,14 +1565,14 @@ packages: dev: true optional: true - /global-dirs/3.0.0: + /global-dirs@3.0.0: resolution: {integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==} engines: {node: '>=10'} dependencies: ini: 2.0.0 dev: true - /global-tunnel-ng/2.7.1: + /global-tunnel-ng@2.7.1: resolution: {integrity: sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==} engines: {node: '>=0.10'} requiresBuild: true @@ -1525,7 +1584,7 @@ packages: dev: true optional: true - /globalthis/1.0.3: + /globalthis@1.0.3: resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} engines: {node: '>= 0.4'} dependencies: @@ -1533,12 +1592,14 @@ packages: dev: true optional: true - /got/9.6.0: + /got@9.6.0: resolution: {integrity: sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==} engines: {node: '>=8.6'} dependencies: '@sindresorhus/is': 0.14.0 '@szmarczak/http-timer': 1.1.2 + '@types/keyv': 3.1.4 + '@types/responselike': 1.0.0 cacheable-request: 6.1.0 decompress-response: 3.3.0 duplexer3: 0.1.4 @@ -1550,59 +1611,59 @@ packages: url-parse-lax: 3.0.0 dev: true - /graceful-fs/4.2.10: + /graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - /graceful-readlink/1.0.1: + /graceful-readlink@1.0.1: resolution: {integrity: sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==} dev: true - /has-flag/4.0.0: + /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} dev: true - /has-property-descriptors/1.0.0: + /has-property-descriptors@1.0.0: resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} dependencies: get-intrinsic: 1.1.2 dev: true optional: true - /has-symbols/1.0.3: + /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} - /has-tostringtag/1.0.0: + /has-tostringtag@1.0.0: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 dev: false - /has-unicode/2.0.1: + /has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} dev: false - /has-yarn/2.1.0: + /has-yarn@2.1.0: resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} engines: {node: '>=8'} dev: true - /has/1.0.3: + /has@1.0.3: resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} engines: {node: '>= 0.4.0'} dependencies: function-bind: 1.1.1 - /hosted-git-info/4.1.0: + /hosted-git-info@4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} dependencies: lru-cache: 6.0.0 dev: true - /http-assert/1.5.0: + /http-assert@1.5.0: resolution: {integrity: sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==} engines: {node: '>= 0.8'} dependencies: @@ -1610,10 +1671,10 @@ packages: http-errors: 1.8.1 dev: false - /http-cache-semantics/4.1.0: + /http-cache-semantics@4.1.0: resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} - /http-errors/1.8.1: + /http-errors@1.8.1: resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} engines: {node: '>= 0.6'} dependencies: @@ -1624,7 +1685,7 @@ packages: toidentifier: 1.0.1 dev: false - /http-errors/2.0.0: + /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} dependencies: @@ -1635,7 +1696,7 @@ packages: toidentifier: 1.0.1 dev: false - /http-proxy-agent/4.0.1: + /http-proxy-agent@4.0.1: resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} engines: {node: '>= 6'} dependencies: @@ -1647,7 +1708,7 @@ packages: dev: false optional: true - /http-proxy-agent/5.0.0: + /http-proxy-agent@5.0.0: resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} engines: {node: '>= 6'} dependencies: @@ -1658,7 +1719,7 @@ packages: - supports-color dev: true - /https-proxy-agent/5.0.1: + /https-proxy-agent@5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} dependencies: @@ -1667,14 +1728,14 @@ packages: transitivePeerDependencies: - supports-color - /humanize-ms/1.2.1: + /humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} dependencies: ms: 2.1.3 dev: false optional: true - /iconv-corefoundation/1.1.7: + /iconv-corefoundation@1.1.7: resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} engines: {node: ^8.11.2 || >=10} os: [darwin] @@ -1685,129 +1746,129 @@ packages: dev: true optional: true - /iconv-lite/0.4.24: + /iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 dev: false - /iconv-lite/0.6.3: + /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - /ieee754/1.2.1: + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} requiresBuild: true dev: true optional: true - /import-lazy/2.1.0: + /import-lazy@2.1.0: resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} engines: {node: '>=4'} dev: true - /imurmurhash/0.1.4: + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - /indent-string/4.0.0: + /indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} dev: false optional: true - /infer-owner/1.0.4: + /infer-owner@1.0.4: resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} dev: false optional: true - /inflation/2.0.0: + /inflation@2.0.0: resolution: {integrity: sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==} engines: {node: '>= 0.8.0'} dev: false - /inflight/1.0.6: + /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: once: 1.4.0 wrappy: 1.0.2 - /inherits/2.0.4: + /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - /ini/1.3.8: + /ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} dev: true - /ini/2.0.0: + /ini@2.0.0: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} dev: true - /interpret/2.2.0: + /interpret@2.2.0: resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} engines: {node: '>= 0.10'} dev: false - /ip/1.1.8: + /ip@1.1.8: resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} dev: false optional: true - /is-binary-path/2.1.0: + /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 dev: false - /is-ci/2.0.0: + /is-ci@2.0.0: resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} hasBin: true dependencies: ci-info: 2.0.0 dev: true - /is-ci/3.0.1: + /is-ci@3.0.1: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true dependencies: ci-info: 3.3.2 dev: true - /is-core-module/2.9.0: + /is-core-module@2.9.0: resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==} dependencies: has: 1.0.3 dev: false - /is-extglob/2.1.1: + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} dev: false - /is-fullwidth-code-point/3.0.0: + /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - /is-generator-function/1.0.10: + /is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} dependencies: has-tostringtag: 1.0.0 dev: false - /is-glob/4.0.3: + /is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 dev: false - /is-installed-globally/0.4.0: + /is-installed-globally@0.4.0: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} dependencies: @@ -1815,59 +1876,59 @@ packages: is-path-inside: 3.0.3 dev: true - /is-lambda/1.0.1: + /is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} dev: false optional: true - /is-npm/5.0.0: + /is-npm@5.0.0: resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==} engines: {node: '>=10'} dev: true - /is-number/7.0.0: + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} dev: false - /is-obj/2.0.0: + /is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} dev: true - /is-path-inside/3.0.3: + /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} dev: true - /is-typedarray/1.0.0: + /is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} dev: true - /is-yarn-global/0.3.0: + /is-yarn-global@0.3.0: resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} dev: true - /isarray/1.0.0: + /isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} dev: true - /isbinaryfile/3.0.3: + /isbinaryfile@3.0.3: resolution: {integrity: sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==} engines: {node: '>=0.6.0'} dependencies: buffer-alloc: 1.2.0 dev: true - /isbinaryfile/4.0.10: + /isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} dev: true - /isexe/2.0.0: + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - /jake/10.8.5: + /jake@10.8.5: resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} engines: {node: '>=10'} hasBin: true @@ -1878,46 +1939,46 @@ packages: minimatch: 3.1.2 dev: true - /js-yaml/4.1.0: + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true dependencies: argparse: 2.0.1 dev: true - /json-buffer/3.0.0: + /json-buffer@3.0.0: resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} dev: true - /json-schema-traverse/0.4.1: + /json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} requiresBuild: true dev: true - /json-stable-stringify/1.0.1: + /json-stable-stringify@1.0.1: resolution: {integrity: sha512-i/J297TW6xyj7sDFa7AmBPkQvLIxWr2kKPWI26tXydnZrzVAocNqn5DMNT1Mzk0vit1V5UkRM7C1KdVNp7Lmcg==} dependencies: jsonify: 0.0.0 dev: true - /json-stringify-safe/5.0.1: + /json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} dev: true optional: true - /json5/2.2.1: + /json5@2.2.1: resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} engines: {node: '>=6'} hasBin: true dev: true - /jsonfile/4.0.0: + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: graceful-fs: 4.2.10 dev: true - /jsonfile/6.1.0: + /jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: universalify: 2.0.0 @@ -1925,15 +1986,15 @@ packages: graceful-fs: 4.2.10 dev: true - /jsonify/0.0.0: + /jsonify@0.0.0: resolution: {integrity: sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA==} dev: true - /jsonschema/1.4.1: + /jsonschema@1.4.1: resolution: {integrity: sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==} dev: false - /jsonwebtoken/8.5.1: + /jsonwebtoken@8.5.1: resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} engines: {node: '>=4', npm: '>=1.4.28'} dependencies: @@ -1949,7 +2010,7 @@ packages: semver: 5.7.1 dev: false - /jwa/1.4.1: + /jwa@1.4.1: resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} dependencies: buffer-equal-constant-time: 1.0.1 @@ -1957,27 +2018,27 @@ packages: safe-buffer: 5.2.1 dev: false - /jws/3.2.2: + /jws@3.2.2: resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} dependencies: jwa: 1.4.1 safe-buffer: 5.2.1 dev: false - /keygrip/1.1.0: + /keygrip@1.1.0: resolution: {integrity: sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==} engines: {node: '>= 0.6'} dependencies: tsscmp: 1.0.6 dev: false - /keyv/3.1.0: + /keyv@3.1.0: resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} dependencies: json-buffer: 3.0.0 dev: true - /knex/0.95.15_sqlite3@5.0.8: + /knex@0.95.15(sqlite3@5.0.8): resolution: {integrity: sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w==} engines: {node: '>=10'} hasBin: true @@ -2020,7 +2081,7 @@ packages: - supports-color dev: false - /koa-bodyparser/4.3.0: + /koa-bodyparser@4.3.0: resolution: {integrity: sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==} engines: {node: '>=8.0.0'} dependencies: @@ -2028,11 +2089,11 @@ packages: copy-to: 2.0.1 dev: false - /koa-compose/4.1.0: + /koa-compose@4.1.0: resolution: {integrity: sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==} dev: false - /koa-convert/2.0.0: + /koa-convert@2.0.0: resolution: {integrity: sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==} engines: {node: '>= 10'} dependencies: @@ -2040,7 +2101,7 @@ packages: koa-compose: 4.1.0 dev: false - /koa-router/10.1.1: + /koa-router@10.1.1: resolution: {integrity: sha512-z/OzxVjf5NyuNO3t9nJpx7e1oR3FSBAauiwXtMQu4ppcnuNZzTaQ4p21P8A6r2Es8uJJM339oc4oVW+qX7SqnQ==} engines: {node: '>= 8.0.0'} dependencies: @@ -2053,7 +2114,7 @@ packages: - supports-color dev: false - /koa/2.13.4: + /koa@2.13.4: resolution: {integrity: sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==} engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} dependencies: @@ -2084,75 +2145,75 @@ packages: - supports-color dev: false - /latest-version/5.1.0: + /latest-version@5.1.0: resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} engines: {node: '>=8'} dependencies: package-json: 6.5.0 dev: true - /lazy-val/1.0.5: + /lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} dev: true - /lodash.includes/4.3.0: + /lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} dev: false - /lodash.isboolean/3.0.3: + /lodash.isboolean@3.0.3: resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} dev: false - /lodash.isinteger/4.0.4: + /lodash.isinteger@4.0.4: resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} dev: false - /lodash.isnumber/3.0.3: + /lodash.isnumber@3.0.3: resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} dev: false - /lodash.isplainobject/4.0.6: + /lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} dev: false - /lodash.isstring/4.0.1: + /lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} dev: false - /lodash.once/4.1.1: + /lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} dev: false - /lodash/4.17.21: + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - /lowercase-keys/1.0.1: + /lowercase-keys@1.0.1: resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} engines: {node: '>=0.10.0'} dev: true - /lowercase-keys/2.0.0: + /lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} dev: true - /lru-cache/6.0.0: + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} dependencies: yallist: 4.0.0 - /make-dir/3.1.0: + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} dependencies: semver: 6.3.0 - /make-error/1.3.6: + /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true - /make-fetch-happen/9.1.0: + /make-fetch-happen@9.1.0: resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} engines: {node: '>= 10'} dependencies: @@ -2173,11 +2234,12 @@ packages: socks-proxy-agent: 6.2.1 ssri: 8.0.1 transitivePeerDependencies: + - bluebird - supports-color dev: false optional: true - /matcher/3.0.0: + /matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} dependencies: @@ -2185,60 +2247,60 @@ packages: dev: true optional: true - /media-typer/0.3.0: + /media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} dev: false - /methods/1.1.2: + /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} dev: false - /mime-db/1.52.0: + /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} - /mime-types/2.1.35: + /mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - /mime/2.6.0: + /mime@2.6.0: resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} engines: {node: '>=4.0.0'} hasBin: true dev: true - /mimic-response/1.0.1: + /mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} dev: true - /minimatch/3.0.4: + /minimatch@3.0.4: resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} dependencies: brace-expansion: 1.1.11 dev: true - /minimatch/3.1.2: + /minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 - /minimatch/5.1.0: + /minimatch@5.1.0: resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==} engines: {node: '>=10'} dependencies: brace-expansion: 2.0.1 dev: true - /minimist/1.2.6: + /minimist@1.2.6: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} dev: true - /minipass-collect/1.0.2: + /minipass-collect@1.0.2: resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} engines: {node: '>= 8'} dependencies: @@ -2246,7 +2308,7 @@ packages: dev: false optional: true - /minipass-fetch/1.4.1: + /minipass-fetch@1.4.1: resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} engines: {node: '>=8'} dependencies: @@ -2258,7 +2320,7 @@ packages: dev: false optional: true - /minipass-flush/1.0.5: + /minipass-flush@1.0.5: resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} engines: {node: '>= 8'} dependencies: @@ -2266,7 +2328,7 @@ packages: dev: false optional: true - /minipass-pipeline/1.2.4: + /minipass-pipeline@1.2.4: resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} engines: {node: '>=8'} dependencies: @@ -2274,7 +2336,7 @@ packages: dev: false optional: true - /minipass-sized/1.0.3: + /minipass-sized@1.0.3: resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} engines: {node: '>=8'} dependencies: @@ -2282,14 +2344,14 @@ packages: dev: false optional: true - /minipass/3.3.3: + /minipass@3.3.3: resolution: {integrity: sha512-N0BOsdFAlNRfmwMhjAsLVWOk7Ljmeb39iqFlsV1At+jqRhSUP9yeof8FyJu4imaJiSUp8vQebWD/guZwGQC8iA==} engines: {node: '>=8'} dependencies: yallist: 4.0.0 dev: false - /minizlib/2.1.2: + /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} dependencies: @@ -2297,54 +2359,54 @@ packages: yallist: 4.0.0 dev: false - /mkdirp/0.5.6: + /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true dependencies: minimist: 1.2.6 dev: true - /mkdirp/1.0.4: + /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true dev: false - /ms/2.0.0: + /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: true - /ms/2.1.2: + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - /ms/2.1.3: + /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false - /natural-orderby/2.0.3: + /natural-orderby@2.0.3: resolution: {integrity: sha512-p7KTHxU0CUrcOXe62Zfrb5Z13nLvPhSWR/so3kFulUQU0sgUll2Z0LwpsLN351eOOD+hRGu/F1g+6xDfPeD++Q==} dev: false - /negotiator/0.6.3: + /negotiator@0.6.3: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} dev: false - /node-addon-api/1.7.2: + /node-addon-api@1.7.2: resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} requiresBuild: true dev: true optional: true - /node-addon-api/3.2.1: + /node-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} dev: false - /node-addon-api/4.3.0: + /node-addon-api@4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} dev: false - /node-fetch/2.6.7: + /node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} peerDependencies: @@ -2356,7 +2418,7 @@ packages: whatwg-url: 5.0.0 dev: false - /node-gyp/8.4.1: + /node-gyp@8.4.1: resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} engines: {node: '>= 10.12.0'} hasBin: true @@ -2373,16 +2435,17 @@ packages: tar: 6.1.11 which: 2.0.2 transitivePeerDependencies: + - bluebird - supports-color dev: false optional: true - /node-stream-zip/1.15.0: + /node-stream-zip@1.15.0: resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} engines: {node: '>=0.12.0'} dev: false - /nopt/5.0.0: + /nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} hasBin: true @@ -2390,17 +2453,17 @@ packages: abbrev: 1.1.1 dev: false - /normalize-path/3.0.0: + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} dev: false - /normalize-url/4.5.1: + /normalize-url@4.5.1: resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} engines: {node: '>=8'} dev: true - /npm-conf/1.1.3: + /npm-conf@1.1.3: resolution: {integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==} engines: {node: '>=4'} dependencies: @@ -2409,7 +2472,7 @@ packages: dev: true optional: true - /npmlog/5.0.1: + /npmlog@5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} dependencies: are-we-there-yet: 2.0.0 @@ -2418,7 +2481,7 @@ packages: set-blocking: 2.0.0 dev: false - /npmlog/6.0.2: + /npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: @@ -2429,43 +2492,43 @@ packages: dev: false optional: true - /object-assign/4.1.1: + /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} dev: false - /object-inspect/1.12.2: + /object-inspect@1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} dev: false - /object-keys/1.1.1: + /object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} dev: true optional: true - /on-finished/2.4.1: + /on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} dependencies: ee-first: 1.1.1 dev: false - /once/1.4.0: + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 - /only/0.0.2: + /only@0.0.2: resolution: {integrity: sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==} dev: false - /p-cancelable/1.1.0: + /p-cancelable@1.1.0: resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} engines: {node: '>=6'} dev: true - /p-map/4.0.0: + /p-map@4.0.0: resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} engines: {node: '>=10'} dependencies: @@ -2473,7 +2536,7 @@ packages: dev: false optional: true - /package-json/6.5.0: + /package-json@6.5.0: resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} engines: {node: '>=8'} dependencies: @@ -2483,48 +2546,47 @@ packages: semver: 6.3.0 dev: true - /parseurl/1.3.3: + /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} dev: false - /path-is-absolute/1.0.1: + /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - /path-key/3.1.1: + /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} dev: true - /path-parse/1.0.7: + /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: false - /path-to-regexp/6.2.1: + /path-to-regexp@6.2.1: resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} dev: false - /pend/1.2.0: + /pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - dev: true - /pg-connection-string/2.5.0: + /pg-connection-string@2.5.0: resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==} dev: false - /picomatch/2.3.1: + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: false - /pify/3.0.0: + /pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} dev: true optional: true - /plist/3.0.5: + /plist@3.0.5: resolution: {integrity: sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==} engines: {node: '>=6'} dependencies: @@ -2532,26 +2594,31 @@ packages: xmlbuilder: 9.0.7 dev: true - /prepend-http/2.0.0: + /prepend-http@2.0.0: resolution: {integrity: sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==} engines: {node: '>=4'} dev: true - /process-nextick-args/2.0.1: + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true - /progress/2.0.3: + /progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} dev: true - /promise-inflight/1.0.1: + /promise-inflight@1.0.1: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true dev: false optional: true - /promise-retry/2.0.1: + /promise-retry@2.0.1: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} dependencies: @@ -2560,39 +2627,39 @@ packages: dev: false optional: true - /proto-list/1.2.4: + /proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} dev: true optional: true - /pump/3.0.0: + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: end-of-stream: 1.4.4 once: 1.4.0 dev: true - /punycode/2.1.1: + /punycode@2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} engines: {node: '>=6'} requiresBuild: true dev: true - /pupa/2.1.1: + /pupa@2.1.1: resolution: {integrity: sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==} engines: {node: '>=8'} dependencies: escape-goat: 2.1.1 dev: true - /qs/6.10.5: + /qs@6.10.5: resolution: {integrity: sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==} engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 dev: false - /raw-body/2.5.1: + /raw-body@2.5.1: resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} engines: {node: '>= 0.8'} dependencies: @@ -2602,7 +2669,7 @@ packages: unpipe: 1.0.0 dev: false - /rc/1.2.8: + /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true dependencies: @@ -2612,7 +2679,7 @@ packages: strip-json-comments: 2.0.1 dev: true - /read-config-file/6.2.0: + /read-config-file@6.2.0: resolution: {integrity: sha512-gx7Pgr5I56JtYz+WuqEbQHj/xWo+5Vwua2jhb1VwM4Wid5PqYmZ4i00ZB0YEGIfkVBsCv9UrjgyqCiQfS/Oosg==} engines: {node: '>=12.0.0'} dependencies: @@ -2623,7 +2690,7 @@ packages: lazy-val: 1.0.5 dev: true - /readable-stream/2.3.7: + /readable-stream@2.3.7: resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} dependencies: core-util-is: 1.0.3 @@ -2635,7 +2702,7 @@ packages: util-deprecate: 1.0.2 dev: true - /readable-stream/3.6.0: + /readable-stream@3.6.0: resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} engines: {node: '>= 6'} dependencies: @@ -2644,45 +2711,45 @@ packages: util-deprecate: 1.0.2 dev: false - /readdirp/3.6.0: + /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 dev: false - /rechoir/0.7.0: + /rechoir@0.7.0: resolution: {integrity: sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==} engines: {node: '>= 0.10'} dependencies: resolve: 1.22.1 dev: false - /registry-auth-token/4.2.2: + /registry-auth-token@4.2.2: resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==} engines: {node: '>=6.0.0'} dependencies: rc: 1.2.8 dev: true - /registry-url/5.1.0: + /registry-url@5.1.0: resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} engines: {node: '>=8'} dependencies: rc: 1.2.8 dev: true - /require-directory/2.1.1: + /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} dev: true - /resolve-from/5.0.0: + /resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} dev: false - /resolve/1.22.1: + /resolve@1.22.1: resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} hasBin: true dependencies: @@ -2691,25 +2758,25 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: false - /responselike/1.0.2: + /responselike@1.0.2: resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} dependencies: lowercase-keys: 1.0.1 dev: true - /retry/0.12.0: + /retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} dev: false optional: true - /rimraf/3.0.2: + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true dependencies: glob: 7.2.3 - /roarr/2.15.4: + /roarr@2.15.4: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} dependencies: @@ -2722,56 +2789,56 @@ packages: dev: true optional: true - /safe-buffer/5.1.2: + /safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} dev: true - /safe-buffer/5.2.1: + /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false - /safer-buffer/2.1.2: + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - /sanitize-filename/1.6.3: + /sanitize-filename@1.6.3: resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} dependencies: truncate-utf8-bytes: 1.0.2 dev: true - /sax/1.2.4: + /sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} dev: true - /semver-compare/1.0.0: + /semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} dev: true optional: true - /semver-diff/3.1.1: + /semver-diff@3.1.1: resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} engines: {node: '>=8'} dependencies: semver: 6.3.0 dev: true - /semver/5.7.1: + /semver@5.7.1: resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true dev: false - /semver/6.3.0: + /semver@6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true - /semver/7.3.7: + /semver@7.3.7: resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} engines: {node: '>=10'} hasBin: true dependencies: lru-cache: 6.0.0 - /serialize-error/7.0.1: + /serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} dependencies: @@ -2779,27 +2846,27 @@ packages: dev: true optional: true - /set-blocking/2.0.0: + /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: false - /setprototypeof/1.2.0: + /setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} dev: false - /shebang-command/2.0.0: + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 dev: true - /shebang-regex/3.0.0: + /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} dev: true - /side-channel/1.0.4: + /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: call-bind: 1.0.2 @@ -2807,10 +2874,10 @@ packages: object-inspect: 1.12.2 dev: false - /signal-exit/3.0.7: + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - /slice-ansi/3.0.0: + /slice-ansi@3.0.0: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} requiresBuild: true @@ -2821,12 +2888,12 @@ packages: dev: true optional: true - /smart-buffer/4.2.0: + /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} optional: true - /socks-proxy-agent/6.2.1: + /socks-proxy-agent@6.2.1: resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} engines: {node: '>= 10'} dependencies: @@ -2838,7 +2905,7 @@ packages: dev: false optional: true - /socks/2.6.2: + /socks@2.6.2: resolution: {integrity: sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==} engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} dependencies: @@ -2847,24 +2914,24 @@ packages: dev: false optional: true - /source-map-support/0.5.21: + /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 dev: true - /source-map/0.6.1: + /source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} dev: true - /sprintf-js/1.1.2: + /sprintf-js@1.1.2: resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} dev: true optional: true - /sqlite3/5.0.8: + /sqlite3@5.0.8: resolution: {integrity: sha512-f2ACsbSyb2D1qFFcqIXPfFscLtPVOWJr5GmUzYxf4W+0qelu5MWrR+FAQE1d5IUArEltBrzSDxDORG8P/IkqyQ==} requiresBuild: true peerDependenciesMeta: @@ -2877,11 +2944,12 @@ packages: optionalDependencies: node-gyp: 8.4.1 transitivePeerDependencies: + - bluebird - encoding - supports-color dev: false - /ssri/8.0.1: + /ssri@8.0.1: resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} engines: {node: '>= 8'} dependencies: @@ -2889,22 +2957,22 @@ packages: dev: false optional: true - /stat-mode/1.0.0: + /stat-mode@1.0.0: resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} engines: {node: '>= 6'} dev: true - /statuses/1.5.0: + /statuses@1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} dev: false - /statuses/2.0.1: + /statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} dev: false - /string-width/4.2.3: + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} dependencies: @@ -2912,30 +2980,30 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - /string_decoder/1.1.1: + /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 dev: true - /string_decoder/1.3.0: + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 dev: false - /strip-ansi/6.0.1: + /strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - /strip-json-comments/2.0.1: + /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} dev: true - /sumchecker/3.0.1: + /sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} dependencies: @@ -2944,19 +3012,19 @@ packages: - supports-color dev: true - /supports-color/7.2.0: + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} dependencies: has-flag: 4.0.0 dev: true - /supports-preserve-symlinks-flag/1.0.0: + /supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} dev: false - /tar/6.1.11: + /tar@6.1.11: resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} engines: {node: '>= 10'} dependencies: @@ -2968,70 +3036,70 @@ packages: yallist: 4.0.0 dev: false - /tarn/3.0.2: + /tarn@3.0.2: resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} engines: {node: '>=8.0.0'} dev: false - /temp-file/3.4.0: + /temp-file@3.4.0: resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} dependencies: async-exit-hook: 2.0.1 fs-extra: 10.1.0 dev: true - /tildify/2.0.0: + /tildify@2.0.0: resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==} engines: {node: '>=8'} dev: false - /tiny-async-pool/1.3.0: + /tiny-async-pool@1.3.0: resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==} dependencies: semver: 5.7.1 dev: false - /tmp-promise/3.0.3: + /tmp-promise@3.0.3: resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} dependencies: tmp: 0.2.1 dev: true - /tmp/0.2.1: + /tmp@0.2.1: resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} engines: {node: '>=8.17.0'} dependencies: rimraf: 3.0.2 dev: true - /to-readable-stream/1.0.0: + /to-readable-stream@1.0.0: resolution: {integrity: sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==} engines: {node: '>=6'} dev: true - /to-regex-range/5.0.1: + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 dev: false - /toidentifier/1.0.1: + /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} dev: false - /tr46/0.0.3: + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false - /truncate-utf8-bytes/1.0.2: + /truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} dependencies: utf8-byte-length: 1.0.4 dev: true - /ts-json-schema-generator/0.82.0: + /ts-json-schema-generator@0.82.0: resolution: {integrity: sha512-g5/h3jmN7DqYTOx7OhTvWS638CJWzyoqlsKwe2eksvK8mnFHTnOZGvZITmPkRDPFUFwSUMo0J+Vm3A+Y8EDsEg==} engines: {node: '>=10.0.0'} hasBin: true @@ -3044,7 +3112,7 @@ packages: typescript: 4.1.6 dev: true - /ts-node/9.1.1_typescript@4.7.4: + /ts-node@9.1.1(typescript@4.7.4): resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==} engines: {node: '>=10.0.0'} hasBin: true @@ -3060,29 +3128,29 @@ packages: yn: 3.1.1 dev: true - /tsscmp/1.0.6: + /tsscmp@1.0.6: resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} engines: {node: '>=0.6.x'} dev: false - /tunnel/0.0.6: + /tunnel@0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} dev: true optional: true - /type-fest/0.13.1: + /type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} dev: true optional: true - /type-fest/0.20.2: + /type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} dev: true - /type-is/1.6.18: + /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} dependencies: @@ -3090,65 +3158,65 @@ packages: mime-types: 2.1.35 dev: false - /typedarray-to-buffer/3.1.5: + /typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} dependencies: is-typedarray: 1.0.0 dev: true - /typedarray/0.0.6: + /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} dev: true - /typescript/4.1.6: + /typescript@4.1.6: resolution: {integrity: sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow==} engines: {node: '>=4.2.0'} hasBin: true dev: true - /typescript/4.7.4: + /typescript@4.7.4: resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} engines: {node: '>=4.2.0'} hasBin: true dev: true - /unique-filename/1.1.1: + /unique-filename@1.1.1: resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} dependencies: unique-slug: 2.0.2 dev: false optional: true - /unique-slug/2.0.2: + /unique-slug@2.0.2: resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} dependencies: imurmurhash: 0.1.4 dev: false optional: true - /unique-string/2.0.0: + /unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} dependencies: crypto-random-string: 2.0.0 dev: true - /universalify/0.1.2: + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} dev: true - /universalify/2.0.0: + /universalify@2.0.0: resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} engines: {node: '>= 10.0.0'} dev: true - /unpipe/1.0.0: + /unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} dev: false - /update-notifier/5.1.0: + /update-notifier@5.1.0: resolution: {integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==} engines: {node: '>=10'} dependencies: @@ -3168,33 +3236,33 @@ packages: xdg-basedir: 4.0.0 dev: true - /uri-js/4.4.1: + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} requiresBuild: true dependencies: punycode: 2.1.1 dev: true - /url-parse-lax/3.0.0: + /url-parse-lax@3.0.0: resolution: {integrity: sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==} engines: {node: '>=4'} dependencies: prepend-http: 2.0.0 dev: true - /utf8-byte-length/1.0.4: + /utf8-byte-length@1.0.4: resolution: {integrity: sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA==} dev: true - /util-deprecate/1.0.2: + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - /vary/1.1.2: + /vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} dev: false - /verror/1.10.1: + /verror@1.10.1: resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} engines: {node: '>=0.6.0'} requiresBuild: true @@ -3205,38 +3273,38 @@ packages: dev: true optional: true - /webidl-conversions/3.0.1: + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false - /whatwg-url/5.0.0: + /whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 dev: false - /which/2.0.2: + /which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true dependencies: isexe: 2.0.0 - /wide-align/1.1.5: + /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: string-width: 4.2.3 dev: false - /widest-line/3.1.0: + /widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} dependencies: string-width: 4.2.3 dev: true - /wrap-ansi/7.0.0: + /wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} dependencies: @@ -3245,10 +3313,10 @@ packages: strip-ansi: 6.0.1 dev: true - /wrappy/1.0.2: + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - /write-file-atomic/3.0.3: + /write-file-atomic@3.0.3: resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} dependencies: imurmurhash: 0.1.4 @@ -3257,38 +3325,38 @@ packages: typedarray-to-buffer: 3.1.5 dev: true - /xdg-basedir/4.0.0: + /xdg-basedir@4.0.0: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} engines: {node: '>=8'} dev: true - /xmlbuilder/15.1.1: + /xmlbuilder@15.1.1: resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} engines: {node: '>=8.0'} requiresBuild: true dev: true optional: true - /xmlbuilder/9.0.7: + /xmlbuilder@9.0.7: resolution: {integrity: sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==} engines: {node: '>=4.0'} requiresBuild: true dev: true - /y18n/5.0.8: + /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} dev: true - /yallist/4.0.0: + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - /yargs-parser/21.0.1: + /yargs-parser@21.0.1: resolution: {integrity: sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==} engines: {node: '>=12'} dev: true - /yargs/17.5.1: + /yargs@17.5.1: resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} engines: {node: '>=12'} dependencies: @@ -3301,19 +3369,18 @@ packages: yargs-parser: 21.0.1 dev: true - /yauzl/2.10.0: + /yauzl@2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} dependencies: buffer-crc32: 0.2.13 fd-slicer: 1.1.0 - dev: true - /ylru/1.3.2: + /ylru@1.3.2: resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} engines: {node: '>= 4.0.0'} dev: false - /yn/3.1.1: + /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} dev: true diff --git a/preload.ts b/preload.ts index 553982d..05b82f8 100644 --- a/preload.ts +++ b/preload.ts @@ -1,7 +1,7 @@ -import {ipcRenderer, contextBridge} from 'electron'; +import { contextBridge, ipcRenderer } from "electron"; -contextBridge.exposeInMainWorld('electron',{ - passwordReset:async (username:string,toPw:string)=>{ - return await ipcRenderer.invoke('reset_password',username,toPw); -} -}); \ No newline at end of file +contextBridge.exposeInMainWorld("electron", { + passwordReset: async (username: string, toPw: string) => { + return await ipcRenderer.invoke("reset_password", username, toPw); + }, +}); diff --git a/src/SettingConfig.schema.json b/src/SettingConfig.schema.json index aedb7ab..0ff349b 100644 --- a/src/SettingConfig.schema.json +++ b/src/SettingConfig.schema.json @@ -1,66 +1,66 @@ { - "$schema": "http://json-schema.org/draft-07/schema#", - "$ref": "#/definitions/SettingConfig", - "definitions": { - "SettingConfig": { - "type": "object", - "properties": { - "localmode": { - "type": "boolean", - "description": "if true, server will bind on '127.0.0.1' rather than '0.0.0.0'" - }, - "guest": { - "type": "array", - "items": { - "$ref": "#/definitions/Permission" - }, - "description": "guest permission" - }, - "jwt_secretkey": { - "type": "string", - "description": "JWT secret key. if you change its value, all access tokens are invalidated." - }, - "port": { - "type": "number", - "description": "the port which running server is binding on." - }, - "mode": { - "type": "string", - "enum": [ - "development", - "production" - ] - }, - "cli": { - "type": "boolean", - "description": "if true, do not show 'electron' window and show terminal only." - }, - "forbid_remote_admin_login": { - "type": "boolean", - "description": "forbid to login admin from remote client. but, it do not invalidate access token. \r if you want to invalidate access token, change 'jwt_secretkey'." - }, - "$schema": { - "type": "string" - } - }, - "required": [ - "localmode", - "guest", - "jwt_secretkey", - "port", - "mode", - "cli", - "forbid_remote_admin_login" - ], - "additionalProperties": false + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/SettingConfig", + "definitions": { + "SettingConfig": { + "type": "object", + "properties": { + "localmode": { + "type": "boolean", + "description": "if true, server will bind on '127.0.0.1' rather than '0.0.0.0'" }, - "Permission": { - "type": "string", - "enum": [ - "ModifyTag", - "QueryContent", - "ModifyTagDesc" - ] + "guest": { + "type": "array", + "items": { + "$ref": "#/definitions/Permission" + }, + "description": "guest permission" + }, + "jwt_secretkey": { + "type": "string", + "description": "JWT secret key. if you change its value, all access tokens are invalidated." + }, + "port": { + "type": "number", + "description": "the port which running server is binding on." + }, + "mode": { + "type": "string", + "enum": [ + "development", + "production" + ] + }, + "cli": { + "type": "boolean", + "description": "if true, do not show 'electron' window and show terminal only." + }, + "forbid_remote_admin_login": { + "type": "boolean", + "description": "forbid to login admin from remote client. but, it do not invalidate access token. \r if you want to invalidate access token, change 'jwt_secretkey'." + }, + "$schema": { + "type": "string" } + }, + "required": [ + "localmode", + "guest", + "jwt_secretkey", + "port", + "mode", + "cli", + "forbid_remote_admin_login" + ], + "additionalProperties": false + }, + "Permission": { + "type": "string", + "enum": [ + "ModifyTag", + "QueryContent", + "ModifyTagDesc" + ] } -} \ No newline at end of file + } +} diff --git a/src/SettingConfig.ts b/src/SettingConfig.ts index ffc19d8..0f8b080 100644 --- a/src/SettingConfig.ts +++ b/src/SettingConfig.ts @@ -1,76 +1,76 @@ -import { randomBytes } from 'crypto'; -import { existsSync, readFileSync, writeFileSync } from 'fs'; -import { Permission } from './permission/permission'; +import { randomBytes } from "crypto"; +import { existsSync, readFileSync, writeFileSync } from "fs"; +import { Permission } from "./permission/permission"; export interface SettingConfig { /** * if true, server will bind on '127.0.0.1' rather than '0.0.0.0' */ - localmode: boolean, + localmode: boolean; /** * secure only */ - secure: boolean, + secure: boolean; /** * guest permission */ - guest: (Permission)[], + guest: (Permission)[]; /** * JWT secret key. if you change its value, all access tokens are invalidated. */ - jwt_secretkey: string, + jwt_secretkey: string; /** * the port which running server is binding on. */ - port:number, + port: number; - mode:"development"|"production", + mode: "development" | "production"; /** * if true, do not show 'electron' window and show terminal only. */ - cli:boolean, - /** forbid to login admin from remote client. but, it do not invalidate access token. - * if you want to invalidate access token, change 'jwt_secretkey'.*/ - forbid_remote_admin_login:boolean, + cli: boolean; + /** forbid to login admin from remote client. but, it do not invalidate access token. + * if you want to invalidate access token, change 'jwt_secretkey'. */ + forbid_remote_admin_login: boolean; } -const default_setting:SettingConfig = { +const default_setting: SettingConfig = { localmode: true, secure: true, - guest:[], - jwt_secretkey:"itsRandom", - port:8080, - mode:"production", - cli:false, - forbid_remote_admin_login:true, -} -let setting: null|SettingConfig = null; + guest: [], + jwt_secretkey: "itsRandom", + port: 8080, + mode: "production", + cli: false, + forbid_remote_admin_login: true, +}; +let setting: null | SettingConfig = null; -const setEmptyToDefault = (target:any,default_table:SettingConfig)=>{ +const setEmptyToDefault = (target: any, default_table: SettingConfig) => { let diff_occur = false; - for(const key in default_table){ - if(key === undefined || key in target){ + for (const key in default_table) { + if (key === undefined || key in target) { continue; } target[key] = default_table[key as keyof SettingConfig]; diff_occur = true; } return diff_occur; -} +}; -export const read_setting_from_file = ()=>{ - let ret = existsSync("settings.json") ? JSON.parse(readFileSync("settings.json",{encoding:"utf8"})) : {}; - const partial_occur = setEmptyToDefault(ret,default_setting); - if(partial_occur){ - writeFileSync("settings.json",JSON.stringify(ret)); +export const read_setting_from_file = () => { + let ret = existsSync("settings.json") ? JSON.parse(readFileSync("settings.json", { encoding: "utf8" })) : {}; + const partial_occur = setEmptyToDefault(ret, default_setting); + if (partial_occur) { + writeFileSync("settings.json", JSON.stringify(ret)); } return ret as SettingConfig; -} -export function get_setting():SettingConfig{ - if(setting === null){ +}; +export function get_setting(): SettingConfig { + if (setting === null) { setting = read_setting_from_file(); const env = process.env.NODE_ENV; - if(env !== undefined && (env != "production" && env != "development")){ + if (env !== undefined && (env != "production" && env != "development")) { throw new Error("process unknown value in NODE_ENV: must be either \"development\" or \"production\""); } setting.mode = env ?? setting.mode; diff --git a/src/client/accessor/document.ts b/src/client/accessor/document.ts index 2fcb9ad..21046b0 100644 --- a/src/client/accessor/document.ts +++ b/src/client/accessor/document.ts @@ -1,99 +1,99 @@ -import {Document, DocumentAccessor, DocumentBody, QueryListOption} from "../../model/doc"; -import {toQueryString} from './util'; +import { Document, DocumentAccessor, DocumentBody, QueryListOption } from "../../model/doc"; +import { toQueryString } from "./util"; const baseurl = "/api/doc"; export * from "../../model/doc"; -export class FetchFailError extends Error{} +export class FetchFailError extends Error {} -export class ClientDocumentAccessor implements DocumentAccessor{ +export class ClientDocumentAccessor implements DocumentAccessor { search: (search_word: string) => Promise; addList: (content_list: DocumentBody[]) => Promise; - async findByPath(basepath: string, filename?: string): Promise{ + async findByPath(basepath: string, filename?: string): Promise { throw new Error("not allowed"); - }; - async findDeleted(content_type: string): Promise{ + } + async findDeleted(content_type: string): Promise { throw new Error("not allowed"); - }; - async findList(option?: QueryListOption | undefined): Promise{ + } + async findList(option?: QueryListOption | undefined): Promise { let res = await fetch(`${baseurl}/search?${option !== undefined ? toQueryString(option) : ""}`); - if(res.status == 401) throw new FetchFailError("Unauthorized") - if(res.status !== 200) throw new FetchFailError("findList Failed"); + if (res.status == 401) throw new FetchFailError("Unauthorized"); + if (res.status !== 200) throw new FetchFailError("findList Failed"); let ret = await res.json(); return ret; } - async findById(id: number, tagload?: boolean | undefined): Promise{ + async findById(id: number, tagload?: boolean | undefined): Promise { let res = await fetch(`${baseurl}/${id}`); - if(res.status !== 200) throw new FetchFailError("findById Failed");; + if (res.status !== 200) throw new FetchFailError("findById Failed"); let ret = await res.json(); return ret; } /** * not implement */ - async findListByBasePath(basepath: string): Promise{ + async findListByBasePath(basepath: string): Promise { throw new Error("not implement"); return []; } - async update(c: Partial & { id: number; }): Promise{ - const {id,...rest} = c; - const res = await fetch(`${baseurl}/${id}`,{ + async update(c: Partial & { id: number }): Promise { + const { id, ...rest } = c; + const res = await fetch(`${baseurl}/${id}`, { method: "POST", body: JSON.stringify(rest), - headers:{ - 'content-type':"application/json" - } + headers: { + "content-type": "application/json", + }, }); const ret = await res.json(); return ret; } - async add(c: DocumentBody): Promise{ + async add(c: DocumentBody): Promise { throw new Error("not allow"); - const res = await fetch(`${baseurl}`,{ + const res = await fetch(`${baseurl}`, { method: "POST", body: JSON.stringify(c), - headers:{ - 'content-type':"application/json" - } + headers: { + "content-type": "application/json", + }, }); const ret = await res.json(); return ret; } - async del(id: number): Promise{ - const res = await fetch(`${baseurl}/${id}`,{ - method: "DELETE" + async del(id: number): Promise { + const res = await fetch(`${baseurl}/${id}`, { + method: "DELETE", }); const ret = await res.json(); return ret; } - async addTag(c: Document, tag_name: string): Promise{ - const {id,...rest} = c; - const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{ + async addTag(c: Document, tag_name: string): Promise { + const { id, ...rest } = c; + const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`, { method: "POST", body: JSON.stringify(rest), - headers:{ - 'content-type':"application/json" - } + headers: { + "content-type": "application/json", + }, }); const ret = await res.json(); return ret; } - async delTag(c: Document, tag_name: string): Promise{ - const {id,...rest} = c; - const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{ + async delTag(c: Document, tag_name: string): Promise { + const { id, ...rest } = c; + const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`, { method: "DELETE", body: JSON.stringify(rest), - headers:{ - 'content-type':"application/json" - } + headers: { + "content-type": "application/json", + }, }); const ret = await res.json(); return ret; } } -export const CDocumentAccessor = new ClientDocumentAccessor; -export const makeThumbnailUrl = (x: Document)=>{ +export const CDocumentAccessor = new ClientDocumentAccessor(); +export const makeThumbnailUrl = (x: Document) => { return `${baseurl}/${x.id}/${x.content_type}/thumbnail`; -} +}; -export default CDocumentAccessor; \ No newline at end of file +export default CDocumentAccessor; diff --git a/src/client/accessor/util.ts b/src/client/accessor/util.ts index 61c66f8..d2fa9ce 100644 --- a/src/client/accessor/util.ts +++ b/src/client/accessor/util.ts @@ -1,35 +1,32 @@ - -type Representable = string|number|boolean; +type Representable = string | number | boolean; type ToQueryStringA = { - [name:string]:Representable|Representable[]|undefined + [name: string]: Representable | Representable[] | undefined; }; -export const toQueryString = (obj:ToQueryStringA)=> { +export const toQueryString = (obj: ToQueryStringA) => { return Object.entries(obj) - .filter((e): e is [string,Representable|Representable[]] => - e[1] !== undefined) - .map(e => - e[1] instanceof Array - ? e[1].map(f=>`${e[0]}=${(f)}`).join('&') - : `${e[0]}=${(e[1])}`) - .join('&'); -} -export const QueryStringToMap = (query:string) =>{ - const keyValue = query.slice(query.indexOf("?")+1).split("&"); - const param:{[k:string]:string|string[]} = {}; - keyValue.forEach((p)=>{ - const [k,v] = p.split("="); + .filter((e): e is [string, Representable | Representable[]] => e[1] !== undefined) + .map(e => + e[1] instanceof Array + ? e[1].map(f => `${e[0]}=${(f)}`).join("&") + : `${e[0]}=${(e[1])}` + ) + .join("&"); +}; +export const QueryStringToMap = (query: string) => { + const keyValue = query.slice(query.indexOf("?") + 1).split("&"); + const param: { [k: string]: string | string[] } = {}; + keyValue.forEach((p) => { + const [k, v] = p.split("="); const pv = param[k]; - if(pv === undefined){ + if (pv === undefined) { param[k] = v; - } - else if(typeof pv === "string"){ - param[k] = [pv,v]; - } - else{ + } else if (typeof pv === "string") { + param[k] = [pv, v]; + } else { pv.push(v); } }); return param; -} \ No newline at end of file +}; diff --git a/src/client/app.tsx b/src/client/app.tsx index 6c44dce..e69a9ed 100644 --- a/src/client/app.tsx +++ b/src/client/app.tsx @@ -1,21 +1,21 @@ -import React, { createContext, useEffect, useRef, useState } from 'react'; -import ReactDom from 'react-dom'; -import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; +import { createTheme, ThemeProvider } from "@mui/material"; +import React, { createContext, useEffect, useRef, useState } from "react"; +import ReactDom from "react-dom"; +import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { - Gallery, + DifferencePage, DocumentAbout, + Gallery, LoginPage, NotFoundPage, ProfilePage, - DifferencePage, - SettingPage, ReaderPage, - TagsPage -} from './page/mod'; -import { getInitialValue, UserContext } from './state'; -import { ThemeProvider, createTheme } from '@mui/material'; + SettingPage, + TagsPage, +} from "./page/mod"; +import { getInitialValue, UserContext } from "./state"; -import './css/style.css'; +import "./css/style.css"; const theme = createTheme(); @@ -29,18 +29,20 @@ const App = () => { setUserPermission(permission); } })(); - //useEffect(()=>{}); + // useEffect(()=>{}); return ( - + - } /> + } /> } /> }> }> @@ -53,10 +55,11 @@ const App = () => { - ); + + ); }; ReactDom.render( , - document.getElementById("root") -); \ No newline at end of file + document.getElementById("root"), +); diff --git a/src/client/build.ts b/src/client/build.ts index ee9fa78..de54a07 100644 --- a/src/client/build.ts +++ b/src/client/build.ts @@ -1,25 +1,24 @@ -import esbuild from 'esbuild'; +import esbuild from "esbuild"; async function main() { try { const result = await esbuild.build({ - entryPoints: ['app.tsx'], + entryPoints: ["app.tsx"], bundle: true, - outfile: '../../dist/bundle.js', - platform: 'browser', + outfile: "../../dist/bundle.js", + platform: "browser", sourcemap: true, minify: true, - target: ['chrome100', 'firefox100'], + target: ["chrome100", "firefox100"], watch: { onRebuild: async (err, _result) => { if (err) { - console.error('watch build failed: ',err); + console.error("watch build failed: ", err); + } else { + console.log("watch build success"); } - else{ - console.log('watch build success'); - } - } - } + }, + }, }); console.log("watching..."); return result; @@ -30,4 +29,4 @@ async function main() { } main().then((res) => { -}); \ No newline at end of file +}); diff --git a/src/client/component/contentinfo.tsx b/src/client/component/contentinfo.tsx index 0c764c9..9477b66 100644 --- a/src/client/component/contentinfo.tsx +++ b/src/client/component/contentinfo.tsx @@ -1,27 +1,27 @@ -import React, { } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; -import { Document } from '../accessor/document'; +import React, {} from "react"; +import { Link as RouterLink } from "react-router-dom"; +import { Document } from "../accessor/document"; -import { Link, Paper, Theme, Box, useTheme, Typography, Grid, Button } from '@mui/material'; -import { ThumbnailContainer } from '../page/reader/reader'; -import { TagChip } from '../component/tagchip'; +import { Box, Button, Grid, Link, Paper, Theme, Typography, useTheme } from "@mui/material"; +import { TagChip } from "../component/tagchip"; +import { ThumbnailContainer } from "../page/reader/reader"; -import DocumentAccessor from '../accessor/document'; +import DocumentAccessor from "../accessor/document"; export const makeContentInfoUrl = (id: number) => `/doc/${id}`; export const makeContentReaderUrl = (id: number) => `/doc/${id}/reader`; -const useStyles = ((theme: Theme) => ({ +const useStyles = (theme: Theme) => ({ thumbnail_content: { - maxHeight: '400px', - maxWidth: 'min(400px, 100vw)', + maxHeight: "400px", + maxWidth: "min(400px, 100vw)", }, tag_list: { - display: 'flex', - justifyContent: 'flex-start', - flexWrap: 'wrap', - overflowY: 'hidden', - '& > *': { + display: "flex", + justifyContent: "flex-start", + flexWrap: "wrap", + overflowY: "hidden", + "& > *": { margin: theme.spacing(0.5), }, }, @@ -32,108 +32,133 @@ const useStyles = ((theme: Theme) => ({ padding: theme.spacing(2), }, subinfoContainer: { - display: 'grid', - gridTemplateColumns: '100px auto', - overflowY: 'hidden', - alignItems: 'baseline', + display: "grid", + gridTemplateColumns: "100px auto", + overflowY: "hidden", + alignItems: "baseline", }, short_subinfoContainer: { [theme.breakpoints.down("md")]: { - display: 'none', + display: "none", }, }, short_root: { - overflowY: 'hidden', - display: 'flex', - flexDirection: 'column', + overflowY: "hidden", + display: "flex", + flexDirection: "column", [theme.breakpoints.up("sm")]: { height: 200, - flexDirection: 'row', + flexDirection: "row", }, }, short_thumbnail_anchor: { - background: '#272733', - display: 'flex', - alignItems: 'center', - justifyContent: 'center', + background: "#272733", + display: "flex", + alignItems: "center", + justifyContent: "center", [theme.breakpoints.up("sm")]: { width: theme.spacing(25), height: theme.spacing(25), flexShrink: 0, - } + }, }, short_thumbnail_content: { - maxWidth: '100%', - maxHeight: '100%', + maxWidth: "100%", + maxHeight: "100%", }, -})) +}); export const ContentInfo = (props: { - document: Document, children?: React.ReactNode, classes?: { - root?: string, - thumbnail_anchor?: string, - thumbnail_content?: string, - tag_list?: string, - title?: string, - infoContainer?: string, - subinfoContainer?: string - }, - gallery?: string, - short?: boolean + document: Document; + children?: React.ReactNode; + classes?: { + root?: string; + thumbnail_anchor?: string; + thumbnail_content?: string; + tag_list?: string; + title?: string; + infoContainer?: string; + subinfoContainer?: string; + }; + gallery?: string; + short?: boolean; }) => { const theme = useTheme(); const document = props.document; const url = props.gallery === undefined ? makeContentReaderUrl(document.id) : makeContentInfoUrl(document.id); - return ( - - {document.deleted_at === null ? - () - : (Deleted)} - - - - {document.title} + return ( + + + {document.deleted_at === null + ? + : Deleted} - {props.short ? ({document.tags.map(x => - () - )}) : ( - ) - } + + {document.title} + + + {props.short + ? ( + + {document.tags.map(x => ( + + ))} + + ) + : ( + + + )} + + {document.deleted_at != null + && ( + + )} - {document.deleted_at != null && - - } - - ); -} -async function documentDelete(id: number){ + + ); +}; +async function documentDelete(id: number) { const t = await DocumentAccessor.del(id); - if(t){ + if (t) { alert("document deleted!"); - } - else{ + } else { alert("document already deleted."); } } function ComicDetailTag(prop: { - tags: string[];/*classes:{ + tags: string[]; /*classes:{ tag_list:string -}*/ +}*/ path?: string; createdAt?: number; deletedAt?: number; @@ -146,40 +171,54 @@ function ComicDetailTag(prop: { tagTable[kind] = tags; allTag = allTag.filter(x => !x.startsWith(kind + ":")); } - return ( - {tagKind.map(key => ( - - - {key} - - - {tagTable[key].length !== 0 ? tagTable[key].join(", ") : "N/A"} - - - ))} - { prop.path != undefined && <> - Path - - {prop.path} - - } - { prop.createdAt != undefined && <> - CreatedAt - - {new Date(prop.createdAt).toUTCString()} - - } - { prop.deletedAt != undefined && <> - DeletedAt - - {new Date(prop.deletedAt).toUTCString()} - - } - - Tags + return ( + + {tagKind.map(key => ( + + + {key} + + + {tagTable[key].length !== 0 ? tagTable[key].join(", ") : "N/A"} + + + ))} + {prop.path != undefined && ( + <> + + Path + + + {prop.path} + + + )} + {prop.createdAt != undefined && ( + <> + + CreatedAt + + + {new Date(prop.createdAt).toUTCString()} + + + )} + {prop.deletedAt != undefined && ( + <> + + DeletedAt + + + {new Date(prop.deletedAt).toUTCString()} + + + )} + + Tags + + + {allTag.map(x => )} + - - {allTag.map(x => ())} - - ); -} \ No newline at end of file + ); +} diff --git a/src/client/component/headline.tsx b/src/client/component/headline.tsx index d8fd1f3..86311e6 100644 --- a/src/client/component/headline.tsx +++ b/src/client/component/headline.tsx @@ -1,21 +1,35 @@ -import React, { useContext, useState } from 'react'; +import { AccountCircle, ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon } from "@mui/icons-material"; import { - Button, CssBaseline, Divider, IconButton, List, ListItem, Drawer, - AppBar, Toolbar, Typography, InputBase, ListItemIcon, ListItemText, Menu, MenuItem, - Hidden, Tooltip, Link, styled -} from '@mui/material'; -import { alpha, Theme, useTheme } from '@mui/material/styles'; -import { - ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon, AccountCircle -} from '@mui/icons-material'; + AppBar, + Button, + CssBaseline, + Divider, + Drawer, + Hidden, + IconButton, + InputBase, + Link, + List, + ListItem, + ListItemIcon, + ListItemText, + Menu, + MenuItem, + styled, + Toolbar, + Tooltip, + Typography, +} from "@mui/material"; +import { alpha, Theme, useTheme } from "@mui/material/styles"; +import React, { useContext, useState } from "react"; -import { Link as RouterLink, useNavigate } from 'react-router-dom'; -import { doLogout, UserContext } from '../state'; +import { Link as RouterLink, useNavigate } from "react-router-dom"; +import { doLogout, UserContext } from "../state"; const drawerWidth = 270; -const DrawerHeader = styled('div')(({ theme }) => ({ - ...theme.mixins.toolbar +const DrawerHeader = styled("div")(({ theme }) => ({ + ...theme.mixins.toolbar, })); const StyledDrawer = styled(Drawer)(({ theme }) => ({ @@ -24,58 +38,56 @@ const StyledDrawer = styled(Drawer)(({ theme }) => ({ [theme.breakpoints.up("sm")]: { width: drawerWidth, }, -} -)); -const StyledSearchBar = styled('div')(({ theme }) => ({ - position: 'relative', +})); +const StyledSearchBar = styled("div")(({ theme }) => ({ + position: "relative", borderRadius: theme.shape.borderRadius, backgroundColor: alpha(theme.palette.common.white, 0.15), - '&:hover': { + "&:hover": { backgroundColor: alpha(theme.palette.common.white, 0.25), }, marginLeft: 0, - width: '100%', - [theme.breakpoints.up('sm')]: { + width: "100%", + [theme.breakpoints.up("sm")]: { marginLeft: theme.spacing(1), - width: 'auto', + width: "auto", }, })); const StyledInputBase = styled(InputBase)(({ theme }) => ({ - color: 'inherit', - '& .MuiInputBase-input': { + color: "inherit", + "& .MuiInputBase-input": { padding: theme.spacing(1, 1, 1, 0), // vertical padding + font size from searchIcon paddingLeft: `calc(1em + ${theme.spacing(4)})`, - transition: theme.transitions.create('width'), - width: '100%', - [theme.breakpoints.up('sm')]: { - width: '12ch', - '&:focus': { - width: '20ch', + transition: theme.transitions.create("width"), + width: "100%", + [theme.breakpoints.up("sm")]: { + width: "12ch", + "&:focus": { + width: "20ch", }, }, }, })); -const StyledNav = styled('nav')(({theme}) => ({ +const StyledNav = styled("nav")(({ theme }) => ({ [theme.breakpoints.up("sm")]: { - width: theme.spacing(7) - } + width: theme.spacing(7), + }, })); const closedMixin = (theme: Theme) => ({ - overflowX: 'hidden', + overflowX: "hidden", width: `calc(${theme.spacing(7)} + 1px)`, }); - export const Headline = (prop: { - children?: React.ReactNode, + children?: React.ReactNode; classes?: { - content?: string, - toolbar?: string, - }, - menu: React.ReactNode + content?: string; + toolbar?: string; + }; + menu: React.ReactNode; }) => { const [v, setv] = useState(false); const [anchorEl, setAnchorEl] = React.useState(null); @@ -84,138 +96,179 @@ export const Headline = (prop: { const handleProfileMenuOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); const handleProfileMenuClose = () => setAnchorEl(null); const isProfileMenuOpened = Boolean(anchorEl); - const menuId = 'primary-search-account-menu'; + const menuId = "primary-search-account-menu"; const user_ctx = useContext(UserContext); const isLogin = user_ctx.username !== ""; const navigate = useNavigate(); const [search, setSearch] = useState(""); - const renderProfileMenu = ( - Profile - { handleProfileMenuClose(); await doLogout(); user_ctx.setUsername(""); }}>Logout - ); - const drawer_contents = (<> - - - {theme.direction === "ltr" ? : } - - - - {prop.menu} - ); - - return (
- - - - - + const renderProfileMenu = ( + + Profile + { + handleProfileMenuClose(); + await doLogout(); + user_ctx.setUsername(""); + }} + > + Logout + + + ); + const drawer_contents = ( + <> + + + {theme.direction === "ltr" ? : } - - Ionian - -
- -
- navSearch(search)} /> -
- setSearch(e.target.value)} - onKeyUp={(e) => { - if (e.key === "Enter") { - navSearch(search); - } +
+ + {prop.menu} + + ); + + return ( +
+ + + + + + + - - { - isLogin ? - - - - : - } - - - {renderProfileMenu} - - - - {drawer_contents} - - - - - {drawer_contents} - - - -
-
- {prop.children} -
-
); - function navSearch(search: string){ + color="inherit" + component={RouterLink} + to="/" + > + Ionian + +
+ +
+ navSearch(search)} /> +
+ setSearch(e.target.value)} + onKeyUp={(e) => { + if (e.key === "Enter") { + navSearch(search); + } + }} + value={search} + > + +
+ {isLogin + ? ( + + + + ) + : } +
+
+ {renderProfileMenu} + + + + {drawer_contents} + + + + + {drawer_contents} + + + +
+
+ {prop.children} +
+
+ ); + function navSearch(search: string) { let words = search.includes("&") ? search.split("&") : [search]; words = words.map(w => w.trim()) - .map(w => w.includes(":") ? - `allow_tag=${w}` - : `word=${encodeURIComponent(w)}`); - navigate(`/search?${words.join("&")}`); + .map(w => + w.includes(":") + ? `allow_tag=${w}` + : `word=${encodeURIComponent(w)}` + ); + navigate(`/search?${words.join("&")}`); } }; -export default Headline; \ No newline at end of file +export default Headline; diff --git a/src/client/component/loading.tsx b/src/client/component/loading.tsx index aa26c17..6aa9172 100644 --- a/src/client/component/loading.tsx +++ b/src/client/component/loading.tsx @@ -1,8 +1,10 @@ -import React from 'react'; -import {Box, CircularProgress} from '@mui/material'; +import { Box, CircularProgress } from "@mui/material"; +import React from "react"; -export const LoadingCircle = ()=>{ - return ( - - ); -} \ No newline at end of file +export const LoadingCircle = () => { + return ( + + + + ); +}; diff --git a/src/client/component/mod.ts b/src/client/component/mod.ts index ba044dd..a92d73a 100644 --- a/src/client/component/mod.ts +++ b/src/client/component/mod.ts @@ -1,5 +1,5 @@ -export * from './contentinfo'; -export * from './loading'; -export * from './tagchip'; -export * from './navlist'; -export * from './headline'; \ No newline at end of file +export * from "./contentinfo"; +export * from "./headline"; +export * from "./loading"; +export * from "./navlist"; +export * from "./tagchip"; diff --git a/src/client/component/navlist.tsx b/src/client/component/navlist.tsx index 94fca51..c7af904 100644 --- a/src/client/component/navlist.tsx +++ b/src/client/component/navlist.tsx @@ -1,43 +1,58 @@ -import React from 'react'; -import {List, ListItem, ListItemIcon, Tooltip, ListItemText, Divider} from '@mui/material'; -import {ArrowBack as ArrowBackIcon, Settings as SettingIcon, - Collections as CollectionIcon, VideoLibrary as VideoIcon, Home as HomeIcon, +import { + ArrowBack as ArrowBackIcon, + Collections as CollectionIcon, + Folder as FolderIcon, + Home as HomeIcon, List as ListIcon, - Folder as FolderIcon } from '@mui/icons-material'; -import {Link as RouterLink} from 'react-router-dom'; + Settings as SettingIcon, + VideoLibrary as VideoIcon, +} from "@mui/icons-material"; +import { Divider, List, ListItem, ListItemIcon, ListItemText, Tooltip } from "@mui/material"; +import React from "react"; +import { Link as RouterLink } from "react-router-dom"; -export const NavItem = (props:{name:string,to:string, icon:React.ReactElement})=>{ - return ( - - - {props.icon} - - - -); -} +export const NavItem = (props: { name: string; to: string; icon: React.ReactElement }) => { + return ( + + + + {props.icon} + + + + + ); +}; -export const NavList = (props: {children?:React.ReactNode})=>{ - return ( +export const NavList = (props: { children?: React.ReactNode }) => { + return ( + {props.children} - ); -} + + ); +}; -export const BackItem = (props:{to?:string})=>{ - return }/>; -} +export const BackItem = (props: { to?: string }) => { + return } />; +}; -export function CommonMenuList(props?:{url?:string}) { +export function CommonMenuList(props?: { url?: string }) { let url = props?.url ?? ""; - return ( - {url !== "" && <> } - } /> - }> - } /> - - }/> - - }> - } /> - ); -} \ No newline at end of file + return ( + + {url !== "" && ( + <> + + + )} + } /> + }> + } /> + + } /> + + }> + } /> + + ); +} diff --git a/src/client/component/tagchip.tsx b/src/client/component/tagchip.tsx index 819a63c..ec724fa 100644 --- a/src/client/component/tagchip.tsx +++ b/src/client/component/tagchip.tsx @@ -1,86 +1,95 @@ -import React from 'react'; -import {ChipTypeMap} from '@mui/material/Chip'; -import { Chip, colors } from '@mui/material'; -import { Theme, emphasize} from '@mui/material/styles'; -import {Link as RouterLink} from 'react-router-dom'; +import { Chip, colors } from "@mui/material"; +import { ChipTypeMap } from "@mui/material/Chip"; +import { emphasize, Theme } from "@mui/material/styles"; +import React from "react"; +import { Link as RouterLink } from "react-router-dom"; type TagChipStyleProp = { - color: string -} + color: string; +}; -const useTagStyles = ((theme:Theme)=>({ - root:(props:TagChipStyleProp)=>({ +const useTagStyles = (theme: Theme) => ({ + root: (props: TagChipStyleProp) => ({ color: theme.palette.getContrastText(props.color), backgroundColor: props.color, }), - clickable:(props:TagChipStyleProp)=>({ - '&:hover, &:focus':{ - backgroundColor:emphasize(props.color,0.08) - } + clickable: (props: TagChipStyleProp) => ({ + "&:hover, &:focus": { + backgroundColor: emphasize(props.color, 0.08), + }, }), deletable: { - '&:focus': { - backgroundColor: (props:TagChipStyleProp)=>emphasize(props.color, 0.2), - } - }, - outlined:{ - color: (props:TagChipStyleProp)=>props.color, - border: (props:TagChipStyleProp)=> `1px solid ${props.color}`, - '$clickable&:hover, $clickable&:focus, $deletable&:focus': { - //backgroundColor:(props:TagChipStyleProp)=> (props.color,theme.palette.action.hoverOpacity), + "&:focus": { + backgroundColor: (props: TagChipStyleProp) => emphasize(props.color, 0.2), }, }, - icon:{ - color:"inherit", + outlined: { + color: (props: TagChipStyleProp) => props.color, + border: (props: TagChipStyleProp) => `1px solid ${props.color}`, + "$clickable&:hover, $clickable&:focus, $deletable&:focus": { + // backgroundColor:(props:TagChipStyleProp)=> (props.color,theme.palette.action.hoverOpacity), + }, }, - deleteIcon:{ - //color:(props:TagChipStyleProp)=> (theme.palette.getContrastText(props.color),0.7), - "&:hover, &:active":{ - color:(props:TagChipStyleProp)=>theme.palette.getContrastText(props.color), - } - } -})); + icon: { + color: "inherit", + }, + deleteIcon: { + // color:(props:TagChipStyleProp)=> (theme.palette.getContrastText(props.color),0.7), + "&:hover, &:active": { + color: (props: TagChipStyleProp) => theme.palette.getContrastText(props.color), + }, + }, +}); -const {blue, pink} = colors; -const getTagColorName = (tagname :string):string=>{ - if(tagname.startsWith("female")){ +const { blue, pink } = colors; +const getTagColorName = (tagname: string): string => { + if (tagname.startsWith("female")) { return pink[600]; - } - else if(tagname.startsWith("male")){ + } else if (tagname.startsWith("male")) { return blue[600]; - } - else return "default"; -} + } else return "default"; +}; -type ColorChipProp = Omit & TagChipStyleProp & { - component?: React.ElementType, - to?: string -} +type ColorChipProp = Omit & TagChipStyleProp & { + component?: React.ElementType; + to?: string; +}; -export const ColorChip = (props:ColorChipProp)=>{ - const {color,...rest} = props; - //const classes = useTagStyles({color : color !== "default" ? color : "#000"}); +export const ColorChip = (props: ColorChipProp) => { + const { color, ...rest } = props; + // const classes = useTagStyles({color : color !== "default" ? color : "#000"}); return ; -} +}; -type TagChipProp = Omit & { - tagname:string -} +type TagChipProp = Omit & { + tagname: string; +}; -export const TagChip = (props:TagChipProp)=>{ - const {tagname,label,clickable,...rest} = props; - let newlabel:string|undefined = undefined; - if(typeof label === "string"){ - if(label.startsWith("female:")){ - newlabel ="♀ "+label.slice(7); - } - else if(label.startsWith("male:")){ - newlabel = "♂ "+label.slice(5); +export const TagChip = (props: TagChipProp) => { + const { tagname, label, clickable, ...rest } = props; + let newlabel: string | undefined = undefined; + if (typeof label === "string") { + if (label.startsWith("female:")) { + newlabel = "♀ " + label.slice(7); + } else if (label.startsWith("male:")) { + newlabel = "♂ " + label.slice(5); } } - const inner = clickable ? - (): - (); + const inner = clickable + ? ( + + + ) + : ( + + + ); return inner; -} \ No newline at end of file +}; diff --git a/src/client/package.json b/src/client/package.json index 844e36f..3831fab 100644 --- a/src/client/package.json +++ b/src/client/package.json @@ -1,24 +1,24 @@ { - "name": "ionian_client", - "version": "0.0.1", - "description": "client of ionian", - "scripts": { - "build:watch": "ts-node build.ts" - }, - "dependencies": { - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", - "@mui/icons-material": "^5.6.2", - "@mui/material": "^5.6.2", - "@mui/x-data-grid": "^5.12.3", - "@types/react": "^18.0.5", - "@types/react-dom": "^18.0.1", - "react": "^18.0.0", - "react-dom": "^18.0.0", - "react-router-dom": "^6.3.0" - }, - "devDependencies": { - "esbuild": "^0.14.36", - "ts-node": "^10.7.0" - } + "name": "ionian_client", + "version": "0.0.1", + "description": "client of ionian", + "scripts": { + "build:watch": "ts-node build.ts" + }, + "dependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/icons-material": "^5.6.2", + "@mui/material": "^5.6.2", + "@mui/x-data-grid": "^5.12.3", + "@types/react": "^18.0.5", + "@types/react-dom": "^18.0.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-router-dom": "^6.3.0" + }, + "devDependencies": { + "esbuild": "^0.14.36", + "ts-node": "^10.7.0" + } } diff --git a/src/client/page/404.tsx b/src/client/page/404.tsx index c87d3f8..d5a0cc8 100644 --- a/src/client/page/404.tsx +++ b/src/client/page/404.tsx @@ -1,11 +1,13 @@ -import React from 'react'; -import {Typography} from '@mui/material'; -import {ArrowBack as ArrowBackIcon} from '@mui/icons-material'; -import { Headline, BackItem, NavList, CommonMenuList } from '../component/mod'; +import { ArrowBack as ArrowBackIcon } from "@mui/icons-material"; +import { Typography } from "@mui/material"; +import React from "react"; +import { BackItem, CommonMenuList, Headline, NavList } from "../component/mod"; -export const NotFoundPage = ()=>{ +export const NotFoundPage = () => { const menu = CommonMenuList(); - return - 404 Not Found - -}; \ No newline at end of file + return ( + + 404 Not Found + + ); +}; diff --git a/src/client/page/contentinfo.tsx b/src/client/page/contentinfo.tsx index ab3ca8d..e4df29c 100644 --- a/src/client/page/contentinfo.tsx +++ b/src/client/page/contentinfo.tsx @@ -1,31 +1,31 @@ -import React, { useState, useEffect } from 'react'; -import { Route, Routes, useLocation, useParams } from 'react-router-dom'; -import DocumentAccessor, { Document } from '../accessor/document'; -import { LoadingCircle } from '../component/loading'; -import { Theme, Typography } from '@mui/material'; -import { getPresenter } from './reader/reader'; -import { CommonMenuList, ContentInfo, Headline } from '../component/mod'; -import { NotFoundPage } from './404'; +import { Theme, Typography } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import { Route, Routes, useLocation, useParams } from "react-router-dom"; +import DocumentAccessor, { Document } from "../accessor/document"; +import { LoadingCircle } from "../component/loading"; +import { CommonMenuList, ContentInfo, Headline } from "../component/mod"; +import { NotFoundPage } from "./404"; +import { getPresenter } from "./reader/reader"; export const makeContentInfoUrl = (id: number) => `/doc/${id}`; export const makeComicReaderUrl = (id: number) => `/doc/${id}/reader`; type DocumentState = { - doc: Document | undefined, - notfound: boolean, -} + doc: Document | undefined; + notfound: boolean; +}; -const styles = ((theme: Theme) => ({ +const styles = (theme: Theme) => ({ noPaddingContent: { - display: 'flex', - flexDirection: 'column', + display: "flex", + flexDirection: "column", flexGrow: 1, }, noPaddingToolbar: { - flex: '0 1 auto', + flex: "0 1 auto", ...theme.mixins.toolbar, - } -})); + }, +}); export function ReaderPage(props?: {}) { const location = useLocation(); @@ -49,28 +49,28 @@ export function ReaderPage(props?: {}) { if (isNaN(id)) { return ( - Oops. Invalid ID + Oops. Invalid ID ); - } - else if (info.notfound) { + } else if (info.notfound) { return ( - Content has been removed. + Content has been removed. - ) - } - else if (info.doc === undefined) { - return ( - - ); - } - else { + } else if (info.doc === undefined) { + return ( + + + + ); + } else { const ReaderPage = getPresenter(info.doc); - return - - + return ( + + + + ); } } @@ -95,28 +95,26 @@ export const DocumentAbout = (prop?: {}) => { if (isNaN(id)) { return ( - Oops. Invalid ID + Oops. Invalid ID ); - } - else if (info.notfound) { + } else if (info.notfound) { return ( - Content has been removed. + Content has been removed. - ) - } - else if (info.doc === undefined) { - return ( - - ); - } - else { + } else if (info.doc === undefined) { + return ( + + + + ); + } else { return ( ); } -} \ No newline at end of file +}; diff --git a/src/client/page/difference.tsx b/src/client/page/difference.tsx index ed4570d..2fbfb27 100644 --- a/src/client/page/difference.tsx +++ b/src/client/page/difference.tsx @@ -1,127 +1,141 @@ -import React, { useContext, useEffect, useState } from 'react'; +import { Box, Button, Grid, Paper, Theme, Typography } from "@mui/material"; +import { Stack } from "@mui/material"; +import React, { useContext, useEffect, useState } from "react"; import { CommonMenuList, Headline } from "../component/mod"; import { UserContext } from "../state"; -import { Box, Grid, Paper, Typography,Button, Theme } from "@mui/material"; -import {Stack} from '@mui/material'; -const useStyles = ((theme:Theme)=>({ - paper:{ +const useStyles = (theme: Theme) => ({ + paper: { padding: theme.spacing(2), }, - commitable:{ - display:'grid', + commitable: { + display: "grid", gridTemplateColumns: `100px auto`, }, - contentTitle:{ - marginLeft: theme.spacing(2) - } -})); + contentTitle: { + marginLeft: theme.spacing(2), + }, +}); type FileDifference = { - type:string, - value:{ - type:string, - path:string, - }[] -} + type: string; + value: { + type: string; + path: string; + }[]; +}; - -function TypeDifference(prop:{ - content:FileDifference, - onCommit:(v:{type:string,path:string})=>void, - onCommitAll:(type:string) => void -}){ - //const classes = useStyles(); +function TypeDifference(prop: { + content: FileDifference; + onCommit: (v: { type: string; path: string }) => void; + onCommitAll: (type: string) => void; +}) { + // const classes = useStyles(); const x = prop.content; - const [button_disable,set_disable] = useState(false); + const [button_disable, set_disable] = useState(false); - return ( - - {x.type} - - - {x.value.map(y=>( - - - {y.path} - - ))} - ); + }} + > + Commit all + + + {x.value.map(y => ( + + + {y.path} + + ))} + + ); } -export function DifferencePage(){ +export function DifferencePage() { const ctx = useContext(UserContext); - //const classes = useStyles(); - const [diffList,setDiffList] = useState< - FileDifference[] + // const classes = useStyles(); + const [diffList, setDiffList] = useState< + FileDifference[] >([]); - const doLoad = async ()=>{ - const list = await fetch('/api/diff/list'); - if(list.ok){ + const doLoad = async () => { + const list = await fetch("/api/diff/list"); + if (list.ok) { const inner = await list.json(); setDiffList(inner); - } - else{ - //setDiffList([]); + } else { + // setDiffList([]); } }; - const Commit = async(x:{type:string,path:string})=>{ - const res = await fetch('/api/diff/commit',{ - method:'POST', - body: JSON.stringify([{...x}]), - headers:{ - 'content-type':'application/json' - } + const Commit = async (x: { type: string; path: string }) => { + const res = await fetch("/api/diff/commit", { + method: "POST", + body: JSON.stringify([{ ...x }]), + headers: { + "content-type": "application/json", + }, }); const bb = await res.json(); - if(bb.ok){ + if (bb.ok) { doLoad(); - } - else{ + } else { console.error("fail to add document"); } - } - const CommitAll = async (type :string)=>{ - const res = await fetch("/api/diff/commitall",{ - method:"POST", - body: JSON.stringify({type:type}), - headers:{ - 'content-type':'application/json' - } + }; + const CommitAll = async (type: string) => { + const res = await fetch("/api/diff/commitall", { + method: "POST", + body: JSON.stringify({ type: type }), + headers: { + "content-type": "application/json", + }, }); const bb = await res.json(); - if(bb.ok){ + if (bb.ok) { doLoad(); - } - else{ + } else { console.error("fail to add document"); } - } + }; useEffect( - ()=>{ + () => { doLoad(); - const i = setInterval(doLoad,5000); - return ()=>{ + const i = setInterval(doLoad, 5000); + return () => { clearInterval(i); - } - },[] - ) + }; + }, + [], + ); const menu = CommonMenuList(); - return ( - {(ctx.username == "admin") ? (
- {(diffList.map(x=> - ))} -
) - :(Not Allowed : please login as an admin) - } - -
) -} \ No newline at end of file + return ( + + {(ctx.username == "admin") + ? ( +
+ {diffList.map(x => ( + + ))} +
+ ) + : Not Allowed : please login as an admin} +
+ ); +} diff --git a/src/client/page/gallery.tsx b/src/client/page/gallery.tsx index 97da843..361614a 100644 --- a/src/client/page/gallery.tsx +++ b/src/client/page/gallery.tsx @@ -1,15 +1,13 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { Headline, CommonMenuList, LoadingCircle, ContentInfo, NavList, NavItem, TagChip } from '../component/mod'; - -import { Box, Typography, Chip, Pagination, Button } from '@mui/material'; -import ContentAccessor, { QueryListOption, Document } from '../accessor/document'; -import { toQueryString } from '../accessor/util'; - -import { useLocation } from 'react-router-dom'; -import { QueryStringToMap } from '../accessor/util'; -import { useIsElementInViewport } from './reader/reader'; +import React, { useContext, useEffect, useState } from "react"; +import { CommonMenuList, ContentInfo, Headline, LoadingCircle, NavItem, NavList, TagChip } from "../component/mod"; +import { Box, Button, Chip, Pagination, Typography } from "@mui/material"; +import ContentAccessor, { Document, QueryListOption } from "../accessor/document"; +import { toQueryString } from "../accessor/util"; +import { useLocation } from "react-router-dom"; +import { QueryStringToMap } from "../accessor/util"; +import { useIsElementInViewport } from "./reader/reader"; export type GalleryProp = { option?: QueryListOption; @@ -17,77 +15,89 @@ export type GalleryProp = { }; type GalleryState = { documents: Document[] | undefined; -} +}; export const GalleryInfo = (props: GalleryProp) => { const [state, setState] = useState({ documents: undefined }); const [error, setError] = useState(null); const [loadAll, setLoadAll] = useState(false); - const {elementRef, isVisible: isLoadVisible} = useIsElementInViewport({}); + const { elementRef, isVisible: isLoadVisible } = useIsElementInViewport({}); - useEffect(()=>{ - if(isLoadVisible && (!loadAll) && (state.documents != undefined)){ + useEffect(() => { + if (isLoadVisible && (!loadAll) && (state.documents != undefined)) { loadMore(); } - },[isLoadVisible]); + }, [isLoadVisible]); useEffect(() => { const abortController = new AbortController(); - console.log('load first',props.option); - const load = (async () => { - try{ + console.log("load first", props.option); + const load = async () => { + try { const c = await ContentAccessor.findList(props.option); - //todo : if c is undefined, retry to fetch 3 times. and show error message. + // todo : if c is undefined, retry to fetch 3 times. and show error message. setState({ documents: c }); setLoadAll(c.length == 0); - } - catch(e){ - if(e instanceof Error){ + } catch (e) { + if (e instanceof Error) { setError(e.message); - } - else{ + } else { setError("unknown error"); } } - }); + }; load(); }, [props.diff]); const queryString = toQueryString(props.option ?? {}); if (state.documents === undefined && error == null) { - return (); - } - else { + return ; + } else { return ( - - {props.option !== undefined && props.diff !== "" && - search for - {props.option.word !== undefined && } - {props.option.content_type !== undefined && } - {props.option.allow_tag !== undefined && props.option.allow_tag.map(x => ( - ))} - } - { - state.documents && state.documents.map(x => { - return (); - }) - } - {(error && Error : {error})} - {state.documents ? state.documents.length : "null"} loaded... - + + {props.option !== undefined && props.diff !== "" && ( + + search for + {props.option.word !== undefined && ( + + )} + {props.option.content_type !== undefined && ( + + )} + {props.option.allow_tag !== undefined + && props.option.allow_tag.map(x => ( + + + ))} + + )} + {state.documents && state.documents.map(x => { + return ; + })} + {error && Error : {error}} + + {state.documents ? state.documents.length : "null"} loaded... + + ); } function loadMore() { - let option = {...props.option}; - console.log(elementRef) - if(state.documents === undefined || state.documents.length === 0){ + let option = { ...props.option }; + console.log(elementRef); + if (state.documents === undefined || state.documents.length === 0) { console.log("loadall"); setLoadAll(true); return; @@ -95,18 +105,17 @@ export const GalleryInfo = (props: GalleryProp) => { const prev_documents = state.documents; option.cursor = prev_documents[prev_documents.length - 1].id; console.log("load more", option); - const load = (async () => { + const load = async () => { const c = await ContentAccessor.findList(option); if (c.length === 0) { setLoadAll(true); - } - else{ + } else { setState({ documents: [...prev_documents, ...c] }); } - }); + }; load(); } -} +}; export const Gallery = () => { const location = useLocation(); @@ -114,8 +123,10 @@ export const Gallery = () => { const menu_list = CommonMenuList({ url: location.search }); let option: QueryListOption = query; option.allow_tag = typeof option.allow_tag === "string" ? [option.allow_tag] : option.allow_tag; - option.limit = typeof query['limit'] === "string" ? parseInt(query['limit']) : undefined; - return ( - - ) -} \ No newline at end of file + option.limit = typeof query["limit"] === "string" ? parseInt(query["limit"]) : undefined; + return ( + + + + ); +}; diff --git a/src/client/page/login.tsx b/src/client/page/login.tsx index 0fd0906..2f03e1e 100644 --- a/src/client/page/login.tsx +++ b/src/client/page/login.tsx @@ -1,66 +1,86 @@ -import React, { useContext, useState } from 'react'; -import {CommonMenuList, Headline} from '../component/mod'; -import { Button, Dialog, DialogActions, DialogContent, DialogContentText, - DialogTitle, MenuList, Paper, TextField, Typography, useTheme } from '@mui/material'; -import { UserContext } from '../state'; -import { useNavigate } from 'react-router-dom'; -import {doLogin as doSessionLogin} from '../state'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + MenuList, + Paper, + TextField, + Typography, + useTheme, +} from "@mui/material"; +import React, { useContext, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { CommonMenuList, Headline } from "../component/mod"; +import { UserContext } from "../state"; +import { doLogin as doSessionLogin } from "../state"; -export const LoginPage = ()=>{ +export const LoginPage = () => { const theme = useTheme(); - const [userLoginInfo,setUserLoginInfo]= useState({username:"",password:""}); - const [openDialog,setOpenDialog] = useState({open:false,message:""}); - const {setUsername,setPermission} = useContext(UserContext); + const [userLoginInfo, setUserLoginInfo] = useState({ username: "", password: "" }); + const [openDialog, setOpenDialog] = useState({ open: false, message: "" }); + const { setUsername, setPermission } = useContext(UserContext); const navigate = useNavigate(); - const handleDialogClose = ()=>{ - setOpenDialog({...openDialog,open:false}); - } - const doLogin = async ()=>{ - try{ + const handleDialogClose = () => { + setOpenDialog({ ...openDialog, open: false }); + }; + const doLogin = async () => { + try { const b = await doSessionLogin(userLoginInfo); - if(typeof b === "string"){ - setOpenDialog({open:true,message: b}); + if (typeof b === "string") { + setOpenDialog({ open: true, message: b }); return; } console.log(`login as ${b.username}`); setUsername(b.username); setPermission(b.permission); - } - catch(e){ - if(e instanceof Error){ + } catch (e) { + if (e instanceof Error) { console.error(e); - setOpenDialog({open:true,message:e.message}); - } - else console.error(e); + setOpenDialog({ open: true, message: e.message }); + } else console.error(e); return; } navigate("/"); - } + }; const menu = CommonMenuList(); - return - - Login -
-
- setUserLoginInfo({...userLoginInfo,username:e.target.value ?? "",})}> - {if(e.key === 'Enter') doLogin();}} - onChange={(e)=>setUserLoginInfo({...userLoginInfo,password:e.target.value ?? "",})}/> -
-
- - -
- -
- - Login Failed - - detail : {openDialog.message} - - - - - -
-} \ No newline at end of file + return ( + + + Login +
+
+ setUserLoginInfo({ ...userLoginInfo, username: e.target.value ?? "" })} + > + + { + if (e.key === "Enter") doLogin(); + }} + onChange={(e) => setUserLoginInfo({ ...userLoginInfo, password: e.target.value ?? "" })} + /> +
+
+ + +
+ +
+ + Login Failed + + detail : {openDialog.message} + + + + + +
+ ); +}; diff --git a/src/client/page/mod.ts b/src/client/page/mod.ts index 0728f6f..47167f2 100644 --- a/src/client/page/mod.ts +++ b/src/client/page/mod.ts @@ -1,8 +1,8 @@ -export * from './contentinfo'; -export * from './gallery'; -export * from './login'; -export * from './404'; -export * from './profile'; -export * from './difference'; -export * from './setting'; -export * from './tags'; \ No newline at end of file +export * from "./404"; +export * from "./contentinfo"; +export * from "./difference"; +export * from "./gallery"; +export * from "./login"; +export * from "./profile"; +export * from "./setting"; +export * from "./tags"; diff --git a/src/client/page/profile.tsx b/src/client/page/profile.tsx index d24c6bd..66211f0 100644 --- a/src/client/page/profile.tsx +++ b/src/client/page/profile.tsx @@ -1,114 +1,147 @@ +import { + Button, + Chip, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Divider, + Grid, + Paper, + TextField, + Theme, + Typography, +} from "@mui/material"; +import React, { useContext, useState } from "react"; import { CommonMenuList, Headline } from "../component/mod"; -import React, { useContext, useState } from 'react'; import { UserContext } from "../state"; -import { Chip, Grid, Paper, Theme, Typography, Divider, Button, - Dialog, DialogTitle, DialogContentText, DialogContent, TextField, DialogActions } from "@mui/material"; -const useStyles = ((theme:Theme)=>({ - paper:{ - alignSelf:"center", - padding:theme.spacing(2), +const useStyles = (theme: Theme) => ({ + paper: { + alignSelf: "center", + padding: theme.spacing(2), }, - formfield:{ - display:'flex', - flexFlow:'column', - } -})); + formfield: { + display: "flex", + flexFlow: "column", + }, +}); -export function ProfilePage(){ +export function ProfilePage() { const userctx = useContext(UserContext); - //const classes = useStyles(); + // const classes = useStyles(); const menu = CommonMenuList(); - const [pw_open,set_pw_open] = useState(false); - const [oldpw,setOldpw] = useState(""); - const [newpw,setNewpw] = useState(""); - const [newpwch,setNewpwch] = useState(""); - const [msg_dialog,set_msg_dialog] = useState({opened:false,msg:""}); - const permission_list =userctx.permission.map(p=>( - - )); - const isElectronContent = (((window['electron'] as any) !== undefined) as boolean); - const handle_open = ()=>set_pw_open(true); - const handle_close = ()=>{ + const [pw_open, set_pw_open] = useState(false); + const [oldpw, setOldpw] = useState(""); + const [newpw, setNewpw] = useState(""); + const [newpwch, setNewpwch] = useState(""); + const [msg_dialog, set_msg_dialog] = useState({ opened: false, msg: "" }); + const permission_list = userctx.permission.map(p => ); + const isElectronContent = ((window["electron"] as any) !== undefined) as boolean; + const handle_open = () => set_pw_open(true); + const handle_close = () => { set_pw_open(false); setNewpw(""); setNewpwch(""); }; - const handle_ok= async ()=>{ - if(newpw != newpwch){ - set_msg_dialog({opened:true,msg:"password and password check is not equal."}); + const handle_ok = async () => { + if (newpw != newpwch) { + set_msg_dialog({ opened: true, msg: "password and password check is not equal." }); handle_close(); return; } - if(isElectronContent){ - const elec = window['electron'] as any; - const success = elec.passwordReset(userctx.username,newpw); - if(!success){ - set_msg_dialog({opened:true,msg:"user not exist."}); + if (isElectronContent) { + const elec = window["electron"] as any; + const success = elec.passwordReset(userctx.username, newpw); + if (!success) { + set_msg_dialog({ opened: true, msg: "user not exist." }); } - } - else{ - const res = await fetch("/user/reset",{ - method: 'POST', - body:JSON.stringify({ - username:userctx.username, - oldpassword:oldpw, - newpassword:newpw, + } else { + const res = await fetch("/user/reset", { + method: "POST", + body: JSON.stringify({ + username: userctx.username, + oldpassword: oldpw, + newpassword: newpw, }), - headers:{ - "content-type":"application/json" - } + headers: { + "content-type": "application/json", + }, }); - if(res.status != 200){ - set_msg_dialog({opened:true,msg:"failed to change password."}); + if (res.status != 200) { + set_msg_dialog({ opened: true, msg: "failed to change password." }); } } handle_close(); - } - return ( - - - - {userctx.username} + }; + return ( + + + + + {userctx.username} + + + + Permission + + + {permission_list.length == 0 ? "-" : permission_list} + + + + - - - Permission - - - {permission_list.length == 0 ? "-" : permission_list} - - - - - - - - Password Reset - - type the old and new password -
- {(!isElectronContent) && (setOldpw(e.target.value)}>)} - setNewpw(e.target.value)}> - setNewpwch(e.target.value)}> -
-
- - - - -
- set_msg_dialog({opened:false,msg:""})}> - Alert! - - {msg_dialog.msg} - - - - - -
) -} \ No newline at end of file + + + Password Reset + + type the old and new password +
+ {(!isElectronContent) && ( + setOldpw(e.target.value)} + > + + )} + setNewpw(e.target.value)} + > + + setNewpwch(e.target.value)} + > + +
+
+ + + + +
+ set_msg_dialog({ opened: false, msg: "" })}> + Alert! + + {msg_dialog.msg} + + + + + + + ); +} diff --git a/src/client/page/reader/comic.tsx b/src/client/page/reader/comic.tsx index 54894e9..f3b057c 100644 --- a/src/client/page/reader/comic.tsx +++ b/src/client/page/reader/comic.tsx @@ -1,47 +1,52 @@ -import React, {useState, useEffect} from 'react'; -import { Typography, useTheme } from '@mui/material'; -import { Document } from '../../accessor/document'; +import { Typography, useTheme } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import { Document } from "../../accessor/document"; -type ComicType = "comic"|"artist cg"|"donjinshi"|"western"; +type ComicType = "comic" | "artist cg" | "donjinshi" | "western"; export type PresentableTag = { - artist:string[], - group: string[], - series: string[], - type: ComicType, - character: string[], - tags: string[], -} + artist: string[]; + group: string[]; + series: string[]; + type: ComicType; + character: string[]; + tags: string[]; +}; -export const ComicReader = (props:{doc:Document})=>{ +export const ComicReader = (props: { doc: Document }) => { const additional = props.doc.additional; - const [curPage,setCurPage] = useState(0); - if(!('page' in additional)){ - console.error("invalid content : page read fail : "+ JSON.stringify(additional)); - return Error. DB error. page restriction + const [curPage, setCurPage] = useState(0); + if (!("page" in additional)) { + console.error("invalid content : page read fail : " + JSON.stringify(additional)); + return Error. DB error. page restriction; } - const PageDown = ()=>setCurPage(Math.max(curPage - 1 , 0)); - const PageUP = ()=>setCurPage(Math.min(curPage + 1, page - 1)); - const page:number = additional['page'] as number; - const onKeyUp = (e: KeyboardEvent)=>{ - if(e.code === "ArrowLeft"){ + const PageDown = () => setCurPage(Math.max(curPage - 1, 0)); + const PageUP = () => setCurPage(Math.min(curPage + 1, page - 1)); + const page: number = additional["page"] as number; + const onKeyUp = (e: KeyboardEvent) => { + if (e.code === "ArrowLeft") { PageDown(); - } - else if(e.code === "ArrowRight"){ + } else if (e.code === "ArrowRight") { PageUP(); } - } - useEffect(()=>{ - document.addEventListener("keydown",onKeyUp); - return ()=>{ - document.removeEventListener("keydown",onKeyUp); - } + }; + useEffect(() => { + document.addEventListener("keydown", onKeyUp); + return () => { + document.removeEventListener("keydown", onKeyUp); + }; }); - //theme.mixins.toolbar.minHeight; - return (
- -
); -} + // theme.mixins.toolbar.minHeight; + return ( +
+ + +
+ ); +}; -export default ComicReader; \ No newline at end of file +export default ComicReader; diff --git a/src/client/page/reader/reader.tsx b/src/client/page/reader/reader.tsx index a1f3245..eb4860e 100644 --- a/src/client/page/reader/reader.tsx +++ b/src/client/page/reader/reader.tsx @@ -1,80 +1,77 @@ -import { Typography, styled } from '@mui/material'; -import React from 'react'; -import { Document, makeThumbnailUrl } from '../../accessor/document'; -import {ComicReader} from './comic'; -import {VideoReader} from './video' +import { styled, Typography } from "@mui/material"; +import React from "react"; +import { Document, makeThumbnailUrl } from "../../accessor/document"; +import { ComicReader } from "./comic"; +import { VideoReader } from "./video"; -export interface PagePresenterProp{ - doc:Document, - className?:string +export interface PagePresenterProp { + doc: Document; + className?: string; } -interface PagePresenter{ - (prop:PagePresenterProp):JSX.Element +interface PagePresenter { + (prop: PagePresenterProp): JSX.Element; } -export const getPresenter = (content:Document):PagePresenter => { +export const getPresenter = (content: Document): PagePresenter => { switch (content.content_type) { case "comic": return ComicReader; case "video": return VideoReader; } - return ()=>Not implemented reader; -} + return () => Not implemented reader; +}; const BackgroundDiv = styled("div")({ - height: '400px', - width:'300px', - backgroundColor:"#272733", - display:"flex", - alignItems:"center", - justifyContent:"center"} - ); + height: "400px", + width: "300px", + backgroundColor: "#272733", + display: "flex", + alignItems: "center", + justifyContent: "center", +}); - -import { useRef, useState, useEffect } from 'react'; -import "./thumbnail.css" +import { useEffect, useRef, useState } from "react"; +import "./thumbnail.css"; export function useIsElementInViewport(options?: IntersectionObserverInit) { const elementRef = useRef(null); const [isVisible, setIsVisible] = useState(false); const callback = (entries: IntersectionObserverEntry[]) => { - const [entry] = entries; - setIsVisible(entry.isIntersecting); + const [entry] = entries; + setIsVisible(entry.isIntersecting); }; useEffect(() => { - const observer = new IntersectionObserver(callback, options); - elementRef.current && observer.observe(elementRef.current); - return () => observer.disconnect(); + const observer = new IntersectionObserver(callback, options); + elementRef.current && observer.observe(elementRef.current); + return () => observer.disconnect(); }, [elementRef, options]); return { elementRef, isVisible }; -}; +} -export function ThumbnailContainer(props:{ - content:Document, - className?:string, -}){ - const {elementRef, isVisible} = useIsElementInViewport({}); +export function ThumbnailContainer(props: { + content: Document; + className?: string; +}) { + const { elementRef, isVisible } = useIsElementInViewport({}); const [loaded, setLoaded] = useState(false); - useEffect(()=>{ - if(isVisible){ + useEffect(() => { + if (isVisible) { setLoaded(true); } - },[isVisible]) + }, [isVisible]); const style = { - maxHeight: '400px', - maxWidth: 'min(400px, 100vw)', + maxHeight: "400px", + maxWidth: "min(400px, 100vw)", }; const thumbnailurl = makeThumbnailUrl(props.content); - if(props.content.content_type === "video"){ - return () - } - else return ( - {loaded && } - ) -} \ No newline at end of file + if (props.content.content_type === "video") { + return ; + } else {return ( + + {loaded && } + + );} +} diff --git a/src/client/page/reader/video.tsx b/src/client/page/reader/video.tsx index fccd2d6..af6d137 100644 --- a/src/client/page/reader/video.tsx +++ b/src/client/page/reader/video.tsx @@ -1,7 +1,10 @@ -import React from 'react'; -import { Document } from '../../accessor/document'; +import React from "react"; +import { Document } from "../../accessor/document"; -export const VideoReader = (props:{doc:Document})=>{ +export const VideoReader = (props: { doc: Document }) => { const id = props.doc.id; - return ; -} \ No newline at end of file + return ( + + ); +}; diff --git a/src/client/page/setting.tsx b/src/client/page/setting.tsx index 88ff943..d40f19a 100644 --- a/src/client/page/setting.tsx +++ b/src/client/page/setting.tsx @@ -1,13 +1,15 @@ -import React from 'react'; -import {Typography, Paper} from '@mui/material'; -import {ArrowBack as ArrowBackIcon} from '@mui/icons-material'; -import { Headline, BackItem, NavList, CommonMenuList } from '../component/mod'; +import { ArrowBack as ArrowBackIcon } from "@mui/icons-material"; +import { Paper, Typography } from "@mui/material"; +import React from "react"; +import { BackItem, CommonMenuList, Headline, NavList } from "../component/mod"; -export const SettingPage = ()=>{ +export const SettingPage = () => { const menu = CommonMenuList(); - return ( + return ( + - Setting + Setting - ); -}; \ No newline at end of file + + ); +}; diff --git a/src/client/page/tags.tsx b/src/client/page/tags.tsx index 11f6255..9b14f4a 100644 --- a/src/client/page/tags.tsx +++ b/src/client/page/tags.tsx @@ -1,8 +1,8 @@ -import React, { useEffect, useState } from 'react'; -import {Typography, Box, Paper} from '@mui/material'; -import {LoadingCircle} from "../component/loading"; -import { Headline, CommonMenuList } from '../component/mod'; -import {DataGrid, GridColDef} from "@mui/x-data-grid" +import { Box, Paper, Typography } from "@mui/material"; +import { DataGrid, GridColDef } from "@mui/x-data-grid"; +import React, { useEffect, useState } from "react"; +import { LoadingCircle } from "../component/loading"; +import { CommonMenuList, Headline } from "../component/mod"; type TagCount = { tag_name: string; @@ -10,52 +10,52 @@ type TagCount = { }; const tagTableColumn: GridColDef[] = [ -{ - field:"tag_name", - headerName:"Tag Name", - width: 200, -}, -{ - field:"occurs", - headerName:"Occurs", - width:100, - type:"number" -} -] + { + field: "tag_name", + headerName: "Tag Name", + width: 200, + }, + { + field: "occurs", + headerName: "Occurs", + width: 100, + type: "number", + }, +]; -function TagTable(){ - const [data,setData] = useState(); - const [error, setErrorMsg] = useState(undefined); +function TagTable() { + const [data, setData] = useState(); + const [error, setErrorMsg] = useState(undefined); const isLoading = data === undefined; - useEffect(()=>{ + useEffect(() => { loadData(); - },[]); + }, []); - if(isLoading){ - return ; + if (isLoading) { + return ; } - if(error !== undefined){ - return {error} + if (error !== undefined) { + return {error}; } - return - - t.tag_name} > - + return ( + + + t.tag_name}> + - - async function loadData(){ - try{ + ); + + async function loadData() { + try { const res = await fetch("/api/tags?withCount=true"); const data = await res.json(); setData(data); - } - catch(e){ + } catch (e) { setData([]); - if(e instanceof Error){ + if (e instanceof Error) { setErrorMsg(e.message); - } - else{ + } else { console.log(e); setErrorMsg(""); } @@ -63,9 +63,11 @@ function TagTable(){ } } -export const TagsPage = ()=>{ +export const TagsPage = () => { const menu = CommonMenuList(); - return - - -}; \ No newline at end of file + return ( + + + + ); +}; diff --git a/src/client/state.tsx b/src/client/state.tsx index e03c1a8..ad9a74c 100644 --- a/src/client/state.tsx +++ b/src/client/state.tsx @@ -1,16 +1,16 @@ -import React, { createContext, useRef, useState } from 'react'; -export const BackLinkContext = createContext({backLink:"",setBackLink:(s:string)=>{} }); +import React, { createContext, useRef, useState } from "react"; +export const BackLinkContext = createContext({ backLink: "", setBackLink: (s: string) => {} }); export const UserContext = createContext({ username: "", permission: [] as string[], - setUsername: (s: string) => { }, - setPermission: (permission: string[]) => { } + setUsername: (s: string) => {}, + setPermission: (permission: string[]) => {}, }); type LoginLocalStorage = { - username: string, - permission: string[], - accessExpired: number + username: string; + permission: string[]; + accessExpired: number; }; let localObj: LoginLocalStorage | null = null; @@ -20,76 +20,75 @@ export const getInitialValue = async () => { const storagestr = window.localStorage.getItem("UserLoginContext") as string | null; const storage = storagestr !== null ? JSON.parse(storagestr) as LoginLocalStorage | null : null; localObj = storage; - } + } if (localObj !== null && localObj.accessExpired > Math.floor(Date.now() / 1000)) { return { username: localObj.username, permission: localObj.permission, - } + }; } - const res = await fetch('/user/refresh', { - method: 'POST', + const res = await fetch("/user/refresh", { + method: "POST", }); - if (res.status !== 200) throw new Error("Maybe Network Error") + if (res.status !== 200) throw new Error("Maybe Network Error"); const r = await res.json() as LoginLocalStorage & { refresh: boolean }; if (r.refresh) { localObj = { username: r.username, permission: r.permission, - accessExpired: r.accessExpired - } - } - else { + accessExpired: r.accessExpired, + }; + } else { localObj = { accessExpired: 0, username: "", - permission: r.permission - } + permission: r.permission, + }; } window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); return { username: r.username, - permission: r.permission - } -} + permission: r.permission, + }; +}; export const doLogout = async () => { - const req = await fetch('/user/logout', { - method: 'POST' + const req = await fetch("/user/logout", { + method: "POST", }); try { const res = await req.json(); localObj = { accessExpired: 0, username: "", - permission: res["permission"] - } + permission: res["permission"], + }; window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); return { username: localObj.username, permission: localObj.permission, - } + }; } catch (error) { console.error(`Server Error ${error}`); return { username: "", permission: [], - } + }; } -} -export const doLogin = async (userLoginInfo:{ - username:string, - password:string, -}): Promise=>{ - const res = await fetch('/user/login',{ - method:'POST', - body:JSON.stringify(userLoginInfo), - headers:{"content-type":"application/json"} +}; +export const doLogin = async (userLoginInfo: { + username: string; + password: string; +}): Promise => { + const res = await fetch("/user/login", { + method: "POST", + body: JSON.stringify(userLoginInfo), + headers: { "content-type": "application/json" }, }); const b = await res.json(); - if(res.status !== 200){ + if (res.status !== 200) { return b.detail as string; } localObj = b; window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); return b; -} \ No newline at end of file +}; diff --git a/src/config.ts b/src/config.ts index 44d381c..583c834 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,22 +1,22 @@ -import {Knex as k} from "knex"; +import { Knex as k } from "knex"; export namespace Knex { - export const config: { - development: k.Config, - production: k.Config - } = { - development: { - client: 'sqlite3', - connection: { - filename: './devdb.sqlite3' + export const config: { + development: k.Config; + production: k.Config; + } = { + development: { + client: "sqlite3", + connection: { + filename: "./devdb.sqlite3", + }, + debug: true, }, - debug: true, - }, - production: { - client: 'sqlite3', - connection: { - filename: './db.sqlite3', + production: { + client: "sqlite3", + connection: { + filename: "./db.sqlite3", + }, }, - } - }; + }; } diff --git a/src/content/comic.ts b/src/content/comic.ts index e06aa42..0f0feb0 100644 --- a/src/content/comic.ts +++ b/src/content/comic.ts @@ -1,65 +1,66 @@ -import {ContentFile, createDefaultClass,registerContentReferrer, ContentConstructOption} from './file'; -import {readZip, readAllFromZip} from '../util/zipwrap'; -import { DocumentBody } from '../model/doc'; -import {extname} from 'path'; +import { extname } from "path"; +import { DocumentBody } from "../model/doc"; +import { readAllFromZip, readZip } from "../util/zipwrap"; +import { ContentConstructOption, ContentFile, createDefaultClass, registerContentReferrer } from "./file"; -type ComicType = "doujinshi"|"artist cg"|"manga"|"western"; -interface ComicDesc{ - title:string, - artist?:string[], - group?:string[], - series?:string[], - type:ComicType|[ComicType], - character?:string[], - tags?:string[] +type ComicType = "doujinshi" | "artist cg" | "manga" | "western"; +interface ComicDesc { + title: string; + artist?: string[]; + group?: string[]; + series?: string[]; + type: ComicType | [ComicType]; + character?: string[]; + tags?: string[]; } -const ImageExt = ['.gif', '.png', '.jpeg', '.bmp', '.webp', '.jpg']; -export class ComicReferrer extends createDefaultClass("comic"){ - desc: ComicDesc|undefined; +const ImageExt = [".gif", ".png", ".jpeg", ".bmp", ".webp", ".jpg"]; +export class ComicReferrer extends createDefaultClass("comic") { + desc: ComicDesc | undefined; pagenum: number; - additional: ContentConstructOption| undefined; - constructor(path:string,option?:ContentConstructOption){ + additional: ContentConstructOption | undefined; + constructor(path: string, option?: ContentConstructOption) { super(path); this.additional = option; this.pagenum = 0; } - async initDesc():Promise{ - if(this.desc !== undefined) return; + async initDesc(): Promise { + if (this.desc !== undefined) return; const zip = await readZip(this.path); const entries = await zip.entries(); - this.pagenum = Object.keys(entries).filter(x=>ImageExt.includes(extname(x))).length; - const entry = entries["desc.json"]; - if(entry === undefined){ + this.pagenum = Object.keys(entries).filter(x => ImageExt.includes(extname(x))).length; + const entry = entries["desc.json"]; + if (entry === undefined) { return; } - const data = (await readAllFromZip(zip,entry)).toString('utf-8'); + const data = (await readAllFromZip(zip, entry)).toString("utf-8"); this.desc = JSON.parse(data); - if(this.desc === undefined) + if (this.desc === undefined) { throw new Error(`JSON.parse is returning undefined. ${this.path} desc.json format error`); + } } - - async createDocumentBody(): Promise{ + + async createDocumentBody(): Promise { await this.initDesc(); const basebody = await super.createDocumentBody(); this.desc?.title; - if(this.desc === undefined){ + if (this.desc === undefined) { return basebody; } - let tags:string[] = this.desc.tags ?? []; - tags = tags.concat(this.desc.artist?.map(x=>`artist:${x}`) ?? []); - tags = tags.concat(this.desc.character?.map(x=>`character:${x}`) ?? []); - tags = tags.concat(this.desc.group?.map(x=>`group:${x}`) ?? []); - tags = tags.concat(this.desc.series?.map(x=>`series:${x}`) ?? []); - const type = this.desc.type instanceof Array ? this.desc.type[0]: this.desc.type; + let tags: string[] = this.desc.tags ?? []; + tags = tags.concat(this.desc.artist?.map(x => `artist:${x}`) ?? []); + tags = tags.concat(this.desc.character?.map(x => `character:${x}`) ?? []); + tags = tags.concat(this.desc.group?.map(x => `group:${x}`) ?? []); + tags = tags.concat(this.desc.series?.map(x => `series:${x}`) ?? []); + const type = this.desc.type instanceof Array ? this.desc.type[0] : this.desc.type; tags.push(`type:${type}`); return { ...basebody, - title:this.desc.title, - additional:{ - page:this.pagenum + title: this.desc.title, + additional: { + page: this.pagenum, }, - tags:tags + tags: tags, }; } -}; -registerContentReferrer(ComicReferrer); \ No newline at end of file +} +registerContentReferrer(ComicReferrer); diff --git a/src/content/file.ts b/src/content/file.ts index b1be815..e7b85e0 100644 --- a/src/content/file.ts +++ b/src/content/file.ts @@ -1,42 +1,44 @@ -import {Context, DefaultState, DefaultContext, Middleware, Next} from 'koa'; -import Router from 'koa-router'; -import {createHash} from 'crypto'; -import {promises, Stats} from 'fs' -import {extname} from 'path'; -import { DocumentBody } from '../model/mod'; -import path from 'path'; +import { createHash } from "crypto"; +import { promises, Stats } from "fs"; +import { Context, DefaultContext, DefaultState, Middleware, Next } from "koa"; +import Router from "koa-router"; +import { extname } from "path"; +import path from "path"; +import { DocumentBody } from "../model/mod"; /** * content file or directory referrer */ -export interface ContentFile{ - getHash():Promise; - createDocumentBody():Promise; +export interface ContentFile { + getHash(): Promise; + createDocumentBody(): Promise; readonly path: string; readonly type: string; } export type ContentConstructOption = { - hash: string, -} -type ContentFileConstructor = (new (path:string,option?:ContentConstructOption) => ContentFile)&{content_type:string}; -export const createDefaultClass = (type:string):ContentFileConstructor=>{ - let cons = class implements ContentFile{ + hash: string; +}; +type ContentFileConstructor = (new(path: string, option?: ContentConstructOption) => ContentFile) & { + content_type: string; +}; +export const createDefaultClass = (type: string): ContentFileConstructor => { + let cons = class implements ContentFile { readonly path: string; - //type = type; + // type = type; static content_type = type; - protected hash: string| undefined; - protected stat: Stats| undefined; + protected hash: string | undefined; + protected stat: Stats | undefined; - constructor(path:string,option?:ContentConstructOption){ + constructor(path: string, option?: ContentConstructOption) { this.path = path; this.hash = option?.hash; this.stat = undefined; } async createDocumentBody(): Promise { - const {base,dir, name} = path.parse(this.path); - + const { base, dir, name } = path.parse(this.path); + const ret = { - title : name, - basepath : dir, + title: name, + basepath: dir, additional: {}, content_type: cons.content_type, filename: base, @@ -46,43 +48,43 @@ export const createDefaultClass = (type:string):ContentFileConstructor=>{ } as DocumentBody; return ret; } - get type():string{ + get type(): string { return cons.content_type; } - async getHash():Promise{ - if(this.hash !== undefined) return this.hash; + async getHash(): Promise { + if (this.hash !== undefined) return this.hash; this.stat = await promises.stat(this.path); const hash = createHash("sha512"); hash.update(extname(this.path)); hash.update(this.stat.mode.toString()); - //if(this.desc !== undefined) + // if(this.desc !== undefined) // hash.update(JSON.stringify(this.desc)); hash.update(this.stat.size.toString()); this.hash = hash.digest("base64"); return this.hash; } - async getMtime():Promise{ - if(this.stat !== undefined) return this.stat.mtimeMs; + async getMtime(): Promise { + if (this.stat !== undefined) return this.stat.mtimeMs; await this.getHash(); return this.stat!.mtimeMs; } }; return cons; -} -let ContstructorTable:{[k:string]:ContentFileConstructor} = {}; -export function registerContentReferrer(s: ContentFileConstructor){ - console.log(`registered content type: ${s.content_type}`) +}; +let ContstructorTable: { [k: string]: ContentFileConstructor } = {}; +export function registerContentReferrer(s: ContentFileConstructor) { + console.log(`registered content type: ${s.content_type}`); ContstructorTable[s.content_type] = s; } -export function createContentFile(type:string,path:string,option?:ContentConstructOption){ +export function createContentFile(type: string, path: string, option?: ContentConstructOption) { const constructorMethod = ContstructorTable[type]; - if(constructorMethod === undefined){ + if (constructorMethod === undefined) { console.log(`${type} are not in ${JSON.stringify(ContstructorTable)}`); throw new Error("construction method of the content type is undefined"); } - return new constructorMethod(path,option); + return new constructorMethod(path, option); } -export function getContentFileConstructor(type:string): ContentFileConstructor|undefined{ +export function getContentFileConstructor(type: string): ContentFileConstructor | undefined { const ret = ContstructorTable[type]; return ret; -} \ No newline at end of file +} diff --git a/src/content/mod.ts b/src/content/mod.ts index 0956d33..1868ecf 100644 --- a/src/content/mod.ts +++ b/src/content/mod.ts @@ -1,3 +1,3 @@ -import './comic'; -import './video'; -export {ContentFile, createContentFile} from './file'; \ No newline at end of file +import "./comic"; +import "./video"; +export { ContentFile, createContentFile } from "./file"; diff --git a/src/content/video.ts b/src/content/video.ts index edf2095..d24e19f 100644 --- a/src/content/video.ts +++ b/src/content/video.ts @@ -1,9 +1,9 @@ -import {ContentFile, registerContentReferrer, ContentConstructOption} from './file'; -import {createDefaultClass} from './file'; +import { ContentConstructOption, ContentFile, registerContentReferrer } from "./file"; +import { createDefaultClass } from "./file"; -export class VideoReferrer extends createDefaultClass("video"){ - constructor(path:string,desc?:ContentConstructOption){ - super(path,desc); +export class VideoReferrer extends createDefaultClass("video") { + constructor(path: string, desc?: ContentConstructOption) { + super(path, desc); } } -registerContentReferrer(VideoReferrer); \ No newline at end of file +registerContentReferrer(VideoReferrer); diff --git a/src/database.ts b/src/database.ts index b779352..285e726 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,49 +1,47 @@ -import { existsSync } from 'fs'; -import Knex from 'knex'; -import {Knex as KnexConfig} from './config'; -import { get_setting } from './SettingConfig'; +import { existsSync } from "fs"; +import Knex from "knex"; +import { Knex as KnexConfig } from "./config"; +import { get_setting } from "./SettingConfig"; -export async function connectDB(){ +export async function connectDB() { const env = get_setting().mode; const config = KnexConfig.config[env]; - if(!config.connection){ + if (!config.connection) { throw new Error("connection options required."); } - const connection = config.connection - if(typeof connection === "string"){ + const connection = config.connection; + if (typeof connection === "string") { throw new Error("unknown connection options"); } - if(typeof connection === "function"){ + if (typeof connection === "function") { throw new Error("connection provider not supported..."); } - if(!("filename" in connection) ){ + if (!("filename" in connection)) { throw new Error("sqlite3 config need"); } const init_need = !existsSync(connection.filename); const knex = Knex(config); let tries = 0; - for(;;){ - try{ + for (;;) { + try { console.log("try to connect db"); - await knex.raw('select 1 + 1;'); + await knex.raw("select 1 + 1;"); console.log("connect success"); - } - catch(err){ - if(tries < 3){ + } catch (err) { + if (tries < 3) { tries++; console.error(`connection fail ${err} retry...`); continue; - } - else{ + } else { throw err; } } break; } - if(init_need){ + if (init_need) { console.log("first execute: initialize database..."); const migrate = await import("../migrations/initial"); await migrate.up(knex); } return knex; -} \ No newline at end of file +} diff --git a/src/db/doc.ts b/src/db/doc.ts index 44cbab7..ef48898 100644 --- a/src/db/doc.ts +++ b/src/db/doc.ts @@ -1,118 +1,118 @@ -import { Document, DocumentBody, DocumentAccessor, QueryListOption } from '../model/doc'; -import {Knex} from 'knex'; -import {createKnexTagController} from './tag'; -import { TagAccessor } from '../model/tag'; +import { Knex } from "knex"; +import { Document, DocumentAccessor, DocumentBody, QueryListOption } from "../model/doc"; +import { TagAccessor } from "../model/tag"; +import { createKnexTagController } from "./tag"; export type DBTagContentRelation = { - doc_id:number, - tag_name:string -} + doc_id: number; + tag_name: string; +}; -class KnexDocumentAccessor implements DocumentAccessor{ - knex : Knex; +class KnexDocumentAccessor implements DocumentAccessor { + knex: Knex; tagController: TagAccessor; - constructor(knex : Knex){ + constructor(knex: Knex) { this.knex = knex; this.tagController = createKnexTagController(knex); } - async search(search_word: string): Promise{ + async search(search_word: string): Promise { throw new Error("Method not implemented."); const sw = `%${search_word}%`; const docs = await this.knex.select("*").from("document") - .where("title","like",sw); + .where("title", "like", sw); return docs; } - async addList(content_list: DocumentBody[]):Promise{ - return await this.knex.transaction(async (trx)=>{ - //add tags + async addList(content_list: DocumentBody[]): Promise { + return await this.knex.transaction(async (trx) => { + // add tags const tagCollected = new Set(); - content_list.map(x=>x.tags).forEach((x)=>{ - x.forEach(x=>{ + content_list.map(x => x.tags).forEach((x) => { + x.forEach(x => { tagCollected.add(x); }); }); const tagCollectPromiseList = []; const tagController = createKnexTagController(trx); - for (const it of tagCollected){ - const p = tagController.addTag({name:it}); + for (const it of tagCollected) { + const p = tagController.addTag({ name: it }); tagCollectPromiseList.push(p); } await Promise.all(tagCollectPromiseList); - //add for each contents + // add for each contents const ret = []; for (const content of content_list) { - const {tags,additional, ...rest} = content; + const { tags, additional, ...rest } = content; const id_lst = await trx.insert({ - additional:JSON.stringify(additional), - created_at:Date.now(), - ...rest + additional: JSON.stringify(additional), + created_at: Date.now(), + ...rest, }).into("document"); const id = id_lst[0]; - if(tags.length > 0){ - await trx.insert(tags.map(y=>({ - doc_id:id, - tag_name:y - }))).into('doc_tag_relation'); + if (tags.length > 0) { + await trx.insert(tags.map(y => ({ + doc_id: id, + tag_name: y, + }))).into("doc_tag_relation"); } ret.push(id); } return ret; }); } - async add(c: DocumentBody){ - const {tags,additional, ...rest} = c; + async add(c: DocumentBody) { + const { tags, additional, ...rest } = c; const id_lst = await this.knex.insert({ - additional:JSON.stringify(additional), - created_at:Date.now(), - ...rest - }).into('document'); + additional: JSON.stringify(additional), + created_at: Date.now(), + ...rest, + }).into("document"); const id = id_lst[0]; for (const it of tags) { - this.tagController.addTag({name:it}); + this.tagController.addTag({ name: it }); } - if(tags.length > 0){ + if (tags.length > 0) { await this.knex.insert( - tags.map(x=>({doc_id:id,tag_name:x})) + tags.map(x => ({ doc_id: id, tag_name: x })), ).into("doc_tag_relation"); } return id; - }; - async del(id:number) { - if (await this.findById(id) !== undefined){ - await this.knex.delete().from("doc_tag_relation").where({doc_id:id}); - await this.knex.delete().from("document").where({id:id}); + } + async del(id: number) { + if (await this.findById(id) !== undefined) { + await this.knex.delete().from("doc_tag_relation").where({ doc_id: id }); + await this.knex.delete().from("document").where({ id: id }); return true; } return false; - }; - async findById(id:number,tagload?:boolean): Promise{ - const s = await this.knex.select("*").from("document").where({id:id}); - if(s.length === 0) return undefined; + } + async findById(id: number, tagload?: boolean): Promise { + const s = await this.knex.select("*").from("document").where({ id: id }); + if (s.length === 0) return undefined; const first = s[0]; - let ret_tags:string[] = [] - if(tagload === true){ - const tags : DBTagContentRelation[] = await this.knex.select("*") - .from("doc_tag_relation").where({doc_id:first.id}); - ret_tags = tags.map(x=>x.tag_name); + let ret_tags: string[] = []; + if (tagload === true) { + const tags: DBTagContentRelation[] = await this.knex.select("*") + .from("doc_tag_relation").where({ doc_id: first.id }); + ret_tags = tags.map(x => x.tag_name); } return { ...first, - tags:ret_tags, + tags: ret_tags, additional: first.additional !== null ? JSON.parse(first.additional) : {}, }; - }; - async findDeleted(content_type:string){ + } + async findDeleted(content_type: string) { const s = await this.knex.select("*") - .where({content_type:content_type}) + .where({ content_type: content_type }) .whereNotNull("update_at") .from("document"); - return s.map(x=>({ + return s.map(x => ({ ...x, - tags:[], - additional:{} + tags: [], + additional: {}, })); } - async findList(option?:QueryListOption){ + async findList(option?: QueryListOption) { option = option ?? {}; const allow_tag = option.allow_tag ?? []; const eager_loading = option.eager_loading ?? true; @@ -123,98 +123,101 @@ class KnexDocumentAccessor implements DocumentAccessor{ const content_type = option.content_type; const cursor = option.cursor; - const buildquery = ()=>{ + const buildquery = () => { let query = this.knex.select("document.*"); - if(allow_tag.length > 0){ + if (allow_tag.length > 0) { query = query.from("doc_tag_relation as tags_0"); - query = query.where("tags_0.tag_name","=",allow_tag[0]); + query = query.where("tags_0.tag_name", "=", allow_tag[0]); for (let index = 1; index < allow_tag.length; index++) { const element = allow_tag[index]; - query = query.innerJoin(`doc_tag_relation as tags_${index}`,`tags_${index}.doc_id`,"tags_0.doc_id"); - query = query.where(`tags_${index}.tag_name`,'=',element); + query = query.innerJoin( + `doc_tag_relation as tags_${index}`, + `tags_${index}.doc_id`, + "tags_0.doc_id", + ); + query = query.where(`tags_${index}.tag_name`, "=", element); } - query = query.innerJoin("document","tags_0.doc_id","document.id"); - } - else{ + query = query.innerJoin("document", "tags_0.doc_id", "document.id"); + } else { query = query.from("document"); } - if(word !== undefined){ - //don't worry about sql injection. - query = query.where('title','like',`%${word}%`); + if (word !== undefined) { + // don't worry about sql injection. + query = query.where("title", "like", `%${word}%`); } - if(content_type !== undefined){ - query = query.where('content_type','=',content_type); + if (content_type !== undefined) { + query = query.where("content_type", "=", content_type); } - if(use_offset){ + if (use_offset) { query = query.offset(offset); - } - else{ - if(cursor !== undefined){ - query = query.where('id','<',cursor); + } else { + if (cursor !== undefined) { + query = query.where("id", "<", cursor); } } query = query.limit(limit); - query = query.orderBy('id',"desc"); + query = query.orderBy("id", "desc"); return query; - } + }; let query = buildquery(); - //console.log(query.toSQL()); - let result:Document[] = await query; - for(let i of result){ + // console.log(query.toSQL()); + let result: Document[] = await query; + for (let i of result) { i.additional = JSON.parse((i.additional as unknown) as string); } - if(eager_loading){ - let idmap: {[index:number]:Document} = {}; - for(const r of result){ + if (eager_loading) { + let idmap: { [index: number]: Document } = {}; + for (const r of result) { idmap[r.id] = r; r.tags = []; } let subquery = buildquery(); - let tagquery= this.knex.select("id","doc_tag_relation.tag_name").from(subquery) - .innerJoin("doc_tag_relation","doc_tag_relation.doc_id","id"); - //console.log(tagquery.toSQL()); - let tagresult:{id:number,tag_name:string}[] = await tagquery; - for(const {id,tag_name} of tagresult){ + let tagquery = this.knex.select("id", "doc_tag_relation.tag_name").from(subquery) + .innerJoin("doc_tag_relation", "doc_tag_relation.doc_id", "id"); + // console.log(tagquery.toSQL()); + let tagresult: { id: number; tag_name: string }[] = await tagquery; + for (const { id, tag_name } of tagresult) { idmap[id].tags.push(tag_name); } - } - else{ - result.forEach(v=>{v.tags = [];}); + } else { + result.forEach(v => { + v.tags = []; + }); } return result; - }; - async findByPath(path:string,filename?:string):Promise{ - const e = filename == undefined ? {} : {filename:filename} - const results = await this.knex.select("*").from("document").where({basepath:path,...e}); - return results.map(x=>({ - ...x, - tags:[], - additional:{} - })) } - async update(c:Partial & { id:number }){ - const {id,tags,...rest} = c; - if (await this.findById(id) !== undefined){ - await this.knex.update(rest).where({id: id}).from("document"); + async findByPath(path: string, filename?: string): Promise { + const e = filename == undefined ? {} : { filename: filename }; + const results = await this.knex.select("*").from("document").where({ basepath: path, ...e }); + return results.map(x => ({ + ...x, + tags: [], + additional: {}, + })); + } + async update(c: Partial & { id: number }) { + const { id, tags, ...rest } = c; + if (await this.findById(id) !== undefined) { + await this.knex.update(rest).where({ id: id }).from("document"); return true; } return false; } - async addTag(c: Document,tag_name:string){ + async addTag(c: Document, tag_name: string) { if (c.tags.includes(tag_name)) return false; - this.tagController.addTag({name:tag_name}); - await this.knex.insert({tag_name: tag_name, doc_id: c.id}) + this.tagController.addTag({ name: tag_name }); + await this.knex.insert({ tag_name: tag_name, doc_id: c.id }) .into("doc_tag_relation"); c.tags.push(tag_name); return true; } - async delTag(c: Document,tag_name:string){ + async delTag(c: Document, tag_name: string) { if (c.tags.includes(tag_name)) return false; - await this.knex.delete().where({tag_name: tag_name,doc_id: c.id}).from("doc_tag_relation"); + await this.knex.delete().where({ tag_name: tag_name, doc_id: c.id }).from("doc_tag_relation"); c.tags.push(tag_name); return true; } } -export const createKnexDocumentAccessor = (knex:Knex): DocumentAccessor=>{ +export const createKnexDocumentAccessor = (knex: Knex): DocumentAccessor => { return new KnexDocumentAccessor(knex); -} \ No newline at end of file +}; diff --git a/src/db/mod.ts b/src/db/mod.ts index 747d980..820bde4 100644 --- a/src/db/mod.ts +++ b/src/db/mod.ts @@ -1,3 +1,3 @@ -export * from './doc'; -export * from './tag'; -export * from './user'; \ No newline at end of file +export * from "./doc"; +export * from "./tag"; +export * from "./user"; diff --git a/src/db/tag.ts b/src/db/tag.ts index 2754223..dae8909 100644 --- a/src/db/tag.ts +++ b/src/db/tag.ts @@ -1,57 +1,57 @@ -import {Tag, TagAccessor, TagCount} from '../model/tag'; -import {Knex} from 'knex'; -import {DBTagContentRelation} from './doc'; +import { Knex } from "knex"; +import { Tag, TagAccessor, TagCount } from "../model/tag"; +import { DBTagContentRelation } from "./doc"; type DBTags = { - name: string, - description?: string -} + name: string; + description?: string; +}; -class KnexTagAccessor implements TagAccessor{ - knex:Knex - constructor(knex:Knex){ +class KnexTagAccessor implements TagAccessor { + knex: Knex; + constructor(knex: Knex) { this.knex = knex; } async getAllTagCount(): Promise { const result = await this.knex("doc_tag_relation").select("tag_name") - .count("*",{as:"occurs"}).groupBy("tag_name"); + .count("*", { as: "occurs" }).groupBy("tag_name"); return result; } - async getAllTagList(onlyname?:boolean){ + async getAllTagList(onlyname?: boolean) { onlyname = onlyname ?? false; - const t:DBTags[] = await this.knex.select(onlyname ? "*" : "name").from("tags") + const t: DBTags[] = await this.knex.select(onlyname ? "*" : "name").from("tags"); return t; } - async getTagByName(name:string){ - const t:DBTags[] = await this.knex.select('*').from("tags").where({name: name}); - if(t.length === 0) return undefined; + async getTagByName(name: string) { + const t: DBTags[] = await this.knex.select("*").from("tags").where({ name: name }); + if (t.length === 0) return undefined; return t[0]; } - async addTag(tag: Tag){ - if(await this.getTagByName(tag.name) === undefined){ + async addTag(tag: Tag) { + if (await this.getTagByName(tag.name) === undefined) { await this.knex.insert({ - name:tag.name, - description:tag.description === undefined ? "" : tag.description + name: tag.name, + description: tag.description === undefined ? "" : tag.description, }).into("tags"); return true; } return false; } - async delTag(name:string){ - if(await this.getTagByName(name) !== undefined){ - await this.knex.delete().where({name:name}).from("tags"); + async delTag(name: string) { + if (await this.getTagByName(name) !== undefined) { + await this.knex.delete().where({ name: name }).from("tags"); return true; } return false; } - async updateTag(name:string,desc:string){ - if(await this.getTagByName(name) !== undefined){ - await this.knex.update({description:desc}).where({name:name}).from("tags"); + async updateTag(name: string, desc: string) { + if (await this.getTagByName(name) !== undefined) { + await this.knex.update({ description: desc }).where({ name: name }).from("tags"); return true; } return false; } -}; -export const createKnexTagController = (knex:Knex):TagAccessor=>{ +} +export const createKnexTagController = (knex: Knex): TagAccessor => { return new KnexTagAccessor(knex); -} \ No newline at end of file +}; diff --git a/src/db/user.ts b/src/db/user.ts index 398b136..1ff2f15 100644 --- a/src/db/user.ts +++ b/src/db/user.ts @@ -1,41 +1,41 @@ -import {Knex} from 'knex'; -import {IUser,UserCreateInput, UserAccessor, Password} from '../model/user'; +import { Knex } from "knex"; +import { IUser, Password, UserAccessor, UserCreateInput } from "../model/user"; type PermissionTable = { - username:string, - name:string + username: string; + name: string; }; type DBUser = { - username : string, - password_hash: string, - password_salt: string -} -class KnexUser implements IUser{ + username: string; + password_hash: string; + password_salt: string; +}; +class KnexUser implements IUser { private knex: Knex; readonly username: string; readonly password: Password; - - constructor(username: string, pw: Password,knex:Knex){ + + constructor(username: string, pw: Password, knex: Knex) { this.username = username; this.password = pw; this.knex = knex; } - async reset_password(password: string){ + async reset_password(password: string) { this.password.set_password(password); await this.knex.from("users") - .where({username:this.username}) - .update({password_hash:this.password.hash,password_salt:this.password.salt}); + .where({ username: this.username }) + .update({ password_hash: this.password.hash, password_salt: this.password.salt }); } - async get_permissions(){ - let b = (await this.knex.select('*').from("permissions") - .where({username : this.username})) as PermissionTable[]; - return b.map(x=>x.name); + async get_permissions() { + let b = (await this.knex.select("*").from("permissions") + .where({ username: this.username })) as PermissionTable[]; + return b.map(x => x.name); } async add(name: string) { - if(!(await this.get_permissions()).includes(name)){ + if (!(await this.get_permissions()).includes(name)) { const r = await this.knex.insert({ username: this.username, - name: name + name: name, }).into("permissions"); return true; } @@ -45,38 +45,43 @@ class KnexUser implements IUser{ const r = await this.knex .from("permissions") .where({ - username:this.username, name:name + username: this.username, + name: name, }).delete(); return r !== 0; } } -export const createKnexUserController = (knex: Knex):UserAccessor=>{ - const createUserKnex = async (input:UserCreateInput)=>{ - if(undefined !== (await findUserKenx(input.username))){ +export const createKnexUserController = (knex: Knex): UserAccessor => { + const createUserKnex = async (input: UserCreateInput) => { + if (undefined !== (await findUserKenx(input.username))) { return undefined; } - const user = new KnexUser(input.username,new Password(input.password),knex); + const user = new KnexUser(input.username, new Password(input.password), knex); await knex.insert({ username: user.username, password_hash: user.password.hash, - password_salt: user.password.salt}).into("users"); + password_salt: user.password.salt, + }).into("users"); return user; }; - const findUserKenx = async (id:string)=>{ - let user:DBUser[] = await knex.select("*").from("users").where({username:id}); - if(user.length == 0) return undefined; + const findUserKenx = async (id: string) => { + let user: DBUser[] = await knex.select("*").from("users").where({ username: id }); + if (user.length == 0) return undefined; const first = user[0]; - return new KnexUser(first.username, - new Password({hash: first.password_hash, salt: first.password_salt}), knex); - } - const delUserKnex = async (id:string) => { - let r = await knex.delete().from("users").where({username:id}); - return r===0; - } + return new KnexUser( + first.username, + new Password({ hash: first.password_hash, salt: first.password_salt }), + knex, + ); + }; + const delUserKnex = async (id: string) => { + let r = await knex.delete().from("users").where({ username: id }); + return r === 0; + }; return { createUser: createUserKnex, findUser: findUserKenx, delUser: delUserKnex, }; -} \ No newline at end of file +}; diff --git a/src/diff/content_handler.ts b/src/diff/content_handler.ts index a3db25b..11ab44c 100644 --- a/src/diff/content_handler.ts +++ b/src/diff/content_handler.ts @@ -1,15 +1,15 @@ -import { basename, dirname, join as pathjoin } from 'path'; -import { Document, DocumentAccessor } from '../model/mod'; -import { ContentFile, createContentFile } from '../content/mod'; -import { IDiffWatcher } from './watcher'; -import { ContentList } from './content_list'; +import { basename, dirname, join as pathjoin } from "path"; +import { ContentFile, createContentFile } from "../content/mod"; +import { Document, DocumentAccessor } from "../model/mod"; +import { ContentList } from "./content_list"; +import { IDiffWatcher } from "./watcher"; -//refactoring needed. +// refactoring needed. export class ContentDiffHandler { /** content file list waiting to add */ waiting_list: ContentList; /** deleted contents */ - tombstone: Map;//hash, contentfile + tombstone: Map; // hash, contentfile doc_cntr: DocumentAccessor; /** content type of handle */ content_type: string; @@ -26,21 +26,21 @@ export class ContentDiffHandler { } } register(diff: IDiffWatcher) { - diff.on('create', (path) => this.OnCreated(path)) - .on('delete', (path) => this.OnDeleted(path)) - .on('change', (prev, cur) => this.OnChanged(prev, cur)); + diff.on("create", (path) => this.OnCreated(path)) + .on("delete", (path) => this.OnDeleted(path)) + .on("change", (prev, cur) => this.OnChanged(prev, cur)); } private async OnDeleted(cpath: string) { const basepath = dirname(cpath); const filename = basename(cpath); console.log("deleted ", cpath); - //if it wait to add, delete it from waiting list. + // if it wait to add, delete it from waiting list. if (this.waiting_list.hasByPath(cpath)) { this.waiting_list.deleteByPath(cpath); return; } const dbc = await this.doc_cntr.findByPath(basepath, filename); - //when there is no related content in db, ignore. + // when there is no related content in db, ignore. if (dbc.length === 0) { console.log("its not in waiting_list and db!!!: ", cpath); return; @@ -48,10 +48,10 @@ export class ContentDiffHandler { const content_hash = dbc[0].content_hash; // When a path is changed, it takes into account when the // creation event occurs first and the deletion occurs, not - // the change event. + // the change event. const cf = this.waiting_list.getByHash(content_hash); if (cf) { - //if a path is changed, update the changed path. + // if a path is changed, update the changed path. console.log("update path from", cpath, "to", cf.path); const newFilename = basename(cf.path); const newBasepath = dirname(cf.path); @@ -64,7 +64,7 @@ export class ContentDiffHandler { }); return; } - //invalidate db and add it to tombstone. + // invalidate db and add it to tombstone. await this.doc_cntr.update({ id: dbc[0].id, deleted_at: Date.now(), @@ -83,14 +83,13 @@ export class ContentDiffHandler { id: c.id, deleted_at: null, filename: filename, - basepath: basepath + basepath: basepath, }); } if (this.waiting_list.hasByHash(hash)) { console.log("Hash Conflict!!!"); } this.waiting_list.set(content); - } private async OnChanged(prev_path: string, cur_path: string) { const prev_basepath = dirname(prev_path); @@ -99,7 +98,7 @@ export class ContentDiffHandler { const cur_filename = basename(cur_path); console.log("modify", cur_path, "from", prev_path); const c = this.waiting_list.getByPath(prev_path); - if(c !== undefined){ + if (c !== undefined) { await this.waiting_list.delete(c); const content = createContentFile(this.content_type, cur_path); await this.waiting_list.set(content); @@ -107,7 +106,7 @@ export class ContentDiffHandler { } const doc = await this.doc_cntr.findByPath(prev_basepath, prev_filename); - if(doc.length === 0){ + if (doc.length === 0) { await this.OnCreated(cur_path); return; } @@ -115,7 +114,7 @@ export class ContentDiffHandler { await this.doc_cntr.update({ ...doc[0], basepath: cur_basepath, - filename: cur_filename + filename: cur_filename, }); } -} \ No newline at end of file +} diff --git a/src/diff/content_list.ts b/src/diff/content_list.ts index 58d4ae2..7a69cbd 100644 --- a/src/diff/content_list.ts +++ b/src/diff/content_list.ts @@ -1,59 +1,59 @@ -import { ContentFile } from '../content/mod'; +import { ContentFile } from "../content/mod"; -export class ContentList{ +export class ContentList { /** path map */ - private cl:Map; + private cl: Map; /** hash map */ - private hl:Map; - - constructor(){ - this.cl = new Map; - this.hl = new Map; + private hl: Map; + + constructor() { + this.cl = new Map(); + this.hl = new Map(); } - hasByHash(s:string){ + hasByHash(s: string) { return this.hl.has(s); } - hasByPath(p:string){ + hasByPath(p: string) { return this.cl.has(p); } - getByHash(s:string){ - return this.hl.get(s) + getByHash(s: string) { + return this.hl.get(s); } - getByPath(p:string){ + getByPath(p: string) { return this.cl.get(p); } - async set(c:ContentFile){ + async set(c: ContentFile) { const path = c.path; const hash = await c.getHash(); - this.cl.set(path,c); - this.hl.set(hash,c); + this.cl.set(path, c); + this.hl.set(hash, c); } /** delete content file */ - async delete(c:ContentFile){ + async delete(c: ContentFile) { const hash = await c.getHash(); let r = true; r = this.cl.delete(c.path) && r; r = this.hl.delete(hash) && r; return r; } - async deleteByPath(p:string){ + async deleteByPath(p: string) { const o = this.getByPath(p); - if(o === undefined) return false; + if (o === undefined) return false; return await this.delete(o); } - deleteByHash(s:string){ + deleteByHash(s: string) { const o = this.getByHash(s); - if(o === undefined) return false; + if (o === undefined) return false; let r = true; r = this.cl.delete(o.path) && r; r = this.hl.delete(s) && r; return r; } - clear(){ + clear() { this.cl.clear(); this.hl.clear(); } - getAll(){ + getAll() { return [...this.cl.values()]; } } diff --git a/src/diff/diff.ts b/src/diff/diff.ts index 8d781dc..c290ee3 100644 --- a/src/diff/diff.ts +++ b/src/diff/diff.ts @@ -1,26 +1,26 @@ -import { DocumentAccessor } from '../model/doc'; -import {ContentDiffHandler} from './content_handler'; -import { IDiffWatcher } from './watcher'; -import asyncPool from 'tiny-async-pool'; +import asyncPool from "tiny-async-pool"; +import { DocumentAccessor } from "../model/doc"; +import { ContentDiffHandler } from "./content_handler"; +import { IDiffWatcher } from "./watcher"; -export class DiffManager{ - watching: {[content_type:string]:ContentDiffHandler}; +export class DiffManager { + watching: { [content_type: string]: ContentDiffHandler }; doc_cntr: DocumentAccessor; - constructor(contorller: DocumentAccessor){ + constructor(contorller: DocumentAccessor) { this.watching = {}; this.doc_cntr = contorller; } - async register(content_type:string,watcher:IDiffWatcher){ - if(this.watching[content_type] === undefined){ - this.watching[content_type] = new ContentDiffHandler(this.doc_cntr,content_type); + async register(content_type: string, watcher: IDiffWatcher) { + if (this.watching[content_type] === undefined) { + this.watching[content_type] = new ContentDiffHandler(this.doc_cntr, content_type); } this.watching[content_type].register(watcher); await watcher.setup(this.doc_cntr); } - async commit(type:string,path:string){ + async commit(type: string, path: string) { const list = this.watching[type].waiting_list; const c = list.getByPath(path); - if(c===undefined){ + if (c === undefined) { throw new Error("path is not exist"); } await list.delete(c); @@ -28,18 +28,18 @@ export class DiffManager{ const id = await this.doc_cntr.add(body); return id; } - async commitAll(type:string){ + async commitAll(type: string) { const list = this.watching[type].waiting_list; const contentFiles = list.getAll(); list.clear(); - const bodies = await asyncPool(30,contentFiles,async (x)=>await x.createDocumentBody()); + const bodies = await asyncPool(30, contentFiles, async (x) => await x.createDocumentBody()); const ids = await this.doc_cntr.addList(bodies); return ids; } - getAdded(){ - return Object.keys(this.watching).map(x=>({ - type:x, - value:this.watching[x].waiting_list.getAll(), + getAdded() { + return Object.keys(this.watching).map(x => ({ + type: x, + value: this.watching[x].waiting_list.getAll(), })); } -}; \ No newline at end of file +} diff --git a/src/diff/mod.ts b/src/diff/mod.ts index cb8a6cd..3d7beab 100644 --- a/src/diff/mod.ts +++ b/src/diff/mod.ts @@ -1,2 +1,2 @@ -export * from './router'; -export * from './diff'; \ No newline at end of file +export * from "./diff"; +export * from "./router"; diff --git a/src/diff/router.ts b/src/diff/router.ts index ff929bb..63dc3ee 100644 --- a/src/diff/router.ts +++ b/src/diff/router.ts @@ -1,70 +1,70 @@ -import Koa from 'koa'; -import Router from 'koa-router'; -import { ContentFile } from '../content/mod'; -import { sendError } from '../route/error_handler'; -import {DiffManager} from './diff'; -import {AdminOnlyMiddleware} from '../permission/permission'; +import Koa from "koa"; +import Router from "koa-router"; +import { ContentFile } from "../content/mod"; +import { AdminOnlyMiddleware } from "../permission/permission"; +import { sendError } from "../route/error_handler"; +import { DiffManager } from "./diff"; -function content_file_to_return(x:ContentFile){ - return {path:x.path,type:x.type}; +function content_file_to_return(x: ContentFile) { + return { path: x.path, type: x.type }; } -export const getAdded = (diffmgr:DiffManager)=> (ctx:Koa.Context,next:Koa.Next)=>{ +export const getAdded = (diffmgr: DiffManager) => (ctx: Koa.Context, next: Koa.Next) => { const ret = diffmgr.getAdded(); - ctx.body = ret.map(x=>({ - type:x.type, - value:x.value.map(x=>({path:x.path,type:x.type})), + ctx.body = ret.map(x => ({ + type: x.type, + value: x.value.map(x => ({ path: x.path, type: x.type })), })); - ctx.type = 'json'; -} + ctx.type = "json"; +}; type PostAddedBody = { - type:string, - path:string, + type: string; + path: string; }[]; -function checkPostAddedBody(body: any): body is PostAddedBody{ - if(body instanceof Array){ - return body.map(x=> 'type' in x && 'path' in x).every(x=>x); +function checkPostAddedBody(body: any): body is PostAddedBody { + if (body instanceof Array) { + return body.map(x => "type" in x && "path" in x).every(x => x); } return false; } -export const postAdded = (diffmgr:DiffManager) => async (ctx:Router.IRouterContext,next:Koa.Next)=>{ +export const postAdded = (diffmgr: DiffManager) => async (ctx: Router.IRouterContext, next: Koa.Next) => { const reqbody = ctx.request.body; - if(!checkPostAddedBody(reqbody)){ - sendError(400,"format exception"); + if (!checkPostAddedBody(reqbody)) { + sendError(400, "format exception"); return; } - const allWork = reqbody.map(op=>diffmgr.commit(op.type,op.path)); + const allWork = reqbody.map(op => diffmgr.commit(op.type, op.path)); const results = await Promise.all(allWork); ctx.body = { - ok:true, - docs:results, - } - ctx.type = 'json'; -} -export const postAddedAll = (diffmgr: DiffManager) => async (ctx:Router.IRouterContext,next:Koa.Next) => { - if (!ctx.is('json')){ - sendError(400,"format exception"); + ok: true, + docs: results, + }; + ctx.type = "json"; +}; +export const postAddedAll = (diffmgr: DiffManager) => async (ctx: Router.IRouterContext, next: Koa.Next) => { + if (!ctx.is("json")) { + sendError(400, "format exception"); return; } - const reqbody = ctx.request.body as Record; - if(!("type" in reqbody)){ - sendError(400,"format exception: there is no \"type\""); + const reqbody = ctx.request.body as Record; + if (!("type" in reqbody)) { + sendError(400, "format exception: there is no \"type\""); return; } const t = reqbody["type"]; - if(typeof t !== "string"){ - sendError(400,"format exception: invalid type of \"type\""); + if (typeof t !== "string") { + sendError(400, "format exception: invalid type of \"type\""); return; } await diffmgr.commitAll(t); ctx.body = { - ok:true + ok: true, }; - ctx.type = 'json'; -} + ctx.type = "json"; +}; /* export const getNotWatched = (diffmgr : DiffManager)=> (ctx:Router.IRouterContext,next:Koa.Next)=>{ ctx.body = { @@ -74,10 +74,10 @@ export const getNotWatched = (diffmgr : DiffManager)=> (ctx:Router.IRouterContex ctx.type = 'json'; }*/ -export function createDiffRouter(diffmgr: DiffManager){ +export function createDiffRouter(diffmgr: DiffManager) { const ret = new Router(); - ret.get("/list",AdminOnlyMiddleware,getAdded(diffmgr)); - ret.post("/commit",AdminOnlyMiddleware,postAdded(diffmgr)); - ret.post("/commitall",AdminOnlyMiddleware,postAddedAll(diffmgr)); + ret.get("/list", AdminOnlyMiddleware, getAdded(diffmgr)); + ret.post("/commit", AdminOnlyMiddleware, postAdded(diffmgr)); + ret.post("/commitall", AdminOnlyMiddleware, postAddedAll(diffmgr)); return ret; -} \ No newline at end of file +} diff --git a/src/diff/watcher.ts b/src/diff/watcher.ts index fbac687..f64604e 100644 --- a/src/diff/watcher.ts +++ b/src/diff/watcher.ts @@ -1,25 +1,25 @@ -import { FSWatcher, watch } from 'fs'; -import { promises } from 'fs'; -import event from 'events'; -import { join } from 'path'; -import { DocumentAccessor } from '../model/doc'; +import event from "events"; +import { FSWatcher, watch } from "fs"; +import { promises } from "fs"; +import { join } from "path"; +import { DocumentAccessor } from "../model/doc"; const readdir = promises.readdir; -export interface DiffWatcherEvent{ - 'create':(path:string)=>void, - 'delete':(path:string)=>void, - 'change':(prev_path:string,cur_path:string)=>void, +export interface DiffWatcherEvent { + "create": (path: string) => void; + "delete": (path: string) => void; + "change": (prev_path: string, cur_path: string) => void; } export interface IDiffWatcher extends event.EventEmitter { - on(event:U,listener:DiffWatcherEvent[U]): this; - emit(event:U,...arg:Parameters): boolean; - setup(cntr:DocumentAccessor):Promise; + on(event: U, listener: DiffWatcherEvent[U]): this; + emit(event: U, ...arg: Parameters): boolean; + setup(cntr: DocumentAccessor): Promise; } -export function linkWatcher(fromWatcher :IDiffWatcher, toWatcher: IDiffWatcher){ - fromWatcher.on("create",p=>toWatcher.emit("create",p)); - fromWatcher.on("delete",p=>toWatcher.emit("delete",p)); - fromWatcher.on("change",(p,c)=>toWatcher.emit("change",p,c)); -} \ No newline at end of file +export function linkWatcher(fromWatcher: IDiffWatcher, toWatcher: IDiffWatcher) { + fromWatcher.on("create", p => toWatcher.emit("create", p)); + fromWatcher.on("delete", p => toWatcher.emit("delete", p)); + fromWatcher.on("change", (p, c) => toWatcher.emit("change", p, c)); +} diff --git a/src/diff/watcher/ComicConfig.schema.json b/src/diff/watcher/ComicConfig.schema.json index c2b29af..50196de 100644 --- a/src/diff/watcher/ComicConfig.schema.json +++ b/src/diff/watcher/ComicConfig.schema.json @@ -1 +1,12 @@ -{"$schema":"http://json-schema.org/draft-07/schema#","$ref":"#/definitions/ComicConfig","definitions":{"ComicConfig":{"type":"object","properties":{"watch":{"type":"array","items":{"type":"string"}},"$schema":{"type":"string"}},"required":["watch"],"additionalProperties":false}}} \ No newline at end of file +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$ref": "#/definitions/ComicConfig", + "definitions": { + "ComicConfig": { + "type": "object", + "properties": { "watch": { "type": "array", "items": { "type": "string" } }, "$schema": { "type": "string" } }, + "required": ["watch"], + "additionalProperties": false + } + } +} diff --git a/src/diff/watcher/ComicConfig.ts b/src/diff/watcher/ComicConfig.ts index ea34493..0ff864c 100644 --- a/src/diff/watcher/ComicConfig.ts +++ b/src/diff/watcher/ComicConfig.ts @@ -1,8 +1,7 @@ -import {ConfigManager} from '../../util/configRW'; -import ComicSchema from "./ComicConfig.schema.json" -export interface ComicConfig{ - watch:string[] +import { ConfigManager } from "../../util/configRW"; +import ComicSchema from "./ComicConfig.schema.json"; +export interface ComicConfig { + watch: string[]; } -export const ComicConfig = new ConfigManager("comic_config.json",{watch:[]},ComicSchema); - +export const ComicConfig = new ConfigManager("comic_config.json", { watch: [] }, ComicSchema); diff --git a/src/diff/watcher/comic_watcher.ts b/src/diff/watcher/comic_watcher.ts index e8fbee8..ebad62b 100644 --- a/src/diff/watcher/comic_watcher.ts +++ b/src/diff/watcher/comic_watcher.ts @@ -1,17 +1,16 @@ -import {IDiffWatcher, DiffWatcherEvent} from '../watcher'; -import {EventEmitter} from 'events'; -import { DocumentAccessor } from '../../model/doc'; -import { WatcherFilter } from './watcher_filter'; -import { RecursiveWatcher } from './recursive_watcher'; -import { ComicConfig } from './ComicConfig'; -import {WatcherCompositer} from './compositer' +import { EventEmitter } from "events"; +import { DocumentAccessor } from "../../model/doc"; +import { DiffWatcherEvent, IDiffWatcher } from "../watcher"; +import { ComicConfig } from "./ComicConfig"; +import { WatcherCompositer } from "./compositer"; +import { RecursiveWatcher } from "./recursive_watcher"; +import { WatcherFilter } from "./watcher_filter"; - -const createComicWatcherBase = (path:string)=> { - return new WatcherFilter(new RecursiveWatcher(path),(x)=>x.endsWith(".zip")); -} -export const createComicWatcher = ()=>{ +const createComicWatcherBase = (path: string) => { + return new WatcherFilter(new RecursiveWatcher(path), (x) => x.endsWith(".zip")); +}; +export const createComicWatcher = () => { const file = ComicConfig.get_config_file(); - console.log(`register comic ${file.watch.join(",")}`) - return new WatcherCompositer(file.watch.map(path=>createComicWatcherBase(path))); -} \ No newline at end of file + console.log(`register comic ${file.watch.join(",")}`); + return new WatcherCompositer(file.watch.map(path => createComicWatcherBase(path))); +}; diff --git a/src/diff/watcher/common_watcher.ts b/src/diff/watcher/common_watcher.ts index 7395d63..808645e 100644 --- a/src/diff/watcher/common_watcher.ts +++ b/src/diff/watcher/common_watcher.ts @@ -1,45 +1,44 @@ -import event from 'events'; -import {FSWatcher,watch,promises} from 'fs'; -import {IDiffWatcher, DiffWatcherEvent} from '../watcher'; -import {join} from 'path'; -import { DocumentAccessor } from '../../model/doc'; -import { setupHelp } from './util'; +import event from "events"; +import { FSWatcher, promises, watch } from "fs"; +import { join } from "path"; +import { DocumentAccessor } from "../../model/doc"; +import { DiffWatcherEvent, IDiffWatcher } from "../watcher"; +import { setupHelp } from "./util"; -const {readdir} = promises; +const { readdir } = promises; -export class CommonDiffWatcher extends event.EventEmitter implements IDiffWatcher{ - on(event:U,listener:DiffWatcherEvent[U]): this{ - return super.on(event,listener); +export class CommonDiffWatcher extends event.EventEmitter implements IDiffWatcher { + on(event: U, listener: DiffWatcherEvent[U]): this { + return super.on(event, listener); } - emit(event:U,...arg:Parameters): boolean{ - return super.emit(event,...arg); + emit(event: U, ...arg: Parameters): boolean { + return super.emit(event, ...arg); } - private _path:string; + private _path: string; private _watcher: FSWatcher; - constructor(path:string){ + constructor(path: string) { super(); this._path = path; - this._watcher = watch(this._path,{persistent: true, recursive:false},async (eventType,filename)=>{ - if(eventType === "rename"){ + this._watcher = watch(this._path, { persistent: true, recursive: false }, async (eventType, filename) => { + if (eventType === "rename") { const cur = await readdir(this._path); - //add - if(cur.includes(filename)){ - this.emit('create',join(this.path,filename)); - } - else{ - this.emit('delete',join(this.path,filename)) + // add + if (cur.includes(filename)) { + this.emit("create", join(this.path, filename)); + } else { + this.emit("delete", join(this.path, filename)); } } }); } async setup(cntr: DocumentAccessor): Promise { - await setupHelp(this,this.path,cntr); + await setupHelp(this, this.path, cntr); } - public get path(){ + public get path() { return this._path; } - watchClose(){ - this._watcher.close() + watchClose() { + this._watcher.close(); } -} \ No newline at end of file +} diff --git a/src/diff/watcher/compositer.ts b/src/diff/watcher/compositer.ts index 020fb70..e621ab8 100644 --- a/src/diff/watcher/compositer.ts +++ b/src/diff/watcher/compositer.ts @@ -2,23 +2,22 @@ import { EventEmitter } from "events"; import { DocumentAccessor } from "../../model/doc"; import { DiffWatcherEvent, IDiffWatcher, linkWatcher } from "../watcher"; - -export class WatcherCompositer extends EventEmitter implements IDiffWatcher{ - refWatchers : IDiffWatcher[]; - on(event:U,listener:DiffWatcherEvent[U]): this{ - return super.on(event,listener); +export class WatcherCompositer extends EventEmitter implements IDiffWatcher { + refWatchers: IDiffWatcher[]; + on(event: U, listener: DiffWatcherEvent[U]): this { + return super.on(event, listener); } - emit(event:U,...arg:Parameters): boolean{ - return super.emit(event,...arg); + emit(event: U, ...arg: Parameters): boolean { + return super.emit(event, ...arg); } - constructor(refWatchers:IDiffWatcher[]){ + constructor(refWatchers: IDiffWatcher[]) { super(); this.refWatchers = refWatchers; - for(const refWatcher of this.refWatchers){ - linkWatcher(refWatcher,this); + for (const refWatcher of this.refWatchers) { + linkWatcher(refWatcher, this); } } async setup(cntr: DocumentAccessor): Promise { - await Promise.all(this.refWatchers.map(x=>x.setup(cntr))); + await Promise.all(this.refWatchers.map(x => x.setup(cntr))); } -} \ No newline at end of file +} diff --git a/src/diff/watcher/recursive_watcher.ts b/src/diff/watcher/recursive_watcher.ts index 283942c..6afadce 100644 --- a/src/diff/watcher/recursive_watcher.ts +++ b/src/diff/watcher/recursive_watcher.ts @@ -1,61 +1,61 @@ -import {watch, FSWatcher} from 'chokidar'; -import { EventEmitter } from 'events'; -import { join } from 'path'; -import { DocumentAccessor } from '../../model/doc'; -import { DiffWatcherEvent, IDiffWatcher } from '../watcher'; -import { setupHelp, setupRecursive } from './util'; +import { FSWatcher, watch } from "chokidar"; +import { EventEmitter } from "events"; +import { join } from "path"; +import { DocumentAccessor } from "../../model/doc"; +import { DiffWatcherEvent, IDiffWatcher } from "../watcher"; +import { setupHelp, setupRecursive } from "./util"; -type RecursiveWatcherOption={ +type RecursiveWatcherOption = { /** @default true */ - watchFile?:boolean, + watchFile?: boolean; /** @default false */ - watchDir?:boolean, -} + watchDir?: boolean; +}; -export class RecursiveWatcher extends EventEmitter implements IDiffWatcher { - on(event:U,listener:DiffWatcherEvent[U]): this{ - return super.on(event,listener); +export class RecursiveWatcher extends EventEmitter implements IDiffWatcher { + on(event: U, listener: DiffWatcherEvent[U]): this { + return super.on(event, listener); } - emit(event:U,...arg:Parameters): boolean{ - return super.emit(event,...arg); + emit(event: U, ...arg: Parameters): boolean { + return super.emit(event, ...arg); } readonly path: string; - private watcher: FSWatcher + private watcher: FSWatcher; - constructor(path:string, option:RecursiveWatcherOption = { - watchDir:false, - watchFile:true, - }){ + constructor(path: string, option: RecursiveWatcherOption = { + watchDir: false, + watchFile: true, + }) { super(); this.path = path; - this.watcher = watch(path,{ - persistent:true, - ignoreInitial:true, - depth:100, + this.watcher = watch(path, { + persistent: true, + ignoreInitial: true, + depth: 100, }); option.watchFile ??= true; - if(option.watchFile){ - this.watcher.on("add",path=>{ + if (option.watchFile) { + this.watcher.on("add", path => { const cpath = path; - //console.log("add ", cpath); - this.emit("create",cpath); - }).on("unlink",path=>{ + // console.log("add ", cpath); + this.emit("create", cpath); + }).on("unlink", path => { const cpath = path; - //console.log("unlink ", cpath); - this.emit("delete",cpath); + // console.log("unlink ", cpath); + this.emit("delete", cpath); }); } - if(option.watchDir){ - this.watcher.on("addDir",path=>{ + if (option.watchDir) { + this.watcher.on("addDir", path => { const cpath = path; - this.emit("create",cpath); - }).on("unlinkDir",path=>{ + this.emit("create", cpath); + }).on("unlinkDir", path => { const cpath = path; - this.emit("delete",cpath); - }) + this.emit("delete", cpath); + }); } } async setup(cntr: DocumentAccessor): Promise { - await setupRecursive(this,this.path,cntr); + await setupRecursive(this, this.path, cntr); } } diff --git a/src/diff/watcher/util.ts b/src/diff/watcher/util.ts index 16e13d9..8e51d7f 100644 --- a/src/diff/watcher/util.ts +++ b/src/diff/watcher/util.ts @@ -1,35 +1,36 @@ import { EventEmitter } from "events"; import { promises } from "fs"; import { join } from "path"; -const {readdir} = promises; +const { readdir } = promises; import { DocumentAccessor } from "../../model/doc"; import { IDiffWatcher } from "../watcher"; - -function setupCommon(watcher:IDiffWatcher,basepath:string,initial_filenames:string[],cur:string[]){ - //Todo : reduce O(nm) to O(n+m) using hash map. +function setupCommon(watcher: IDiffWatcher, basepath: string, initial_filenames: string[], cur: string[]) { + // Todo : reduce O(nm) to O(n+m) using hash map. let added = cur.filter(x => !initial_filenames.includes(x)); - let deleted = initial_filenames.filter(x=>!cur.includes(x)); + let deleted = initial_filenames.filter(x => !cur.includes(x)); for (const it of added) { - const cpath = join(basepath,it); - watcher.emit('create',cpath); + const cpath = join(basepath, it); + watcher.emit("create", cpath); } - for (const it of deleted){ - const cpath = join(basepath,it); - watcher.emit('delete',cpath); + for (const it of deleted) { + const cpath = join(basepath, it); + watcher.emit("delete", cpath); } } -export async function setupHelp(watcher:IDiffWatcher,basepath:string,cntr:DocumentAccessor){ +export async function setupHelp(watcher: IDiffWatcher, basepath: string, cntr: DocumentAccessor) { const initial_document = await cntr.findByPath(basepath); - const initial_filenames = initial_document.map(x=>x.filename); + const initial_filenames = initial_document.map(x => x.filename); const cur = await readdir(basepath); - setupCommon(watcher,basepath,initial_filenames,cur); + setupCommon(watcher, basepath, initial_filenames, cur); } -export async function setupRecursive(watcher:IDiffWatcher,basepath:string,cntr:DocumentAccessor){ +export async function setupRecursive(watcher: IDiffWatcher, basepath: string, cntr: DocumentAccessor) { const initial_document = await cntr.findByPath(basepath); - const initial_filenames = initial_document.map(x=>x.filename); - const cur = await readdir(basepath,{withFileTypes:true}); - setupCommon(watcher,basepath,initial_filenames,cur.map(x=>x.name)); - await Promise.all([cur.filter(x=>x.isDirectory()) - .map(x=>setupHelp(watcher,join(basepath,x.name),cntr))]); + const initial_filenames = initial_document.map(x => x.filename); + const cur = await readdir(basepath, { withFileTypes: true }); + setupCommon(watcher, basepath, initial_filenames, cur.map(x => x.name)); + await Promise.all([ + cur.filter(x => x.isDirectory()) + .map(x => setupHelp(watcher, join(basepath, x.name), cntr)), + ]); } diff --git a/src/diff/watcher/watcher_filter.ts b/src/diff/watcher/watcher_filter.ts index e0bfb30..381b807 100644 --- a/src/diff/watcher/watcher_filter.ts +++ b/src/diff/watcher/watcher_filter.ts @@ -2,50 +2,45 @@ import { EventEmitter } from "events"; import { DocumentAccessor } from "../../model/doc"; import { DiffWatcherEvent, IDiffWatcher, linkWatcher } from "../watcher"; - -export class WatcherFilter extends EventEmitter implements IDiffWatcher{ - refWatcher : IDiffWatcher; - filter : (filename:string)=>boolean;; - on(event:U,listener:DiffWatcherEvent[U]): this{ - return super.on(event,listener); +export class WatcherFilter extends EventEmitter implements IDiffWatcher { + refWatcher: IDiffWatcher; + filter: (filename: string) => boolean; + on(event: U, listener: DiffWatcherEvent[U]): this { + return super.on(event, listener); } /** * emit event - * @param event - * @param arg + * @param event + * @param arg * @returns `true` if the event had listeners, `false` otherwise. */ - emit(event:U,...arg:Parameters): boolean{ - if(event === "change"){ + emit(event: U, ...arg: Parameters): boolean { + if (event === "change") { const prev = arg[0]; const cur = arg[1] as string; - if(this.filter(prev)){ - if(this.filter(cur)){ - return super.emit("change",prev,cur); + if (this.filter(prev)) { + if (this.filter(cur)) { + return super.emit("change", prev, cur); + } else { + return super.emit("delete", cur); } - else{ - return super.emit("delete",cur); - } - } - else{ - if(this.filter(cur)){ - return super.emit("create",cur); + } else { + if (this.filter(cur)) { + return super.emit("create", cur); } } return false; - } - else if(!this.filter(arg[0])){ + } else if (!this.filter(arg[0])) { return false; - } - else return super.emit(event,...arg); + } else return super.emit(event, ...arg); } - constructor(refWatcher:IDiffWatcher, filter:(filename:string)=>boolean){ + constructor(refWatcher: IDiffWatcher, filter: (filename: string) => boolean) { super(); this.refWatcher = refWatcher; this.filter = filter; - linkWatcher(refWatcher,this); + linkWatcher(refWatcher, this); } - setup(cntr:DocumentAccessor): Promise { + setup(cntr: DocumentAccessor): Promise { return this.refWatcher.setup(cntr); } -} \ No newline at end of file +} diff --git a/src/login.ts b/src/login.ts index 291fb7f..a3a1cbf 100644 --- a/src/login.ts +++ b/src/login.ts @@ -1,289 +1,285 @@ +import { request } from "http"; import { decode, sign, TokenExpiredError, verify } from "jsonwebtoken"; +import Knex from "knex"; import Koa from "koa"; import Router from "koa-router"; -import { sendError } from "./route/error_handler"; -import Knex from "knex"; import { createKnexUserController } from "./db/mod"; -import { request } from "http"; -import { get_setting } from "./SettingConfig"; import { IUser, UserAccessor } from "./model/mod"; +import { sendError } from "./route/error_handler"; +import { get_setting } from "./SettingConfig"; type PayloadInfo = { - username: string; - permission: string[]; + username: string; + permission: string[]; }; export type UserState = { - user: PayloadInfo; + user: PayloadInfo; }; const isUserState = (obj: object | string): obj is PayloadInfo => { - if (typeof obj === "string") return false; - return "username" in obj && "permission" in obj && - (obj as { permission: unknown }).permission instanceof Array; + if (typeof obj === "string") return false; + return "username" in obj && "permission" in obj + && (obj as { permission: unknown }).permission instanceof Array; }; type RefreshPayloadInfo = { username: string }; const isRefreshToken = (obj: object | string): obj is RefreshPayloadInfo => { - if (typeof obj === "string") return false; - return "username" in obj && - typeof (obj as { username: unknown }).username === "string"; + if (typeof obj === "string") return false; + return "username" in obj + && typeof (obj as { username: unknown }).username === "string"; }; export const accessTokenName = "access_token"; export const refreshTokenName = "refresh_token"; -const accessExpiredTime = 60 * 60; //1 hour +const accessExpiredTime = 60 * 60; // 1 hour const refreshExpiredTime = 60 * 60 * 24 * 14; // 14 day; export const getAdminAccessTokenValue = () => { - const { jwt_secretkey } = get_setting(); - return publishAccessToken(jwt_secretkey, "admin", [], accessExpiredTime); + const { jwt_secretkey } = get_setting(); + return publishAccessToken(jwt_secretkey, "admin", [], accessExpiredTime); }; export const getAdminRefreshTokenValue = () => { - const { jwt_secretkey } = get_setting(); - return publishRefreshToken(jwt_secretkey, "admin", refreshExpiredTime); + const { jwt_secretkey } = get_setting(); + return publishRefreshToken(jwt_secretkey, "admin", refreshExpiredTime); }; const publishAccessToken = ( - secretKey: string, - username: string, - permission: string[], - expiredtime: number, + secretKey: string, + username: string, + permission: string[], + expiredtime: number, ) => { - const payload = sign( - { - username: username, - permission: permission, - }, - secretKey, - { expiresIn: expiredtime }, - ); - return payload; + const payload = sign( + { + username: username, + permission: permission, + }, + secretKey, + { expiresIn: expiredtime }, + ); + return payload; }; const publishRefreshToken = ( - secretKey: string, - username: string, - expiredtime: number, + secretKey: string, + username: string, + expiredtime: number, ) => { - const payload = sign( - { username: username }, - secretKey, - { expiresIn: expiredtime }, - ); - return payload; + const payload = sign( + { username: username }, + secretKey, + { expiresIn: expiredtime }, + ); + return payload; }; function setToken( - ctx: Koa.Context, - token_name: string, - token_payload: string | null, - expiredtime: number, + ctx: Koa.Context, + token_name: string, + token_payload: string | null, + expiredtime: number, ) { - const setting = get_setting(); - if (token_payload === null && !!!ctx.cookies.get(token_name)) { - return; - } - ctx.cookies.set(token_name, token_payload, { - httpOnly: true, - secure: setting.secure, - sameSite: "strict", - expires: new Date(Date.now() + expiredtime * 1000), - }); -}; -export const createLoginMiddleware = (userController: UserAccessor) => - async (ctx: Koa.Context, _next: Koa.Next) => { + const setting = get_setting(); + if (token_payload === null && !!!ctx.cookies.get(token_name)) { + return; + } + ctx.cookies.set(token_name, token_payload, { + httpOnly: true, + secure: setting.secure, + sameSite: "strict", + expires: new Date(Date.now() + expiredtime * 1000), + }); +} +export const createLoginMiddleware = (userController: UserAccessor) => async (ctx: Koa.Context, _next: Koa.Next) => { const setting = get_setting(); const secretKey = setting.jwt_secretkey; const body = ctx.request.body; - //check format + // check format if (typeof body == "string" || !("username" in body) || !("password" in body)) { - return sendError( - 400, - "invalid form : username or password is not found in query.", - ); + return sendError( + 400, + "invalid form : username or password is not found in query.", + ); } const username = body["username"]; const password = body["password"]; - //check type + // check type if (typeof username !== "string" || typeof password !== "string") { - return sendError( - 400, - "invalid form : username or password is not string", - ); + return sendError( + 400, + "invalid form : username or password is not string", + ); } - //if admin login is forbidden? + // if admin login is forbidden? if (username === "admin" && setting.forbid_remote_admin_login) { - return sendError(403, "forbidden remote admin login"); + return sendError(403, "forbidden remote admin login"); } const user = await userController.findUser(username); - //username not exist + // username not exist if (user === undefined) return sendError(401, "not authorized"); - //password not matched + // password not matched if (!user.password.check_password(password)) { - return sendError(401, "not authorized"); + return sendError(401, "not authorized"); } - //create token + // create token const userPermission = await user.get_permissions(); const payload = publishAccessToken( - secretKey, - user.username, - userPermission, - accessExpiredTime, + secretKey, + user.username, + userPermission, + accessExpiredTime, ); const payload2 = publishRefreshToken( - secretKey, - user.username, - refreshExpiredTime, + secretKey, + user.username, + refreshExpiredTime, ); setToken(ctx, accessTokenName, payload, accessExpiredTime); setToken(ctx, refreshTokenName, payload2, refreshExpiredTime); ctx.body = { - username: user.username, - permission: userPermission, - accessExpired: (Math.floor(Date.now() / 1000) + accessExpiredTime), + username: user.username, + permission: userPermission, + accessExpired: (Math.floor(Date.now() / 1000) + accessExpiredTime), }; console.log(`${username} logined`); return; - }; +}; export const LogoutMiddleware = (ctx: Koa.Context, next: Koa.Next) => { - const setting = get_setting() - ctx.cookies.set(accessTokenName, null); - ctx.cookies.set(refreshTokenName, null); - ctx.body = { - ok: true, - username: "", - permission: setting.guest - }; - return; -}; -export const createUserMiddleWare = (userController: UserAccessor) => - async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { - const refreshToken = refreshTokenHandler(userController); const setting = get_setting(); - const setGuest = async () => { - setToken(ctx, accessTokenName, null, 0); - setToken(ctx, refreshTokenName, null, 0); - ctx.state["user"] = { username: "", permission: setting.guest }; - return await next(); + ctx.cookies.set(accessTokenName, null); + ctx.cookies.set(refreshTokenName, null); + ctx.body = { + ok: true, + username: "", + permission: setting.guest, }; - return await refreshToken(ctx, setGuest, next); - }; -const refreshTokenHandler = (cntr: UserAccessor) => - async (ctx: Koa.Context, fail: Koa.Next, next: Koa.Next) => { + return; +}; +export const createUserMiddleWare = + (userController: UserAccessor) => async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { + const refreshToken = refreshTokenHandler(userController); + const setting = get_setting(); + const setGuest = async () => { + setToken(ctx, accessTokenName, null, 0); + setToken(ctx, refreshTokenName, null, 0); + ctx.state["user"] = { username: "", permission: setting.guest }; + return await next(); + }; + return await refreshToken(ctx, setGuest, next); + }; +const refreshTokenHandler = (cntr: UserAccessor) => async (ctx: Koa.Context, fail: Koa.Next, next: Koa.Next) => { const accessPayload = ctx.cookies.get(accessTokenName); const setting = get_setting(); const secretKey = setting.jwt_secretkey; if (accessPayload == undefined) { - return await checkRefreshAndUpdate(); + return await checkRefreshAndUpdate(); } try { - const o = verify(accessPayload, secretKey); - if (isUserState(o)) { - ctx.state.user = o; - return await next(); - } else { - console.error("invalid token detected"); - throw new Error("token form invalid"); - } - } catch (e) { - if (e instanceof TokenExpiredError) { - return await checkRefreshAndUpdate(); - } else throw e; - } - async function checkRefreshAndUpdate() { - const refreshPayload = ctx.cookies.get(refreshTokenName); - if (refreshPayload === undefined) { - return await fail(); // refresh token doesn't exist - } else { - try { - const o = verify(refreshPayload, secretKey); - if (isRefreshToken(o)) { - const user = await cntr.findUser(o.username); - if (user === undefined) return await fail(); //already non-existence user - const perm = await user.get_permissions(); - const payload = publishAccessToken( - secretKey, - user.username, - perm, - accessExpiredTime, - ); - setToken(ctx, accessTokenName, payload, accessExpiredTime); - ctx.state.user = { username: o.username, permission: perm }; - } else { + const o = verify(accessPayload, secretKey); + if (isUserState(o)) { + ctx.state.user = o; + return await next(); + } else { console.error("invalid token detected"); throw new Error("token form invalid"); - } - } catch (e) { - if (e instanceof TokenExpiredError) { // refresh token is expired. - return await fail(); - } else throw e; } - } - return await next(); - }; - }; -export const createRefreshTokenMiddleware = (cntr: UserAccessor) => - async (ctx: Koa.Context, next: Koa.Next) => { + } catch (e) { + if (e instanceof TokenExpiredError) { + return await checkRefreshAndUpdate(); + } else throw e; + } + async function checkRefreshAndUpdate() { + const refreshPayload = ctx.cookies.get(refreshTokenName); + if (refreshPayload === undefined) { + return await fail(); // refresh token doesn't exist + } else { + try { + const o = verify(refreshPayload, secretKey); + if (isRefreshToken(o)) { + const user = await cntr.findUser(o.username); + if (user === undefined) return await fail(); // already non-existence user + const perm = await user.get_permissions(); + const payload = publishAccessToken( + secretKey, + user.username, + perm, + accessExpiredTime, + ); + setToken(ctx, accessTokenName, payload, accessExpiredTime); + ctx.state.user = { username: o.username, permission: perm }; + } else { + console.error("invalid token detected"); + throw new Error("token form invalid"); + } + } catch (e) { + if (e instanceof TokenExpiredError) { // refresh token is expired. + return await fail(); + } else throw e; + } + } + return await next(); + } +}; +export const createRefreshTokenMiddleware = (cntr: UserAccessor) => async (ctx: Koa.Context, next: Koa.Next) => { const handler = refreshTokenHandler(cntr); await handler(ctx, fail, success); async function fail() { - const user = ctx.state.user as PayloadInfo; - ctx.body = { - refresh: false, - ...user, - }; - ctx.type = "json"; - }; - async function success() { - const user = ctx.state.user as PayloadInfo; - ctx.body = { - ...user, - refresh: true, - refreshExpired: Math.floor(Date.now() / 1000 + accessExpiredTime), - }; - ctx.type = "json"; - }; - }; -export const resetPasswordMiddleware = (cntr: UserAccessor) => - async (ctx: Koa.Context, next: Koa.Next) => { - const body = ctx.request.body; - if (typeof body !== "object" || !('username' in body) || !('oldpassword' in body) || !('newpassword' in body)) { - return sendError(400, "request body is invalid format"); + const user = ctx.state.user as PayloadInfo; + ctx.body = { + refresh: false, + ...user, + }; + ctx.type = "json"; } - const username = body['username']; - const oldpw = body['oldpassword']; - const newpw = body['newpassword']; + async function success() { + const user = ctx.state.user as PayloadInfo; + ctx.body = { + ...user, + refresh: true, + refreshExpired: Math.floor(Date.now() / 1000 + accessExpiredTime), + }; + ctx.type = "json"; + } +}; +export const resetPasswordMiddleware = (cntr: UserAccessor) => async (ctx: Koa.Context, next: Koa.Next) => { + const body = ctx.request.body; + if (typeof body !== "object" || !("username" in body) || !("oldpassword" in body) || !("newpassword" in body)) { + return sendError(400, "request body is invalid format"); + } + const username = body["username"]; + const oldpw = body["oldpassword"]; + const newpw = body["newpassword"]; if (typeof username !== "string" || typeof oldpw !== "string" || typeof newpw !== "string") { - return sendError(400, "request body is invalid format"); + return sendError(400, "request body is invalid format"); } const user = await cntr.findUser(username); if (user === undefined) { - return sendError(403, "not authorized"); + return sendError(403, "not authorized"); } if (!user.password.check_password(oldpw)) { - return sendError(403, "not authorized"); + return sendError(403, "not authorized"); } user.reset_password(newpw); - ctx.body = { ok: true } - ctx.type = 'json'; - } + ctx.body = { ok: true }; + ctx.type = "json"; +}; export function createLoginRouter(userController: UserAccessor) { - const router = new Router(); - router.post('/login', createLoginMiddleware(userController)); - router.post('/logout', LogoutMiddleware); - router.post('/refresh', createRefreshTokenMiddleware(userController)); - router.post('/reset', resetPasswordMiddleware(userController)); - return router; + const router = new Router(); + router.post("/login", createLoginMiddleware(userController)); + router.post("/logout", LogoutMiddleware); + router.post("/refresh", createRefreshTokenMiddleware(userController)); + router.post("/reset", resetPasswordMiddleware(userController)); + return router; } export const getAdmin = async (cntr: UserAccessor) => { - const admin = await cntr.findUser("admin"); - if (admin === undefined) { - throw new Error("initial process failed!"); //??? - } - return admin; + const admin = await cntr.findUser("admin"); + if (admin === undefined) { + throw new Error("initial process failed!"); // ??? + } + return admin; }; export const isAdminFirst = (admin: IUser) => { - return admin.password.hash === "unchecked" && - admin.password.salt === "unchecked"; + return admin.password.hash === "unchecked" + && admin.password.salt === "unchecked"; }; diff --git a/src/model/doc.ts b/src/model/doc.ts index 398b826..7954de0 100644 --- a/src/model/doc.ts +++ b/src/model/doc.ts @@ -1,129 +1,129 @@ -import {TagAccessor} from './tag'; -import {check_type} from '../util/type_check' -import {JSONMap} from '../types/json'; +import { JSONMap } from "../types/json"; +import { check_type } from "../util/type_check"; +import { TagAccessor } from "./tag"; -export interface DocumentBody{ - title : string, - content_type : string, - basepath : string, - filename : string, - modified_at : number, - content_hash : string, - additional : JSONMap, - tags : string[],//eager loading +export interface DocumentBody { + title: string; + content_type: string; + basepath: string; + filename: string; + modified_at: number; + content_hash: string; + additional: JSONMap; + tags: string[]; // eager loading } export const MetaContentBody = { - title : "string", - content_type : "string", - basepath : "string", - filename : "string", - content_hash : "string", - additional : "object", - tags : "string[]", -} - -export const isDocBody = (c : any):c is DocumentBody =>{ - return check_type(c,MetaContentBody); -} - -export interface Document extends DocumentBody{ - readonly id: number; - readonly created_at:number; - readonly deleted_at:number|null; + title: "string", + content_type: "string", + basepath: "string", + filename: "string", + content_hash: "string", + additional: "object", + tags: "string[]", }; -export const isDoc = (c: any):c is Document =>{ - if('id' in c && typeof c['id'] === "number"){ - const {id, ...rest} = c; +export const isDocBody = (c: any): c is DocumentBody => { + return check_type(c, MetaContentBody); +}; + +export interface Document extends DocumentBody { + readonly id: number; + readonly created_at: number; + readonly deleted_at: number | null; +} + +export const isDoc = (c: any): c is Document => { + if ("id" in c && typeof c["id"] === "number") { + const { id, ...rest } = c; return isDocBody(rest); } return false; -} +}; export type QueryListOption = { /** * search word */ - word?:string, - allow_tag?:string[], + word?: string; + allow_tag?: string[]; /** * limit of list * @default 20 */ - limit?:number, + limit?: number; /** * use offset if true, otherwise * @default false */ - use_offset?:boolean, + use_offset?: boolean; /** * cursor of documents */ - cursor?:number, + cursor?: number; /** * offset of documents */ - offset?:number, + offset?: number; /** * tag eager loading * @default true */ - eager_loading?:boolean, + eager_loading?: boolean; /** * content type */ - content_type?:string -} + content_type?: string; +}; -export interface DocumentAccessor{ +export interface DocumentAccessor { /** * find list by option * @returns documents list */ - findList: (option?:QueryListOption)=>Promise, + findList: (option?: QueryListOption) => Promise; /** * @returns document if exist, otherwise undefined */ - findById: (id:number,tagload?:boolean)=> Promise, + findById: (id: number, tagload?: boolean) => Promise; /** * find by base path and filename. * if you call this function with filename, its return array length is 0 or 1. */ - findByPath:(basepath: string,filename?:string)=>Promise; + findByPath: (basepath: string, filename?: string) => Promise; /** * find deleted content */ - findDeleted:(content_type:string)=>Promise; + findDeleted: (content_type: string) => Promise; /** * search by in document */ - search:(search_word:string)=>Promise + search: (search_word: string) => Promise; /** * update document except tag. */ - update:(c:Partial & { id:number })=>Promise; + update: (c: Partial & { id: number }) => Promise; /** * add document */ - add:(c:DocumentBody)=>Promise; + add: (c: DocumentBody) => Promise; /** * add document list */ - addList:(content_list:DocumentBody[]) => Promise; + addList: (content_list: DocumentBody[]) => Promise; /** * delete document * @returns if it exists, return true. */ - del:(id:number)=>Promise; + del: (id: number) => Promise; /** * @param c Valid Document * @param tagname tag name to add * @returns if success, return true */ - addTag:(c:Document,tag_name:string)=>Promise; + addTag: (c: Document, tag_name: string) => Promise; /** * @returns if success, return true */ - delTag:(c:Document,tag_name:string)=>Promise; -}; \ No newline at end of file + delTag: (c: Document, tag_name: string) => Promise; +} diff --git a/src/model/mod.ts b/src/model/mod.ts index 747d980..820bde4 100644 --- a/src/model/mod.ts +++ b/src/model/mod.ts @@ -1,3 +1,3 @@ -export * from './doc'; -export * from './tag'; -export * from './user'; \ No newline at end of file +export * from "./doc"; +export * from "./tag"; +export * from "./user"; diff --git a/src/model/tag.ts b/src/model/tag.ts index 61881d5..74d945c 100644 --- a/src/model/tag.ts +++ b/src/model/tag.ts @@ -1,18 +1,18 @@ -export interface Tag{ - readonly name: string, - description?: string +export interface Tag { + readonly name: string; + description?: string; } -export interface TagCount{ +export interface TagCount { tag_name: string; occurs: number; } -export interface TagAccessor{ - getAllTagList: (onlyname?:boolean)=> Promise; +export interface TagAccessor { + getAllTagList: (onlyname?: boolean) => Promise; getAllTagCount(): Promise; - getTagByName: (name:string)=>Promise; - addTag: (tag:Tag)=>Promise; - delTag: (name:string) => Promise; - updateTag: (name:string,tag:string) => Promise; + getTagByName: (name: string) => Promise; + addTag: (tag: Tag) => Promise; + delTag: (name: string) => Promise; + updateTag: (name: string, tag: string) => Promise; } diff --git a/src/model/user.ts b/src/model/user.ts index 02b3f4c..eb7c14b 100644 --- a/src/model/user.ts +++ b/src/model/user.ts @@ -1,80 +1,84 @@ -import { createHmac, randomBytes } from 'crypto'; +import { createHmac, randomBytes } from "crypto"; -function hashForPassword(salt: string,password:string){ - return createHmac('sha256', salt).update(password).digest('hex') +function hashForPassword(salt: string, password: string) { + return createHmac("sha256", salt).update(password).digest("hex"); } -function createPasswordHashAndSalt(password: string):{salt:string,hash:string}{ - const secret = randomBytes(32).toString('hex'); +function createPasswordHashAndSalt(password: string): { salt: string; hash: string } { + const secret = randomBytes(32).toString("hex"); return { salt: secret, - hash: hashForPassword(secret,password) + hash: hashForPassword(secret, password), }; } -export class Password{ - private _salt:string; - private _hash:string; - constructor(pw : string|{salt:string,hash:string}){ - const {salt,hash} = typeof pw === "string" ? createPasswordHashAndSalt(pw) : pw; +export class Password { + private _salt: string; + private _hash: string; + constructor(pw: string | { salt: string; hash: string }) { + const { salt, hash } = typeof pw === "string" ? createPasswordHashAndSalt(pw) : pw; this._hash = hash; this._salt = salt; } - set_password(password: string){ - const {salt,hash} = createPasswordHashAndSalt(password); + set_password(password: string) { + const { salt, hash } = createPasswordHashAndSalt(password); this._hash = hash; this._salt = salt; } - check_password(password: string):boolean{ - return this._hash === hashForPassword(this._salt,password); + check_password(password: string): boolean { + return this._hash === hashForPassword(this._salt, password); + } + get salt() { + return this._salt; + } + get hash() { + return this._hash; } - get salt(){return this._salt;} - get hash(){return this._hash;} } -export interface UserCreateInput{ - username: string, - password: string +export interface UserCreateInput { + username: string; + password: string; } -export interface IUser{ - readonly username : string; - readonly password : Password; +export interface IUser { + readonly username: string; + readonly password: Password; /** * return user's permission list. */ - get_permissions():Promise; + get_permissions(): Promise; /** * add permission * @param name permission name to add * @returns if `name` doesn't exist, return true */ - add(name :string):Promise; + add(name: string): Promise; /** * remove permission * @param name permission name to remove * @returns if `name` exist, return true */ - remove(name :string):Promise; + remove(name: string): Promise; /** * reset password. * @param password password to set */ - reset_password(password: string):Promise; -}; + reset_password(password: string): Promise; +} -export interface UserAccessor{ +export interface UserAccessor { /** * create user * @returns if user exist, return undefined */ - createUser: (input :UserCreateInput)=> Promise, + createUser: (input: UserCreateInput) => Promise; /** * find user */ - findUser: (username: string)=> Promise, + findUser: (username: string) => Promise; /** * remove user * @returns if user exist, true */ - delUser: (username: string)=>Promise -}; \ No newline at end of file + delUser: (username: string) => Promise; +} diff --git a/src/permission/permission.ts b/src/permission/permission.ts index 6bee8ad..2d06225 100644 --- a/src/permission/permission.ts +++ b/src/permission/permission.ts @@ -1,60 +1,58 @@ -import Koa from 'koa'; -import { UserState } from '../login'; -import { sendError } from '../route/error_handler'; +import Koa from "koa"; +import { UserState } from "../login"; +import { sendError } from "../route/error_handler"; - -export enum Permission{ - //======== - //not implemented - //admin only +export enum Permission { + // ======== + // not implemented + // admin only /** remove document */ - //removeContent = 'removeContent', + // removeContent = 'removeContent', /** upload document */ - //uploadContent = 'uploadContent', + // uploadContent = 'uploadContent', /** modify document except base path, filename, content_hash. but admin can modify all. */ - //modifyContent = 'modifyContent', - + // modifyContent = 'modifyContent', + /** add tag into document */ - //addTagContent = 'addTagContent', + // addTagContent = 'addTagContent', /** remove tag from document */ - //removeTagContent = 'removeTagContent', + // removeTagContent = 'removeTagContent', /** ModifyTagInDoc */ - ModifyTag = 'ModifyTag', + ModifyTag = "ModifyTag", /** find documents with query */ - //findAllContent = 'findAllContent', + // findAllContent = 'findAllContent', /** find one document. */ - //findOneContent = 'findOneContent', + // findOneContent = 'findOneContent', /** view content*/ - //viewContent = 'viewContent', - QueryContent = 'QueryContent', - + // viewContent = 'viewContent', + QueryContent = "QueryContent", + /** modify description about the one tag. */ - modifyTagDesc = 'ModifyTagDesc', + modifyTagDesc = "ModifyTagDesc", } -export const createPermissionCheckMiddleware = (...permissions:string[]) => - async (ctx: Koa.ParameterizedContext,next:Koa.Next) => { - const user = ctx.state['user']; - if(user.username === "admin"){ - return await next(); - } - const user_permission = user.permission; - //if permissions is not subset of user permission - if(!permissions.map(p=>user_permission.includes(p)).every(x=>x)){ - if(user.username === ""){ - return sendError(401,"you are guest. login needed."); +export const createPermissionCheckMiddleware = + (...permissions: string[]) => async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { + const user = ctx.state["user"]; + if (user.username === "admin") { + return await next(); } - else return sendError(403,"do not have permission"); + const user_permission = user.permission; + // if permissions is not subset of user permission + if (!permissions.map(p => user_permission.includes(p)).every(x => x)) { + if (user.username === "") { + return sendError(401, "you are guest. login needed."); + } else return sendError(403, "do not have permission"); + } + await next(); + }; +export const AdminOnlyMiddleware = async (ctx: Koa.ParameterizedContext, next: Koa.Next) => { + const user = ctx.state["user"]; + if (user.username !== "admin") { + return sendError(403, "admin only"); } await next(); -} -export const AdminOnlyMiddleware = async (ctx: Koa.ParameterizedContext,next:Koa.Next)=>{ - const user = ctx.state['user']; - if(user.username !== "admin"){ - return sendError(403,"admin only"); - } - await next(); -} \ No newline at end of file +}; diff --git a/src/route/all.ts b/src/route/all.ts index 543e3ba..381226b 100644 --- a/src/route/all.ts +++ b/src/route/all.ts @@ -1,55 +1,57 @@ -import { DefaultContext, Middleware, Next, ParameterizedContext } from 'koa'; -import compose from 'koa-compose'; -import Router, { IParamMiddleware } from 'koa-router'; -import { ContentContext } from './context'; -import ComicRouter from './comic'; -import VideoRouter from './video'; +import { DefaultContext, Middleware, Next, ParameterizedContext } from "koa"; +import compose from "koa-compose"; +import Router, { IParamMiddleware } from "koa-router"; +import ComicRouter from "./comic"; +import { ContentContext } from "./context"; +import VideoRouter from "./video"; -const table:{[s:string]:Router|undefined} = { - "comic": new ComicRouter, - "video": new VideoRouter -} -const all_middleware = (cont: string|undefined, restarg: string|undefined)=>async (ctx:ParameterizedContext,next:Next)=>{ - if(cont == undefined){ - ctx.status = 404; - return; - } - if(ctx.state.location.type != cont){ - console.error("not matched") - ctx.status = 404; - return; - } - const router = table[cont]; - if(router == undefined){ - ctx.status = 404; - return; - } - const rest = "/"+(restarg ?? ""); - const result = router.match(rest,"GET"); - if(!result.route){ - return await next(); - } - const chain = result.pathAndMethod.reduce((combination : Middleware[],cur)=>{ - combination.push(async (ctx,next)=>{ - const captures = cur.captures(rest); - ctx.params = cur.params(rest,captures); - ctx.request.params = ctx.params; - ctx.routerPath = cur.path; - return await next(); - }); - return combination.concat(cur.stack); - },[]); - return await compose(chain)(ctx,next); +const table: { [s: string]: Router | undefined } = { + "comic": new ComicRouter(), + "video": new VideoRouter(), }; -export class AllContentRouter extends Router{ - constructor(){ +const all_middleware = + (cont: string | undefined, restarg: string | undefined) => + async (ctx: ParameterizedContext, next: Next) => { + if (cont == undefined) { + ctx.status = 404; + return; + } + if (ctx.state.location.type != cont) { + console.error("not matched"); + ctx.status = 404; + return; + } + const router = table[cont]; + if (router == undefined) { + ctx.status = 404; + return; + } + const rest = "/" + (restarg ?? ""); + const result = router.match(rest, "GET"); + if (!result.route) { + return await next(); + } + const chain = result.pathAndMethod.reduce((combination: Middleware[], cur) => { + combination.push(async (ctx, next) => { + const captures = cur.captures(rest); + ctx.params = cur.params(rest, captures); + ctx.request.params = ctx.params; + ctx.routerPath = cur.path; + return await next(); + }); + return combination.concat(cur.stack); + }, []); + return await compose(chain)(ctx, next); + }; +export class AllContentRouter extends Router { + constructor() { super(); - this.get('/:content_type',async (ctx,next)=>{ - return await (all_middleware(ctx.params["content_type"],undefined))(ctx,next); + this.get("/:content_type", async (ctx, next) => { + return await (all_middleware(ctx.params["content_type"], undefined))(ctx, next); }); - this.get('/:content_type/:rest(.*)', async (ctx,next) => { + this.get("/:content_type/:rest(.*)", async (ctx, next) => { const cont = ctx.params["content_type"] as string; - return await (all_middleware(cont,ctx.params["rest"]))(ctx,next); + return await (all_middleware(cont, ctx.params["rest"]))(ctx, next); }); } -}; \ No newline at end of file +} diff --git a/src/route/comic.ts b/src/route/comic.ts index 9f46cd1..875fd02 100644 --- a/src/route/comic.ts +++ b/src/route/comic.ts @@ -1,13 +1,8 @@ import { Context, DefaultContext, DefaultState, Next } from "koa"; -import { - createReadableStreamFromZip, - entriesByNaturalOrder, - readZip, - ZipAsync, -} from "../util/zipwrap"; -import { since_last_modified } from "./util"; -import { ContentContext } from "./context"; import Router from "koa-router"; +import { createReadableStreamFromZip, entriesByNaturalOrder, readZip, ZipAsync } from "../util/zipwrap"; +import { ContentContext } from "./context"; +import { since_last_modified } from "./util"; /** * zip stream cache. @@ -16,88 +11,86 @@ import Router from "koa-router"; let ZipStreamCache: { [path: string]: [ZipAsync, number] } = {}; async function acquireZip(path: string) { - if (!(path in ZipStreamCache)) { - const ret = await readZip(path); - ZipStreamCache[path] = [ret, 1]; - //console.log(`acquire ${path} 1`); - return ret; - } - else { - const [ret, refCount] = ZipStreamCache[path]; - ZipStreamCache[path] = [ret, refCount + 1]; - //console.log(`acquire ${path} ${refCount + 1}`); - return ret; - } + if (!(path in ZipStreamCache)) { + const ret = await readZip(path); + ZipStreamCache[path] = [ret, 1]; + // console.log(`acquire ${path} 1`); + return ret; + } else { + const [ret, refCount] = ZipStreamCache[path]; + ZipStreamCache[path] = [ret, refCount + 1]; + // console.log(`acquire ${path} ${refCount + 1}`); + return ret; + } } function releaseZip(path: string) { - const obj = ZipStreamCache[path]; - if (obj === undefined) throw new Error("error! key invalid"); - const [ref, refCount] = obj; - //console.log(`release ${path} : ${refCount}`); - if (refCount === 1) { - ref.close(); - delete ZipStreamCache[path]; - } - else{ - ZipStreamCache[path] = [ref, refCount - 1]; - } + const obj = ZipStreamCache[path]; + if (obj === undefined) throw new Error("error! key invalid"); + const [ref, refCount] = obj; + // console.log(`release ${path} : ${refCount}`); + if (refCount === 1) { + ref.close(); + delete ZipStreamCache[path]; + } else { + ZipStreamCache[path] = [ref, refCount - 1]; + } } async function renderZipImage(ctx: Context, path: string, page: number) { - const image_ext = ["gif", "png", "jpeg", "bmp", "webp", "jpg"]; - //console.log(`opened ${page}`); - let zip = await acquireZip(path); - const entries = (await entriesByNaturalOrder(zip)).filter((x) => { - const ext = x.name.split(".").pop(); - return ext !== undefined && image_ext.includes(ext); - }); - if (0 <= page && page < entries.length) { - const entry = entries[page]; - const last_modified = new Date(entry.time); - if (since_last_modified(ctx, last_modified)) { - return; - } - const read_stream = (await createReadableStreamFromZip(zip, entry)); - /**Exceptions (ECONNRESET, ECONNABORTED) may be thrown when processing this request - * for reasons such as when the browser unexpectedly closes the connection. - * Once such an exception is raised, the stream is not properly destroyed, - * so there is a problem with the zlib stream being accessed even after the stream is closed. - * So it waits for 100 ms and releases it. - * Additionaly, there is a risk of memory leak becuase zlib stream is not properly destroyed. - * @todo modify function 'stream' in 'node-stream-zip' library to prevent memory leak*/ - read_stream.once("close", () => { - setTimeout(() => { - releaseZip(path); - }, 100); + const image_ext = ["gif", "png", "jpeg", "bmp", "webp", "jpg"]; + // console.log(`opened ${page}`); + let zip = await acquireZip(path); + const entries = (await entriesByNaturalOrder(zip)).filter((x) => { + const ext = x.name.split(".").pop(); + return ext !== undefined && image_ext.includes(ext); }); + if (0 <= page && page < entries.length) { + const entry = entries[page]; + const last_modified = new Date(entry.time); + if (since_last_modified(ctx, last_modified)) { + return; + } + const read_stream = await createReadableStreamFromZip(zip, entry); + /** Exceptions (ECONNRESET, ECONNABORTED) may be thrown when processing this request + * for reasons such as when the browser unexpectedly closes the connection. + * Once such an exception is raised, the stream is not properly destroyed, + * so there is a problem with the zlib stream being accessed even after the stream is closed. + * So it waits for 100 ms and releases it. + * Additionaly, there is a risk of memory leak becuase zlib stream is not properly destroyed. + * @todo modify function 'stream' in 'node-stream-zip' library to prevent memory leak */ + read_stream.once("close", () => { + setTimeout(() => { + releaseZip(path); + }, 100); + }); - ctx.body = read_stream; - ctx.response.length = entry.size; - //console.log(`${entry.name}'s ${page}:${entry.size}`); - ctx.response.type = entry.name.split(".").pop() as string; - ctx.status = 200; - ctx.set("Date", new Date().toUTCString()); - ctx.set("Last-Modified", last_modified.toUTCString()); - } else { - ctx.status = 404; - } + ctx.body = read_stream; + ctx.response.length = entry.size; + // console.log(`${entry.name}'s ${page}:${entry.size}`); + ctx.response.type = entry.name.split(".").pop() as string; + ctx.status = 200; + ctx.set("Date", new Date().toUTCString()); + ctx.set("Last-Modified", last_modified.toUTCString()); + } else { + ctx.status = 404; + } } export class ComicRouter extends Router { - constructor() { - super(); - this.get("/", async (ctx, next) => { - await renderZipImage(ctx, ctx.state.location.path, 0); - }); - this.get("/:page(\\d+)", async (ctx, next) => { - const page = Number.parseInt(ctx.params["page"]); - await renderZipImage(ctx, ctx.state.location.path, page); - }); - this.get("/thumbnail", async (ctx, next) => { - await renderZipImage(ctx, ctx.state.location.path, 0); - }); - } + constructor() { + super(); + this.get("/", async (ctx, next) => { + await renderZipImage(ctx, ctx.state.location.path, 0); + }); + this.get("/:page(\\d+)", async (ctx, next) => { + const page = Number.parseInt(ctx.params["page"]); + await renderZipImage(ctx, ctx.state.location.path, page); + }); + this.get("/thumbnail", async (ctx, next) => { + await renderZipImage(ctx, ctx.state.location.path, 0); + }); + } } export default ComicRouter; diff --git a/src/route/contents.ts b/src/route/contents.ts index f19f378..1f737a1 100644 --- a/src/route/contents.ts +++ b/src/route/contents.ts @@ -1,63 +1,68 @@ -import { Context, Next } from 'koa'; -import Router from 'koa-router'; -import {Document, DocumentAccessor, isDocBody} from '../model/doc'; -import {QueryListOption} from '../model/doc'; -import {ParseQueryNumber, ParseQueryArray, ParseQueryBoolean, ParseQueryArgString} from './util' -import {sendError} from './error_handler'; -import { join } from 'path'; -import {AllContentRouter} from './all'; -import {createPermissionCheckMiddleware as PerCheck, Permission as Per, AdminOnlyMiddleware as AdminOnly} from '../permission/permission'; -import {ContentLocation} from './context' +import { Context, Next } from "koa"; +import Router from "koa-router"; +import { join } from "path"; +import { Document, DocumentAccessor, isDocBody } from "../model/doc"; +import { QueryListOption } from "../model/doc"; +import { + AdminOnlyMiddleware as AdminOnly, + createPermissionCheckMiddleware as PerCheck, + Permission as Per, +} from "../permission/permission"; +import { AllContentRouter } from "./all"; +import { ContentLocation } from "./context"; +import { sendError } from "./error_handler"; +import { ParseQueryArgString, ParseQueryArray, ParseQueryBoolean, ParseQueryNumber } from "./util"; -const ContentIDHandler = (controller: DocumentAccessor) => async (ctx: Context,next: Next)=>{ - const num = Number.parseInt(ctx.params['num']); - let document = await controller.findById(num,true); - if (document == undefined){ - return sendError(404,"document does not exist."); +const ContentIDHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { + const num = Number.parseInt(ctx.params["num"]); + let document = await controller.findById(num, true); + if (document == undefined) { + return sendError(404, "document does not exist."); } ctx.body = document; - ctx.type = 'json'; + ctx.type = "json"; console.log(document.additional); }; -const ContentTagIDHandler = (controller: DocumentAccessor) => async (ctx: Context,next: Next)=>{ - const num = Number.parseInt(ctx.params['num']); - let document = await controller.findById(num,true); - if (document == undefined){ - return sendError(404,"document does not exist."); +const ContentTagIDHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { + const num = Number.parseInt(ctx.params["num"]); + let document = await controller.findById(num, true); + if (document == undefined) { + return sendError(404, "document does not exist."); } ctx.body = document.tags; - ctx.type = 'json'; + ctx.type = "json"; }; -const ContentQueryHandler = (controller : DocumentAccessor) => async (ctx: Context,next: Next)=>{ - - let query_limit = (ctx.query['limit']); - let query_cursor = (ctx.query['cursor']); - let query_word = (ctx.query['word']); - let query_content_type = (ctx.query['content_type']); - let query_offset = (ctx.query['offset']); - let query_use_offset = ctx.query['use_offset']; - if(query_limit instanceof Array - || query_cursor instanceof Array - || query_word instanceof Array +const ContentQueryHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { + let query_limit = ctx.query["limit"]; + let query_cursor = ctx.query["cursor"]; + let query_word = ctx.query["word"]; + let query_content_type = ctx.query["content_type"]; + let query_offset = ctx.query["offset"]; + let query_use_offset = ctx.query["use_offset"]; + if ( + query_limit instanceof Array + || query_cursor instanceof Array + || query_word instanceof Array || query_content_type instanceof Array || query_offset instanceof Array - || query_use_offset instanceof Array){ - return sendError(400,"paramter can not be array"); + || query_use_offset instanceof Array + ) { + return sendError(400, "paramter can not be array"); } const limit = ParseQueryNumber(query_limit); const cursor = ParseQueryNumber(query_cursor); const word = ParseQueryArgString(query_word); const content_type = ParseQueryArgString(query_content_type); const offset = ParseQueryNumber(query_offset); - if(limit === NaN || cursor === NaN || offset === NaN){ - return sendError(400,"parameter limit, cursor or offset is not a number"); + if (limit === NaN || cursor === NaN || offset === NaN) { + return sendError(400, "parameter limit, cursor or offset is not a number"); } - const allow_tag = ParseQueryArray(ctx.query['allow_tag']); - const [ok,use_offset] = ParseQueryBoolean(query_use_offset); - if(!ok){ - return sendError(400,"use_offset must be true or false."); + const allow_tag = ParseQueryArray(ctx.query["allow_tag"]); + const [ok, use_offset] = ParseQueryBoolean(query_use_offset); + if (!ok) { + return sendError(400, "use_offset must be true or false."); } - const option :QueryListOption = { + const option: QueryListOption = { limit: limit, allow_tag: allow_tag, word: word, @@ -69,93 +74,94 @@ const ContentQueryHandler = (controller : DocumentAccessor) => async (ctx: Conte }; let document = await controller.findList(option); ctx.body = document; - ctx.type = 'json'; -} -const UpdateContentHandler = (controller : DocumentAccessor) => async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params['num']); + ctx.type = "json"; +}; +const UpdateContentHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { + const num = Number.parseInt(ctx.params["num"]); - if(ctx.request.type !== 'json'){ - return sendError(400,"update fail. invalid document type: it is not json."); + if (ctx.request.type !== "json") { + return sendError(400, "update fail. invalid document type: it is not json."); } - if(typeof ctx.request.body !== "object"){ - return sendError(400,"update fail. invalid argument: not"); + if (typeof ctx.request.body !== "object") { + return sendError(400, "update fail. invalid argument: not"); } - const content_desc: Partial & {id: number} = { - id:num,...ctx.request.body + const content_desc: Partial & { id: number } = { + id: num, + ...ctx.request.body, }; const success = await controller.update(content_desc); ctx.body = JSON.stringify(success); - ctx.type = 'json'; -} - -const AddTagHandler = (controller: DocumentAccessor)=>async (ctx: Context, next: Next)=>{ - let tag_name = ctx.params['tag']; - const num = Number.parseInt(ctx.params['num']); - if(typeof tag_name === undefined){ - return sendError(400,"??? Unreachable"); - } - tag_name = String(tag_name); - const c = await controller.findById(num); - if(c === undefined){ - return sendError(404); - } - const r = await controller.addTag(c,tag_name); - ctx.body = JSON.stringify(r); - ctx.type = 'json'; + ctx.type = "json"; }; -const DelTagHandler = (controller: DocumentAccessor)=>async (ctx: Context, next: Next)=>{ - let tag_name = ctx.params['tag']; - const num = Number.parseInt(ctx.params['num']); - if(typeof tag_name === undefined){ - return sendError(400,"?? Unreachable"); + +const AddTagHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { + let tag_name = ctx.params["tag"]; + const num = Number.parseInt(ctx.params["num"]); + if (typeof tag_name === undefined) { + return sendError(400, "??? Unreachable"); } tag_name = String(tag_name); const c = await controller.findById(num); - if(c === undefined){ + if (c === undefined) { return sendError(404); } - const r = await controller.delTag(c,tag_name); + const r = await controller.addTag(c, tag_name); ctx.body = JSON.stringify(r); - ctx.type = 'json'; -} -const DeleteContentHandler = (controller : DocumentAccessor) => async (ctx: Context, next: Next) => { - const num = Number.parseInt(ctx.params['num']); + ctx.type = "json"; +}; +const DelTagHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { + let tag_name = ctx.params["tag"]; + const num = Number.parseInt(ctx.params["num"]); + if (typeof tag_name === undefined) { + return sendError(400, "?? Unreachable"); + } + tag_name = String(tag_name); + const c = await controller.findById(num); + if (c === undefined) { + return sendError(404); + } + const r = await controller.delTag(c, tag_name); + ctx.body = JSON.stringify(r); + ctx.type = "json"; +}; +const DeleteContentHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { + const num = Number.parseInt(ctx.params["num"]); const r = await controller.del(num); ctx.body = JSON.stringify(r); - ctx.type = 'json'; + ctx.type = "json"; }; -const ContentHandler = (controller : DocumentAccessor) => async (ctx:Context, next:Next) => { - const num = Number.parseInt(ctx.params['num']); - let document = await controller.findById(num,true); - if (document == undefined){ - return sendError(404,"document does not exist."); +const ContentHandler = (controller: DocumentAccessor) => async (ctx: Context, next: Next) => { + const num = Number.parseInt(ctx.params["num"]); + let document = await controller.findById(num, true); + if (document == undefined) { + return sendError(404, "document does not exist."); } - if(document.deleted_at !== null){ - return sendError(404,"document has been removed."); + if (document.deleted_at !== null) { + return sendError(404, "document has been removed."); } - const path = join(document.basepath,document.filename); - ctx.state['location'] = { - path:path, - type:document.content_type, - additional:document.additional, + const path = join(document.basepath, document.filename); + ctx.state["location"] = { + path: path, + type: document.content_type, + additional: document.additional, } as ContentLocation; await next(); }; -export const getContentRouter = (controller: DocumentAccessor)=>{ +export const getContentRouter = (controller: DocumentAccessor) => { const ret = new Router(); - ret.get("/search",PerCheck(Per.QueryContent),ContentQueryHandler(controller)); - ret.get("/:num(\\d+)",PerCheck(Per.QueryContent),ContentIDHandler(controller)); - ret.post("/:num(\\d+)",AdminOnly,UpdateContentHandler(controller)); - //ret.use("/:num(\\d+)/:content_type"); - //ret.post("/",AdminOnly,CreateContentHandler(controller)); - ret.get("/:num(\\d+)/tags",PerCheck(Per.QueryContent),ContentTagIDHandler(controller)); - ret.post("/:num(\\d+)/tags/:tag",PerCheck(Per.ModifyTag),AddTagHandler(controller)); - ret.del("/:num(\\d+)/tags/:tag",PerCheck(Per.ModifyTag),DelTagHandler(controller)); - ret.del("/:num(\\d+)",AdminOnly,DeleteContentHandler(controller)); - ret.all("/:num(\\d+)/(.*)",PerCheck(Per.QueryContent),ContentHandler(controller)); - ret.use("/:num(\\d+)",PerCheck(Per.QueryContent),(new AllContentRouter).routes()); + ret.get("/search", PerCheck(Per.QueryContent), ContentQueryHandler(controller)); + ret.get("/:num(\\d+)", PerCheck(Per.QueryContent), ContentIDHandler(controller)); + ret.post("/:num(\\d+)", AdminOnly, UpdateContentHandler(controller)); + // ret.use("/:num(\\d+)/:content_type"); + // ret.post("/",AdminOnly,CreateContentHandler(controller)); + ret.get("/:num(\\d+)/tags", PerCheck(Per.QueryContent), ContentTagIDHandler(controller)); + ret.post("/:num(\\d+)/tags/:tag", PerCheck(Per.ModifyTag), AddTagHandler(controller)); + ret.del("/:num(\\d+)/tags/:tag", PerCheck(Per.ModifyTag), DelTagHandler(controller)); + ret.del("/:num(\\d+)", AdminOnly, DeleteContentHandler(controller)); + ret.all("/:num(\\d+)/(.*)", PerCheck(Per.QueryContent), ContentHandler(controller)); + ret.use("/:num(\\d+)", PerCheck(Per.QueryContent), (new AllContentRouter()).routes()); return ret; -} +}; -export default getContentRouter; \ No newline at end of file +export default getContentRouter; diff --git a/src/route/context.ts b/src/route/context.ts index eea6123..19e44d9 100644 --- a/src/route/context.ts +++ b/src/route/context.ts @@ -1,8 +1,8 @@ export type ContentLocation = { - path:string, - type:string, - additional:object|undefined, + path: string; + type: string; + additional: object | undefined; +}; +export interface ContentContext { + location: ContentLocation; } -export interface ContentContext{ - location:ContentLocation -} \ No newline at end of file diff --git a/src/route/error_handler.ts b/src/route/error_handler.ts index 3cc0a76..5d3e84f 100644 --- a/src/route/error_handler.ts +++ b/src/route/error_handler.ts @@ -1,50 +1,49 @@ -import {Context, Next} from 'koa'; +import { Context, Next } from "koa"; export interface ErrorFormat { - code: number, - message: string, - detail?: string + code: number; + message: string; + detail?: string; } -class ClientRequestError implements Error{ +class ClientRequestError implements Error { name: string; message: string; stack?: string | undefined; code: number; - constructor(code : number,message: string){ + constructor(code: number, message: string) { this.name = "client request error"; this.message = message; this.code = code; } } -const code_to_message_table:{[key:number]:string|undefined} = { - 400:"BadRequest", - 404:"NotFound" -} +const code_to_message_table: { [key: number]: string | undefined } = { + 400: "BadRequest", + 404: "NotFound", +}; -export const error_handler = async (ctx:Context,next: Next)=>{ +export const error_handler = async (ctx: Context, next: Next) => { try { await next(); } catch (err) { - if(err instanceof ClientRequestError){ - const body : ErrorFormat= { + if (err instanceof ClientRequestError) { + const body: ErrorFormat = { code: err.code, message: code_to_message_table[err.code] ?? "", - detail: err.message - } + detail: err.message, + }; ctx.status = err.code; ctx.body = body; - } - else{ + } else { throw err; } } -} +}; -export const sendError = (code:number,message?:string) =>{ - throw new ClientRequestError(code,message ?? ""); -} +export const sendError = (code: number, message?: string) => { + throw new ClientRequestError(code, message ?? ""); +}; -export default error_handler; \ No newline at end of file +export default error_handler; diff --git a/src/route/tags.ts b/src/route/tags.ts index 2d243bb..aa86d67 100644 --- a/src/route/tags.ts +++ b/src/route/tags.ts @@ -1,32 +1,29 @@ -import {Context, Next} from "koa"; -import Router,{RouterContext} from "koa-router"; +import { Context, Next } from "koa"; +import Router, { RouterContext } from "koa-router"; import { TagAccessor } from "../model/tag"; -import { sendError } from "./error_handler"; import { createPermissionCheckMiddleware as PerCheck, Permission } from "../permission/permission"; +import { sendError } from "./error_handler"; -export function getTagRounter(tagController: TagAccessor){ +export function getTagRounter(tagController: TagAccessor) { let router = new Router(); - router.get("/",PerCheck(Permission.QueryContent), - async (ctx: Context)=>{ - if(ctx.query["withCount"]){ - const c = await tagController.getAllTagCount(); - ctx.body = c; - } - else { - const c = await tagController.getAllTagList(); - ctx.body = c; - } - ctx.type = "json"; - }); - router.get("/:tag_name", PerCheck(Permission.QueryContent), - async (ctx: RouterContext)=>{ - const tag_name = ctx.params["tag_name"]; - const c = await tagController.getTagByName(tag_name); - if (!c){ - sendError(404, "tags not found"); - } + router.get("/", PerCheck(Permission.QueryContent), async (ctx: Context) => { + if (ctx.query["withCount"]) { + const c = await tagController.getAllTagCount(); ctx.body = c; - ctx.type = "json"; - }); + } else { + const c = await tagController.getAllTagList(); + ctx.body = c; + } + ctx.type = "json"; + }); + router.get("/:tag_name", PerCheck(Permission.QueryContent), async (ctx: RouterContext) => { + const tag_name = ctx.params["tag_name"]; + const c = await tagController.getTagByName(tag_name); + if (!c) { + sendError(404, "tags not found"); + } + ctx.body = c; + ctx.type = "json"; + }); return router; -} \ No newline at end of file +} diff --git a/src/route/util.ts b/src/route/util.ts index 41687bd..8a64a41 100644 --- a/src/route/util.ts +++ b/src/route/util.ts @@ -1,39 +1,37 @@ +import { Context } from "koa"; -import {Context} from 'koa'; - - -export function ParseQueryNumber(s: string[] | string|undefined): number| undefined{ - if(s === undefined) return undefined; - else if(typeof s === "object") return undefined; +export function ParseQueryNumber(s: string[] | string | undefined): number | undefined { + if (s === undefined) return undefined; + else if (typeof s === "object") return undefined; else return Number.parseInt(s); } -export function ParseQueryArray(s: string[]|string|undefined){ +export function ParseQueryArray(s: string[] | string | undefined) { s = s ?? []; const r = s instanceof Array ? s : [s]; - return r.map(x=>decodeURIComponent(x)); + return r.map(x => decodeURIComponent(x)); } -export function ParseQueryArgString(s: string[]|string|undefined){ - if(typeof s === "object") return undefined; +export function ParseQueryArgString(s: string[] | string | undefined) { + if (typeof s === "object") return undefined; return s === undefined ? s : decodeURIComponent(s); } -export function ParseQueryBoolean(s: string[] |string|undefined): [boolean,boolean|undefined]{ - let value:boolean|undefined; +export function ParseQueryBoolean(s: string[] | string | undefined): [boolean, boolean | undefined] { + let value: boolean | undefined; - if(s === "true") + if (s === "true") { value = true; - else if(s === "false") + } else if (s === "false") { value = false; - else if(s === undefined) + } else if (s === undefined) { value = undefined; - else return [false,undefined] - return [true,value] + } else return [false, undefined]; + return [true, value]; } -export function since_last_modified(ctx: Context, last_modified: Date): boolean{ +export function since_last_modified(ctx: Context, last_modified: Date): boolean { const con = ctx.get("If-Modified-Since"); - if(con === "") return false; + if (con === "") return false; const mdate = new Date(con); - if(last_modified > mdate) return false; + if (last_modified > mdate) return false; ctx.status = 304; return true; } diff --git a/src/route/video.ts b/src/route/video.ts index 2ab431e..f52499d 100644 --- a/src/route/video.ts +++ b/src/route/video.ts @@ -1,13 +1,13 @@ -import {Context } from 'koa'; -import {promises, createReadStream} from "fs"; -import {ContentContext} from './context'; -import Router from 'koa-router'; +import { createReadStream, promises } from "fs"; +import { Context } from "koa"; +import Router from "koa-router"; +import { ContentContext } from "./context"; -export async function renderVideo(ctx: Context,path : string){ - const ext = path.trim().split('.').pop(); - if(ext === undefined) { - //ctx.status = 404; - console.error(`${path}:${ext}`) +export async function renderVideo(ctx: Context, path: string) { + const ext = path.trim().split(".").pop(); + if (ext === undefined) { + // ctx.status = 404; + console.error(`${path}:${ext}`); return; } ctx.response.type = ext; @@ -15,13 +15,13 @@ export async function renderVideo(ctx: Context,path : string){ const stat = await promises.stat(path); let start = 0; let end = 0; - ctx.set('Last-Modified',(new Date(stat.mtime).toUTCString())); - ctx.set('Date', new Date().toUTCString()); + ctx.set("Last-Modified", new Date(stat.mtime).toUTCString()); + ctx.set("Date", new Date().toUTCString()); ctx.set("Accept-Ranges", "bytes"); - if(range_text === ''){ - end = 1024*512; - end = Math.min(end,stat.size-1); - if(start > end){ + if (range_text === "") { + end = 1024 * 512; + end = Math.min(end, stat.size - 1); + if (start > end) { ctx.status = 416; return; } @@ -29,40 +29,39 @@ export async function renderVideo(ctx: Context,path : string){ ctx.length = stat.size; let stream = createReadStream(path); ctx.body = stream; - } - else{ + } else { const m = range_text.match(/^bytes=(\d+)-(\d*)/); - if(m === null){ + if (m === null) { ctx.status = 416; return; } start = parseInt(m[1]); - end = m[2].length > 0 ? parseInt(m[2]) : start + 1024*1024; - end = Math.min(end,stat.size-1); - if(start > end){ + end = m[2].length > 0 ? parseInt(m[2]) : start + 1024 * 1024; + end = Math.min(end, stat.size - 1); + if (start > end) { ctx.status = 416; return; } ctx.status = 206; ctx.length = end - start + 1; - ctx.response.set("Content-Range",`bytes ${start}-${end}/${stat.size}`); - ctx.body = createReadStream(path,{ - start:start, - end:end - });//inclusive range. + ctx.response.set("Content-Range", `bytes ${start}-${end}/${stat.size}`); + ctx.body = createReadStream(path, { + start: start, + end: end, + }); // inclusive range. } } -export class VideoRouter extends Router{ - constructor(){ +export class VideoRouter extends Router { + constructor() { super(); - this.get("/", async (ctx,next)=>{ - await renderVideo(ctx,ctx.state.location.path); + this.get("/", async (ctx, next) => { + await renderVideo(ctx, ctx.state.location.path); + }); + this.get("/thumbnail", async (ctx, next) => { + await renderVideo(ctx, ctx.state.location.path); }); - this.get("/thumbnail", async (ctx,next)=>{ - await renderVideo(ctx,ctx.state.location.path); - }) } } -export default VideoRouter; \ No newline at end of file +export default VideoRouter; diff --git a/src/search/indexer.ts b/src/search/indexer.ts index 06091ed..f3c129b 100644 --- a/src/search/indexer.ts +++ b/src/search/indexer.ts @@ -1,13 +1,12 @@ - -export interface PaginationOption{ - cursor:number; - limit:number; +export interface PaginationOption { + cursor: number; + limit: number; } -export interface IIndexer{ - indexDoc(word:string,doc_id:number):boolean; - indexDoc(word:string[],doc_id:number):boolean; +export interface IIndexer { + indexDoc(word: string, doc_id: number): boolean; + indexDoc(word: string[], doc_id: number): boolean; - getDoc(word:string,option?:PaginationOption):number[]; - getDoc(word:string[],option?:PaginationOption):number[]; -} \ No newline at end of file + getDoc(word: string, option?: PaginationOption): number[]; + getDoc(word: string[], option?: PaginationOption): number[]; +} diff --git a/src/search/tokenizer.ts b/src/search/tokenizer.ts index 5d57b77..cb7f8ad 100644 --- a/src/search/tokenizer.ts +++ b/src/search/tokenizer.ts @@ -1,10 +1,9 @@ - -export interface ITokenizer{ - tokenize(s:string):string[]; +export interface ITokenizer { + tokenize(s: string): string[]; } -export class DefaultTokenizer implements ITokenizer{ +export class DefaultTokenizer implements ITokenizer { tokenize(s: string): string[] { return s.split(" "); } -} \ No newline at end of file +} diff --git a/src/server.ts b/src/server.ts index 85b0a57..23590b0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,55 +1,55 @@ -import Koa from 'koa'; -import Router from 'koa-router'; +import Koa from "koa"; +import Router from "koa-router"; -import {get_setting, SettingConfig} from './SettingConfig'; -import {connectDB} from './database'; -import {DiffManager, createDiffRouter} from './diff/mod'; +import { connectDB } from "./database"; +import { createDiffRouter, DiffManager } from "./diff/mod"; +import { get_setting, SettingConfig } from "./SettingConfig"; -import { createReadStream, readFileSync } from 'fs'; -import getContentRouter from './route/contents'; -import { createKnexDocumentAccessor, createKnexTagController, createKnexUserController } from './db/mod'; -import bodyparser from 'koa-bodyparser'; -import {error_handler} from './route/error_handler'; -import {createUserMiddleWare, createLoginRouter, isAdminFirst, getAdmin} from './login'; +import { createReadStream, readFileSync } from "fs"; +import bodyparser from "koa-bodyparser"; +import { createKnexDocumentAccessor, createKnexTagController, createKnexUserController } from "./db/mod"; +import { createLoginRouter, createUserMiddleWare, getAdmin, isAdminFirst } from "./login"; +import getContentRouter from "./route/contents"; +import { error_handler } from "./route/error_handler"; -import {createInterface as createReadlineInterface} from 'readline'; -import { DocumentAccessor, UserAccessor, TagAccessor } from './model/mod'; -import { createComicWatcher } from './diff/watcher/comic_watcher'; -import { getTagRounter } from './route/tags'; +import { createInterface as createReadlineInterface } from "readline"; +import { createComicWatcher } from "./diff/watcher/comic_watcher"; +import { DocumentAccessor, TagAccessor, UserAccessor } from "./model/mod"; +import { getTagRounter } from "./route/tags"; - -class ServerApplication{ +class ServerApplication { readonly userController: UserAccessor; readonly documentController: DocumentAccessor; readonly tagController: TagAccessor; readonly diffManger: DiffManager; readonly app: Koa; - private index_html:string; - private constructor(controller:{ - userController: UserAccessor, - documentController:DocumentAccessor, - tagController: TagAccessor}){ + private index_html: string; + private constructor(controller: { + userController: UserAccessor; + documentController: DocumentAccessor; + tagController: TagAccessor; + }) { this.userController = controller.userController; this.documentController = controller.documentController; this.tagController = controller.tagController; - + this.diffManger = new DiffManager(this.documentController); this.app = new Koa(); - this.index_html = readFileSync("index.html","utf-8"); + this.index_html = readFileSync("index.html", "utf-8"); } - private async setup(){ + private async setup() { const setting = get_setting(); const app = this.app; - if(setting.cli){ + if (setting.cli) { const userAdmin = await getAdmin(this.userController); - if(await isAdminFirst(userAdmin)){ + if (await isAdminFirst(userAdmin)) { const rl = createReadlineInterface({ - input:process.stdin, - output:process.stdout + input: process.stdin, + output: process.stdout, }); - const pw = await new Promise((res:(data:string)=>void,err)=>{ - rl.question("put admin password :",(data)=>{ + const pw = await new Promise((res: (data: string) => void, err) => { + rl.question("put admin password :", (data) => { res(data); }); }); @@ -60,167 +60,168 @@ class ServerApplication{ app.use(bodyparser()); app.use(error_handler); app.use(createUserMiddleWare(this.userController)); - + let diff_router = createDiffRouter(this.diffManger); - this.diffManger.register("comic",createComicWatcher()); - + this.diffManger.register("comic", createComicWatcher()); + console.log("setup router"); - + let router = new Router(); - router.use("/api/(.*)", async (ctx,next)=>{ - //For CORS + router.use("/api/(.*)", async (ctx, next) => { + // For CORS ctx.res.setHeader("access-control-allow-origin", "*"); await next(); }); - router.use('/api/diff',diff_router.routes()); - router.use('/api/diff',diff_router.allowedMethods()); - + router.use("/api/diff", diff_router.routes()); + router.use("/api/diff", diff_router.allowedMethods()); + const content_router = getContentRouter(this.documentController); - router.use('/api/doc',content_router.routes()); - router.use('/api/doc',content_router.allowedMethods()); - + router.use("/api/doc", content_router.routes()); + router.use("/api/doc", content_router.allowedMethods()); + const tags_router = getTagRounter(this.tagController); - router.use("/api/tags",tags_router.allowedMethods()); - router.use("/api/tags",tags_router.routes()); - - - + router.use("/api/tags", tags_router.allowedMethods()); + router.use("/api/tags", tags_router.routes()); + this.serve_with_meta_index(router); this.serve_index(router); this.serve_static_file(router); - const login_router = createLoginRouter(this.userController); - router.use('/user',login_router.routes()); - router.use('/user',login_router.allowedMethods()); - - - if(setting.mode == "development"){ - let mm_count = 0; - app.use(async (ctx,next)=>{ - console.log(`==========================${mm_count++}`); - const ip = (ctx.get("X-Real-IP")) ?? ctx.ip; - const fromClient = ctx.state['user'].username === "" ? ip : ctx.state['user'].username; - console.log(`${fromClient} : ${ctx.method} ${ctx.url}`); - await next(); - //console.log(`404`); - });} + router.use("/user", login_router.routes()); + router.use("/user", login_router.allowedMethods()); + + if (setting.mode == "development") { + let mm_count = 0; + app.use(async (ctx, next) => { + console.log(`==========================${mm_count++}`); + const ip = (ctx.get("X-Real-IP")) ?? ctx.ip; + const fromClient = ctx.state["user"].username === "" ? ip : ctx.state["user"].username; + console.log(`${fromClient} : ${ctx.method} ${ctx.url}`); + await next(); + // console.log(`404`); + }); + } app.use(router.routes()); app.use(router.allowedMethods()); console.log("setup done"); } - private serve_index(router:Router){ - const serveindex = (url:string)=>{ - router.get(url, (ctx)=>{ - ctx.type = 'html'; ctx.body = this.index_html; + private serve_index(router: Router) { + const serveindex = (url: string) => { + router.get(url, (ctx) => { + ctx.type = "html"; + ctx.body = this.index_html; const setting = get_setting(); - ctx.set('x-content-type-options','no-sniff'); - if(setting.mode === "development"){ - ctx.set('cache-control','no-cache'); + ctx.set("x-content-type-options", "no-sniff"); + if (setting.mode === "development") { + ctx.set("cache-control", "no-cache"); + } else { + ctx.set("cache-control", "public, max-age=3600"); } - else{ - ctx.set('cache-control','public, max-age=3600'); - } - }) - } - serveindex('/'); - serveindex('/doc/:rest(.*)'); - serveindex('/search'); - serveindex('/login'); - serveindex('/profile'); - serveindex('/difference'); - serveindex('/setting'); - serveindex('/tags'); + }); + }; + serveindex("/"); + serveindex("/doc/:rest(.*)"); + serveindex("/search"); + serveindex("/login"); + serveindex("/profile"); + serveindex("/difference"); + serveindex("/setting"); + serveindex("/tags"); } - private serve_with_meta_index(router:Router){ - const DocMiddleware = async (ctx: Koa.ParameterizedContext)=>{ + private serve_with_meta_index(router: Router) { + const DocMiddleware = async (ctx: Koa.ParameterizedContext) => { const docId = Number.parseInt(ctx.params["id"]); - const doc = await this.documentController.findById(docId,true); + const doc = await this.documentController.findById(docId, true); let meta; - if(doc === undefined){ + if (doc === undefined) { ctx.status = 404; - meta = NotFoundContent(); - } - else { + meta = NotFoundContent(); + } else { ctx.status = 200; - meta = createOgTagContent(doc.title,doc.tags.join(", "), - `https://aeolian.prelude.duckdns.org/api/doc/${docId}/comic/thumbnail`); + meta = createOgTagContent( + doc.title, + doc.tags.join(", "), + `https://aeolian.prelude.duckdns.org/api/doc/${docId}/comic/thumbnail`, + ); } - const html = makeMetaTagInjectedHTML(this.index_html,meta); - serveHTML(ctx,html); + const html = makeMetaTagInjectedHTML(this.index_html, meta); + serveHTML(ctx, html); + }; + router.get("/doc/:id(\\d+)", DocMiddleware); + + function NotFoundContent() { + return createOgTagContent("Not Found Doc", "Not Found", ""); } - router.get('/doc/:id(\\d+)',DocMiddleware); - - function NotFoundContent(){ - return createOgTagContent("Not Found Doc","Not Found",""); + function makeMetaTagInjectedHTML(html: string, tagContent: string) { + return html.replace("", tagContent); } - function makeMetaTagInjectedHTML(html:string,tagContent:string){ - return html.replace("",tagContent); - } - function serveHTML(ctx: Koa.Context, file: string){ - ctx.type = 'html'; ctx.body = file; + function serveHTML(ctx: Koa.Context, file: string) { + ctx.type = "html"; + ctx.body = file; const setting = get_setting(); - ctx.set('x-content-type-options','no-sniff'); - if(setting.mode === "development"){ - ctx.set('cache-control','no-cache'); - } - else{ - ctx.set('cache-control','public, max-age=3600'); + ctx.set("x-content-type-options", "no-sniff"); + if (setting.mode === "development") { + ctx.set("cache-control", "no-cache"); + } else { + ctx.set("cache-control", "public, max-age=3600"); } } - - function createMetaTagContent(key: string, value:string){ + + function createMetaTagContent(key: string, value: string) { return ``; } - function createOgTagContent(title:string,description: string, image: string){ - return [createMetaTagContent("og:title",title), - createMetaTagContent("og:type","website"), - createMetaTagContent("og:description",description), - createMetaTagContent("og:image",image), - //createMetaTagContent("og:image:width","480"), - //createMetaTagContent("og:image","480"), - //createMetaTagContent("og:image:type","image/png"), - createMetaTagContent("twitter:card","summary_large_image"), - createMetaTagContent("twitter:title",title), - createMetaTagContent("twitter:description",description), - createMetaTagContent("twitter:image",image), - ].join("\n"); + function createOgTagContent(title: string, description: string, image: string) { + return [ + createMetaTagContent("og:title", title), + createMetaTagContent("og:type", "website"), + createMetaTagContent("og:description", description), + createMetaTagContent("og:image", image), + // createMetaTagContent("og:image:width","480"), + // createMetaTagContent("og:image","480"), + // createMetaTagContent("og:image:type","image/png"), + createMetaTagContent("twitter:card", "summary_large_image"), + createMetaTagContent("twitter:title", title), + createMetaTagContent("twitter:description", description), + createMetaTagContent("twitter:image", image), + ].join("\n"); } } - private serve_static_file(router: Router){ - const static_file_server = (path:string,type:string) => { - router.get('/'+path,async (ctx,next)=>{ + private serve_static_file(router: Router) { + const static_file_server = (path: string, type: string) => { + router.get("/" + path, async (ctx, next) => { const setting = get_setting(); - ctx.type = type; ctx.body = createReadStream(path); - ctx.set('x-content-type-options','no-sniff'); - if(setting.mode === "development"){ - ctx.set('cache-control','no-cache'); + ctx.type = type; + ctx.body = createReadStream(path); + ctx.set("x-content-type-options", "no-sniff"); + if (setting.mode === "development") { + ctx.set("cache-control", "no-cache"); + } else { + ctx.set("cache-control", "public, max-age=3600"); } - else{ - ctx.set('cache-control','public, max-age=3600'); - } - })}; + }); + }; const setting = get_setting(); - static_file_server('dist/bundle.css','css'); - static_file_server('dist/bundle.js','js'); - if(setting.mode === "development"){ - static_file_server('dist/bundle.js.map','text'); - static_file_server('dist/bundle.css.map','text'); + static_file_server("dist/bundle.css", "css"); + static_file_server("dist/bundle.js", "js"); + if (setting.mode === "development") { + static_file_server("dist/bundle.js.map", "text"); + static_file_server("dist/bundle.css.map", "text"); } } - start_server(){ + start_server() { let setting = get_setting(); - //todo : support https + // todo : support https console.log(`listen on http://${setting.localmode ? "localhost" : "0.0.0.0"}:${setting.port}`); - return this.app.listen(setting.port,setting.localmode ? "127.0.0.1" : "0.0.0.0"); + return this.app.listen(setting.port, setting.localmode ? "127.0.0.1" : "0.0.0.0"); } - static async createServer(){ + static async createServer() { const setting = get_setting(); let db = await connectDB(); const app = new ServerApplication({ - userController:createKnexUserController(db), + userController: createKnexUserController(db), documentController: createKnexDocumentAccessor(db), tagController: createKnexTagController(db), }); @@ -229,8 +230,8 @@ class ServerApplication{ } } -export async function create_server(){ +export async function create_server() { return await ServerApplication.createServer(); } -export default {create_server}; \ No newline at end of file +export default { create_server }; diff --git a/src/types/db.d.ts b/src/types/db.d.ts index 4171e95..4e2fdda 100644 --- a/src/types/db.d.ts +++ b/src/types/db.d.ts @@ -1,34 +1,34 @@ -import {Knex} from "knex"; +import { Knex } from "knex"; declare module "knex" { - interface Tables { - tags: { - name: string; - description?: string; - }; - users: { - username: string; - password_hash: string; - password_salt: string; - }; - document: { - id: number; - title: string; - content_type: string; - basepath: string; - filename: string; - created_at: number; - deleted_at: number|null; - content_hash: string; - additional: string|null; - }; - doc_tag_relation: { - doc_id: number; - tag_name: string; - }; - permissions: { - username: string; - name: string; - }; - } + interface Tables { + tags: { + name: string; + description?: string; + }; + users: { + username: string; + password_hash: string; + password_salt: string; + }; + document: { + id: number; + title: string; + content_type: string; + basepath: string; + filename: string; + created_at: number; + deleted_at: number | null; + content_hash: string; + additional: string | null; + }; + doc_tag_relation: { + doc_id: number; + tag_name: string; + }; + permissions: { + username: string; + name: string; + }; + } } diff --git a/src/types/json.ts b/src/types/json.ts index f927d45..b774877 100644 --- a/src/types/json.ts +++ b/src/types/json.ts @@ -1,5 +1,4 @@ - -export type JSONPrimitive = null|boolean|number|string; -export interface JSONMap extends Record{} -export interface JSONArray extends Array{}; -export type JSONType = JSONMap|JSONPrimitive|JSONArray; \ No newline at end of file +export type JSONPrimitive = null | boolean | number | string; +export interface JSONMap extends Record {} +export interface JSONArray extends Array {} +export type JSONType = JSONMap | JSONPrimitive | JSONArray; diff --git a/src/util/configRW.ts b/src/util/configRW.ts index c2db258..f0700f9 100644 --- a/src/util/configRW.ts +++ b/src/util/configRW.ts @@ -1,26 +1,26 @@ -import {readFileSync, existsSync, writeFileSync, promises as fs} from 'fs'; -import {validate} from 'jsonschema'; +import { existsSync, promises as fs, readFileSync, writeFileSync } from "fs"; +import { validate } from "jsonschema"; -export class ConfigManager{ - path:string; +export class ConfigManager { + path: string; default_config: T; - config: T| null; - schema:object; - constructor(path:string,default_config:T,schema:object){ + config: T | null; + schema: object; + constructor(path: string, default_config: T, schema: object) { this.path = path; this.default_config = default_config; this.config = null; this.schema = schema; } - get_config_file(): T{ - if(this.config !== null) return this.config; - this.config = {...this.read_config_file()}; + get_config_file(): T { + if (this.config !== null) return this.config; + this.config = { ...this.read_config_file() }; return this.config; } - private emptyToDefault(target:T){ + private emptyToDefault(target: T) { let occur = false; - for(const key in this.default_config){ - if(key === undefined || key in target){ + for (const key in this.default_config) { + if (key === undefined || key in target) { continue; } target[key] = this.default_config[key]; @@ -28,24 +28,24 @@ export class ConfigManager{ } return occur; } - read_config_file():T{ - if(!existsSync(this.path)){ - writeFileSync(this.path,JSON.stringify(this.default_config)); + read_config_file(): T { + if (!existsSync(this.path)) { + writeFileSync(this.path, JSON.stringify(this.default_config)); return this.default_config; } - const ret = JSON.parse(readFileSync(this.path,{encoding:"utf8"})); - if(this.emptyToDefault(ret)){ - writeFileSync(this.path,JSON.stringify(ret)); + const ret = JSON.parse(readFileSync(this.path, { encoding: "utf8" })); + if (this.emptyToDefault(ret)) { + writeFileSync(this.path, JSON.stringify(ret)); } - const result = validate(ret,this.schema); - if(!result.valid){ + const result = validate(ret, this.schema); + if (!result.valid) { throw new Error(result.toString()); } return ret; } - async write_config_file(new_config:T){ + async write_config_file(new_config: T) { this.config = new_config; - await fs.writeFile(`${this.path}.temp`,JSON.stringify(new_config)); - await fs.rename(`${this.path}.temp`,this.path); + await fs.writeFile(`${this.path}.temp`, JSON.stringify(new_config)); + await fs.rename(`${this.path}.temp`, this.path); } -} \ No newline at end of file +} diff --git a/src/util/type_check.ts b/src/util/type_check.ts index b85f915..c45a2b1 100644 --- a/src/util/type_check.ts +++ b/src/util/type_check.ts @@ -1,16 +1,15 @@ -export function check_type(obj: any,check_proto:Record):obj is T{ +export function check_type(obj: any, check_proto: Record): obj is T { for (const it in check_proto) { let defined = check_proto[it]; - if(defined === undefined) return false; + if (defined === undefined) return false; defined = defined.trim(); - if(defined.endsWith("[]")){ - if(!(obj[it] instanceof Array)){ + if (defined.endsWith("[]")) { + if (!(obj[it] instanceof Array)) { return false; } - } - else if(defined !== typeof obj[it]){ + } else if (defined !== typeof obj[it]) { return false; } } return true; -}; \ No newline at end of file +} diff --git a/src/util/zipwrap.ts b/src/util/zipwrap.ts index 0457f12..2de4529 100644 --- a/src/util/zipwrap.ts +++ b/src/util/zipwrap.ts @@ -1,31 +1,33 @@ -import { ZipEntry } from 'node-stream-zip'; +import { ZipEntry } from "node-stream-zip"; -import {orderBy} from 'natural-orderby'; -import { ReadStream } from 'fs'; -import StreamZip from 'node-stream-zip'; +import { ReadStream } from "fs"; +import { orderBy } from "natural-orderby"; +import StreamZip from "node-stream-zip"; export type ZipAsync = InstanceType; -export async function readZip(path : string): Promise{ +export async function readZip(path: string): Promise { return new StreamZip.async({ - file:path, - storeEntries: true + file: path, + storeEntries: true, }); } -export async function entriesByNaturalOrder(zip: ZipAsync){ +export async function entriesByNaturalOrder(zip: ZipAsync) { const entries = await zip.entries(); - const ret = orderBy(Object.values(entries),v=>v.name); + const ret = orderBy(Object.values(entries), v => v.name); return ret; } -export async function createReadableStreamFromZip(zip:ZipAsync,entry: ZipEntry):Promise{ +export async function createReadableStreamFromZip(zip: ZipAsync, entry: ZipEntry): Promise { return await zip.stream(entry); } -export async function readAllFromZip(zip:ZipAsync,entry: ZipEntry):Promise{ - const stream = await createReadableStreamFromZip(zip,entry); - const chunks:Uint8Array[] = []; - return new Promise((resolve,reject)=>{ - stream.on('data',(data)=>{chunks.push(data)}); - stream.on('error', (err)=>reject(err)); - stream.on('end',()=>resolve(Buffer.concat(chunks))); +export async function readAllFromZip(zip: ZipAsync, entry: ZipEntry): Promise { + const stream = await createReadableStreamFromZip(zip, entry); + const chunks: Uint8Array[] = []; + return new Promise((resolve, reject) => { + stream.on("data", (data) => { + chunks.push(data); + }); + stream.on("error", (err) => reject(err)); + stream.on("end", () => resolve(Buffer.concat(chunks))); }); -} \ No newline at end of file +} diff --git a/tsconfig.json b/tsconfig.json index 5ed2e68..4b13120 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,17 +4,17 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ - "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - "lib": ["DOM","ES6"], /* Specify library files to be included in the compilation. */ - //"allowJs": true, /* Allow javascript files to be compiled. */ + "target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "lib": ["DOM", "ES6"], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ - "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./build", /* Redirect output structure to the directory. */ + "outDir": "./build", /* Redirect output structure to the directory. */ // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ @@ -25,13 +25,13 @@ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ - "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - "strictNullChecks": true, /* Enable strict null checks. */ + "strict": true, /* Enable all strict type-checking options. */ + "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ @@ -41,14 +41,14 @@ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ - "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "resolveJsonModule": true, // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ @@ -64,9 +64,9 @@ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ - "skipLibCheck": true, /* Skip type checking of declaration files. */ - "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ }, "include": ["./"], - "exclude": ["src/client","app","seeds"], + "exclude": ["src/client", "app", "seeds"] }