From ba867428e2247933524c3d30e91c92f03e0f2ac5 Mon Sep 17 00:00:00 2001 From: monoid Date: Tue, 26 Mar 2024 23:58:26 +0900 Subject: [PATCH] init monorepo --- .gitignore | 4 +- app.ts | 143 - biome.jsonc | 21 + gen_conf_schema.ts | 48 - migrations/initial.ts | 54 - package.json | 90 +- packages/client/.eslintrc.cjs | 18 + packages/client/.gitignore | 24 + packages/client/README.md | 30 + packages/client/index.html | 13 + packages/client/package.json | 28 + packages/client/public/vite.svg | 1 + packages/client/src/App.css | 0 packages/client/src/App.tsx | 59 + packages/client/src/accessor/document.ts | 99 + packages/client/src/accessor/util.ts | 28 + packages/client/src/assets/react.svg | 1 + packages/client/src/component/contentinfo.tsx | 238 + packages/client/src/component/headline.tsx | 273 + packages/client/src/component/loading.tsx | 10 + .../client/src}/component/mod.ts | 0 packages/client/src/component/navlist.tsx | 54 + packages/client/src/component/pagepad.tsx | 5 + packages/client/src/component/tagchip.tsx | 80 + packages/client/src/index.css | 0 packages/client/src/main.tsx | 10 + .../client/src}/page/404.tsx | 16 +- packages/client/src/page/contentinfo.tsx | 136 + packages/client/src/page/difference.tsx | 126 + packages/client/src/page/gallery.tsx | 133 + packages/client/src/page/login.tsx | 90 + .../client/src}/page/mod.ts | 0 packages/client/src/page/profile.tsx | 149 + packages/client/src/page/reader/comic.tsx | 83 + packages/client/src/page/reader/reader.tsx | 80 + .../client/src}/page/reader/thumbnail.css | 0 packages/client/src/page/reader/video.tsx | 14 + packages/client/src/page/setting.tsx | 17 + packages/client/src/page/tags.tsx | 76 + packages/client/src/state.tsx | 94 + packages/client/src/vite-env.d.ts | 1 + packages/client/tsconfig.json | 25 + packages/client/tsconfig.node.json | 11 + packages/client/vite.config.ts | 7 + packages/dbtype/package.json | 18 + packages/dbtype/types.ts | 53 + packages/server/app.ts | 145 + packages/server/gen_conf_schema.ts | 50 + index.html => packages/server/index.html | 0 knexfile.js => packages/server/knexfile.js | 0 packages/server/migrations/initial.ts | 56 + packages/server/package.json | 38 + packages/server/preload.ts | 7 + packages/server/src/SettingConfig.schema.json | 51 + packages/server/src/SettingConfig.ts | 79 + packages/server/src/config.ts | 22 + packages/server/src/content/comic.ts | 66 + packages/server/src/content/file.ts | 93 + {src => packages/server/src}/content/mod.ts | 0 {src => packages/server/src}/content/video.ts | 6 +- packages/server/src/database.ts | 47 + packages/server/src/db/doc.ts | 235 + {src => packages/server/src}/db/mod.ts | 0 packages/server/src/db/tag.ts | 61 + packages/server/src/db/user.ts | 88 + packages/server/src/diff/content_handler.ts | 121 + packages/server/src/diff/content_list.ts | 59 + packages/server/src/diff/diff.ts | 45 + {src => packages/server/src}/diff/mod.ts | 0 packages/server/src/diff/router.ts | 83 + packages/server/src/diff/watcher.ts | 25 + .../src/diff/watcher/ComicConfig.schema.json | 12 + .../server/src}/diff/watcher/ComicConfig.ts | 2 +- .../server/src}/diff/watcher/comic_watcher.ts | 8 +- .../server/src/diff/watcher/common_watcher.ts | 44 + .../server/src/diff/watcher/compositer.ts | 23 + .../src/diff/watcher/recursive_watcher.ts | 68 + packages/server/src/diff/watcher/util.ts | 38 + .../server/src/diff/watcher/watcher_filter.ts | 46 + packages/server/src/login.ts | 245 + packages/server/src/model/doc.ts | 129 + {src => packages/server/src}/model/mod.ts | 0 packages/server/src/model/tag.ts | 18 + packages/server/src/model/user.ts | 84 + packages/server/src/permission/permission.ts | 59 + packages/server/src/route/all.ts | 57 + packages/server/src/route/comic.ts | 96 + packages/server/src/route/contents.ts | 167 + packages/server/src/route/context.ts | 8 + packages/server/src/route/error_handler.ts | 49 + packages/server/src/route/tags.ts | 29 + packages/server/src/route/util.ts | 37 + packages/server/src/route/video.ts | 67 + packages/server/src/search/indexer.ts | 12 + packages/server/src/search/tokenizer.ts | 9 + packages/server/src/server.ts | 237 + packages/server/src/types/db.d.ts | 34 + {src => packages/server/src}/types/json.ts | 0 packages/server/src/util/configRW.ts | 51 + packages/server/src/util/type_check.ts | 15 + packages/server/src/util/zipwrap.ts | 33 + .../server/tsconfig.json | 0 pnpm-lock.yaml | 4421 ++++++++--------- pnpm-workspace.yaml | 2 + preload.ts | 7 - src/SettingConfig.schema.json | 66 - src/SettingConfig.ts | 79 - src/client/accessor/document.ts | 99 - src/client/accessor/util.ts | 32 - src/client/app.tsx | 65 - src/client/build.ts | 32 - src/client/component/contentinfo.tsx | 226 - src/client/component/headline.tsx | 273 - src/client/component/loading.tsx | 10 - src/client/component/navlist.tsx | 58 - src/client/component/pagepad.tsx | 5 - src/client/component/tagchip.tsx | 78 - src/client/css/style.css | 9 - src/client/package.json | 24 - src/client/page/contentinfo.tsx | 131 - src/client/page/difference.tsx | 131 - src/client/page/gallery.tsx | 134 - src/client/page/login.tsx | 89 - src/client/page/profile.tsx | 151 - src/client/page/reader/comic.tsx | 83 - src/client/page/reader/reader.tsx | 78 - src/client/page/reader/video.tsx | 10 - src/client/page/setting.tsx | 17 - src/client/page/tags.tsx | 76 - src/client/pnpm-lock.yaml | 1421 ------ src/client/state.tsx | 94 - src/config.ts | 22 - src/content/comic.ts | 66 - src/content/file.ts | 90 - src/database.ts | 47 - src/db/doc.ts | 223 - src/db/tag.ts | 57 - src/db/user.ts | 87 - src/diff/content_handler.ts | 120 - src/diff/content_list.ts | 59 - src/diff/diff.ts | 45 - src/diff/router.ts | 83 - src/diff/watcher.ts | 25 - src/diff/watcher/ComicConfig.schema.json | 12 - src/diff/watcher/common_watcher.ts | 44 - src/diff/watcher/compositer.ts | 23 - src/diff/watcher/recursive_watcher.ts | 61 - src/diff/watcher/util.ts | 36 - src/diff/watcher/watcher_filter.ts | 46 - src/login.ts | 285 -- src/model/doc.ts | 129 - src/model/tag.ts | 18 - src/model/user.ts | 84 - src/permission/permission.ts | 58 - src/route/all.ts | 57 - src/route/comic.ts | 96 - src/route/contents.ts | 167 - src/route/context.ts | 8 - src/route/error_handler.ts | 49 - src/route/tags.ts | 29 - src/route/util.ts | 37 - src/route/video.ts | 67 - src/search/indexer.ts | 12 - src/search/tokenizer.ts | 9 - src/server.ts | 237 - src/types/db.d.ts | 34 - src/util/configRW.ts | 51 - src/util/type_check.ts | 15 - src/util/zipwrap.ts | 33 - 169 files changed, 7149 insertions(+), 8887 deletions(-) delete mode 100644 app.ts create mode 100644 biome.jsonc delete mode 100644 gen_conf_schema.ts delete mode 100644 migrations/initial.ts create mode 100644 packages/client/.eslintrc.cjs create mode 100644 packages/client/.gitignore create mode 100644 packages/client/README.md create mode 100644 packages/client/index.html create mode 100644 packages/client/package.json create mode 100644 packages/client/public/vite.svg create mode 100644 packages/client/src/App.css create mode 100644 packages/client/src/App.tsx create mode 100644 packages/client/src/accessor/document.ts create mode 100644 packages/client/src/accessor/util.ts create mode 100644 packages/client/src/assets/react.svg create mode 100644 packages/client/src/component/contentinfo.tsx create mode 100644 packages/client/src/component/headline.tsx create mode 100644 packages/client/src/component/loading.tsx rename {src/client => packages/client/src}/component/mod.ts (100%) create mode 100644 packages/client/src/component/navlist.tsx create mode 100644 packages/client/src/component/pagepad.tsx create mode 100644 packages/client/src/component/tagchip.tsx create mode 100644 packages/client/src/index.css create mode 100644 packages/client/src/main.tsx rename {src/client => packages/client/src}/page/404.tsx (50%) create mode 100644 packages/client/src/page/contentinfo.tsx create mode 100644 packages/client/src/page/difference.tsx create mode 100644 packages/client/src/page/gallery.tsx create mode 100644 packages/client/src/page/login.tsx rename {src/client => packages/client/src}/page/mod.ts (100%) create mode 100644 packages/client/src/page/profile.tsx create mode 100644 packages/client/src/page/reader/comic.tsx create mode 100644 packages/client/src/page/reader/reader.tsx rename {src/client => packages/client/src}/page/reader/thumbnail.css (100%) create mode 100644 packages/client/src/page/reader/video.tsx create mode 100644 packages/client/src/page/setting.tsx create mode 100644 packages/client/src/page/tags.tsx create mode 100644 packages/client/src/state.tsx create mode 100644 packages/client/src/vite-env.d.ts create mode 100644 packages/client/tsconfig.json create mode 100644 packages/client/tsconfig.node.json create mode 100644 packages/client/vite.config.ts create mode 100644 packages/dbtype/package.json create mode 100644 packages/dbtype/types.ts create mode 100644 packages/server/app.ts create mode 100644 packages/server/gen_conf_schema.ts rename index.html => packages/server/index.html (100%) rename knexfile.js => packages/server/knexfile.js (100%) create mode 100644 packages/server/migrations/initial.ts create mode 100644 packages/server/package.json create mode 100644 packages/server/preload.ts create mode 100644 packages/server/src/SettingConfig.schema.json create mode 100644 packages/server/src/SettingConfig.ts create mode 100644 packages/server/src/config.ts create mode 100644 packages/server/src/content/comic.ts create mode 100644 packages/server/src/content/file.ts rename {src => packages/server/src}/content/mod.ts (100%) rename {src => packages/server/src}/content/video.ts (71%) create mode 100644 packages/server/src/database.ts create mode 100644 packages/server/src/db/doc.ts rename {src => packages/server/src}/db/mod.ts (100%) create mode 100644 packages/server/src/db/tag.ts create mode 100644 packages/server/src/db/user.ts create mode 100644 packages/server/src/diff/content_handler.ts create mode 100644 packages/server/src/diff/content_list.ts create mode 100644 packages/server/src/diff/diff.ts rename {src => packages/server/src}/diff/mod.ts (100%) create mode 100644 packages/server/src/diff/router.ts create mode 100644 packages/server/src/diff/watcher.ts create mode 100644 packages/server/src/diff/watcher/ComicConfig.schema.json rename {src => packages/server/src}/diff/watcher/ComicConfig.ts (92%) rename {src => packages/server/src}/diff/watcher/comic_watcher.ts (61%) create mode 100644 packages/server/src/diff/watcher/common_watcher.ts create mode 100644 packages/server/src/diff/watcher/compositer.ts create mode 100644 packages/server/src/diff/watcher/recursive_watcher.ts create mode 100644 packages/server/src/diff/watcher/util.ts create mode 100644 packages/server/src/diff/watcher/watcher_filter.ts create mode 100644 packages/server/src/login.ts create mode 100644 packages/server/src/model/doc.ts rename {src => packages/server/src}/model/mod.ts (100%) create mode 100644 packages/server/src/model/tag.ts create mode 100644 packages/server/src/model/user.ts create mode 100644 packages/server/src/permission/permission.ts create mode 100644 packages/server/src/route/all.ts create mode 100644 packages/server/src/route/comic.ts create mode 100644 packages/server/src/route/contents.ts create mode 100644 packages/server/src/route/context.ts create mode 100644 packages/server/src/route/error_handler.ts create mode 100644 packages/server/src/route/tags.ts create mode 100644 packages/server/src/route/util.ts create mode 100644 packages/server/src/route/video.ts create mode 100644 packages/server/src/search/indexer.ts create mode 100644 packages/server/src/search/tokenizer.ts create mode 100644 packages/server/src/server.ts create mode 100644 packages/server/src/types/db.d.ts rename {src => packages/server/src}/types/json.ts (100%) create mode 100644 packages/server/src/util/configRW.ts create mode 100644 packages/server/src/util/type_check.ts create mode 100644 packages/server/src/util/zipwrap.ts rename tsconfig.json => packages/server/tsconfig.json (100%) create mode 100644 pnpm-workspace.yaml delete mode 100644 preload.ts delete mode 100644 src/SettingConfig.schema.json delete mode 100644 src/SettingConfig.ts delete mode 100644 src/client/accessor/document.ts delete mode 100644 src/client/accessor/util.ts delete mode 100644 src/client/app.tsx delete mode 100644 src/client/build.ts delete mode 100644 src/client/component/contentinfo.tsx delete mode 100644 src/client/component/headline.tsx delete mode 100644 src/client/component/loading.tsx delete mode 100644 src/client/component/navlist.tsx delete mode 100644 src/client/component/pagepad.tsx delete mode 100644 src/client/component/tagchip.tsx delete mode 100644 src/client/css/style.css delete mode 100644 src/client/package.json delete mode 100644 src/client/page/contentinfo.tsx delete mode 100644 src/client/page/difference.tsx delete mode 100644 src/client/page/gallery.tsx delete mode 100644 src/client/page/login.tsx delete mode 100644 src/client/page/profile.tsx delete mode 100644 src/client/page/reader/comic.tsx delete mode 100644 src/client/page/reader/reader.tsx delete mode 100644 src/client/page/reader/video.tsx delete mode 100644 src/client/page/setting.tsx delete mode 100644 src/client/page/tags.tsx delete mode 100644 src/client/pnpm-lock.yaml delete mode 100644 src/client/state.tsx delete mode 100644 src/config.ts delete mode 100644 src/content/comic.ts delete mode 100644 src/content/file.ts delete mode 100644 src/database.ts delete mode 100644 src/db/doc.ts delete mode 100644 src/db/tag.ts delete mode 100644 src/db/user.ts delete mode 100644 src/diff/content_handler.ts delete mode 100644 src/diff/content_list.ts delete mode 100644 src/diff/diff.ts delete mode 100644 src/diff/router.ts delete mode 100644 src/diff/watcher.ts delete mode 100644 src/diff/watcher/ComicConfig.schema.json delete mode 100644 src/diff/watcher/common_watcher.ts delete mode 100644 src/diff/watcher/compositer.ts delete mode 100644 src/diff/watcher/recursive_watcher.ts delete mode 100644 src/diff/watcher/util.ts delete mode 100644 src/diff/watcher/watcher_filter.ts delete mode 100644 src/login.ts delete mode 100644 src/model/doc.ts delete mode 100644 src/model/tag.ts delete mode 100644 src/model/user.ts delete mode 100644 src/permission/permission.ts delete mode 100644 src/route/all.ts delete mode 100644 src/route/comic.ts delete mode 100644 src/route/contents.ts delete mode 100644 src/route/context.ts delete mode 100644 src/route/error_handler.ts delete mode 100644 src/route/tags.ts delete mode 100644 src/route/util.ts delete mode 100644 src/route/video.ts delete mode 100644 src/search/indexer.ts delete mode 100644 src/search/tokenizer.ts delete mode 100644 src/server.ts delete mode 100644 src/types/db.d.ts delete mode 100644 src/util/configRW.ts delete mode 100644 src/util/type_check.ts delete mode 100644 src/util/zipwrap.ts diff --git a/.gitignore b/.gitignore index 2d08fce..ea060c5 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,6 @@ db.sqlite3 build/** app/** settings.json -*config.json -.pnpm-store/** \ No newline at end of file +.pnpm-store/** +.env \ No newline at end of file diff --git a/app.ts b/app.ts deleted file mode 100644 index 27c6636..0000000 --- a/app.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { app, BrowserWindow, dialog, session } from "electron"; -import { ipcMain } from "electron"; -import { join } from "path"; -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; - }); -} -const setting = get_setting(); -if (!setting.cli) { - 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, - }); - } 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(); - } - 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("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); - } - })(); -} -const loading_html = ` - - -loading - - - - - -

Loading...

-
- -`; diff --git a/biome.jsonc b/biome.jsonc new file mode 100644 index 0000000..df0035c --- /dev/null +++ b/biome.jsonc @@ -0,0 +1,21 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.6.3/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "enabled": true, + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + } +} diff --git a/gen_conf_schema.ts b/gen_conf_schema.ts deleted file mode 100644 index 460665b..0000000 --- a/gen_conf_schema.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { promises } from "fs"; -const { readdir, writeFile } = promises; -import { dirname, join } from "path"; -import { createGenerator } from "ts-json-schema-generator"; - -async function genSchema(path: string, typename: string) { - const gen = createGenerator({ - path: path, - type: typename, - tsconfig: "tsconfig.json", - }); - const schema = gen.createSchema(typename); - if (schema.definitions != undefined) { - const definitions = schema.definitions; - const definition = definitions[typename]; - if (typeof definition == "object") { - let property = definition.properties; - if (property) { - property["$schema"] = { - type: "string", - }; - } - } - } - const text = JSON.stringify(schema); - await writeFile(join(dirname(path), `${typename}.schema.json`), text); -} -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 => { - const name = x.name; - const m = /(.+)\.ts/.exec(name); - if (m !== null) { - const typename = m[1]; - 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)); - } -} -setToALL("src"); diff --git a/migrations/initial.ts b/migrations/initial.ts deleted file mode 100644 index 816926d..0000000 --- a/migrations/initial.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Knex } from "knex"; - -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) => { - b.string("username").primary().comment("user's login id"); - b.string("password_hash", 64).notNullable(); - b.string("password_salt", 64).notNullable(); - }); - 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_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"); - }); - await knex.schema.createTable("tags", (b) => { - b.string("name").primary(); - b.text("description"); - }); - 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"]); - }); - await knex.schema.createTable("permissions", b => { - b.string("username").notNullable(); - b.string("name").notNullable(); - b.primary(["username", "name"]); - b.foreign("username").references("users.username"); - }); - // create admin account. - await knex.insert({ - 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."); -} diff --git a/package.json b/package.json index 2d0c444..d798872 100644 --- a/package.json +++ b/package.json @@ -1,86 +1,20 @@ { - "name": "followed", + "name": "ionian", "version": "1.0.0", "description": "", - "main": "build/app.js", + "main": "index.js", "scripts": { - "compile": "tsc", - "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", - "app:build:win64": "electron-builder --win --x64", - "app:pack:win64": "electron-builder --win --x64 --dir", - "cliapp": "node build/app.js" - }, - "build": { - "asar": true, - "files": [ - "build/**/*", - "node_modules/**/*", - "package.json" - ], - "extraFiles": [ - { - "from": "dist/", - "to": "dist/", - "filter": [ - "**/*", - "!**/*.map" - ] - }, - "index.html" - ], - "appId": "com.prelude.ionian.app", - "productName": "Ionian", - "win": { - "target": [ - "zip" - ] - }, - "linux": { - "target": [ - "zip" - ] - }, - "directories": { - "output": "app/", - "app": "." - } + "test": "echo \"Error: no test specified\" && exit 1", + "format": "biome format --write", + "lint": "biome lint" }, + "keywords": [], + "workspaces": [ + "packages/*" + ], "author": "", - "license": "ISC", - "dependencies": { - "@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", - "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" - }, + "license": "MIT", "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" + "@biomejs/biome": "1.6.3" } -} +} \ No newline at end of file diff --git a/packages/client/.eslintrc.cjs b/packages/client/.eslintrc.cjs new file mode 100644 index 0000000..d6c9537 --- /dev/null +++ b/packages/client/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/packages/client/.gitignore b/packages/client/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/packages/client/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/packages/client/README.md b/packages/client/README.md new file mode 100644 index 0000000..0d6babe --- /dev/null +++ b/packages/client/README.md @@ -0,0 +1,30 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: + +- Configure the top-level `parserOptions` property like this: + +```js +export default { + // other rules... + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + project: ['./tsconfig.json', './tsconfig.node.json'], + tsconfigRootDir: __dirname, + }, +} +``` + +- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` +- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` +- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list diff --git a/packages/client/index.html b/packages/client/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/packages/client/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/packages/client/package.json b/packages/client/package.json new file mode 100644 index 0000000..c454f0b --- /dev/null +++ b/packages/client/package.json @@ -0,0 +1,28 @@ +{ + "name": "client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/packages/client/public/vite.svg b/packages/client/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/packages/client/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/client/src/App.css b/packages/client/src/App.css new file mode 100644 index 0000000..e69de29 diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx new file mode 100644 index 0000000..1b75bf9 --- /dev/null +++ b/packages/client/src/App.tsx @@ -0,0 +1,59 @@ + +import './App.css' +import React, { createContext, useEffect, useRef, useState } from "react"; +import ReactDom from "react-dom"; +import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; +import { + DifferencePage, + DocumentAbout, + Gallery, + LoginPage, + NotFoundPage, + ProfilePage, + ReaderPage, + SettingPage, + TagsPage, +} from "./page/mod"; +import { getInitialValue, UserContext } from "./state"; + +import "./css/style.css"; + +const App = () => { + const [user, setUser] = useState(""); + const [userPermission, setUserPermission] = useState([]); + (async () => { + const { username, permission } = await getInitialValue(); + if (username !== user) { + setUser(username); + setUserPermission(permission); + } + })(); + // useEffect(()=>{}); + return ( + + + + } /> + } /> + }> + }> + } /> + }> + }> + }> + }> + } /> + + + + ); +}; + +export default App diff --git a/packages/client/src/accessor/document.ts b/packages/client/src/accessor/document.ts new file mode 100644 index 0000000..ac95feb --- /dev/null +++ b/packages/client/src/accessor/document.ts @@ -0,0 +1,99 @@ +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 ClientDocumentAccessor implements DocumentAccessor { + search: (search_word: string) => Promise; + addList: (content_list: DocumentBody[]) => Promise; + async findByPath(basepath: string, filename?: string): Promise { + throw new Error("not allowed"); + } + async findDeleted(content_type: string): Promise { + throw new Error("not allowed"); + } + 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"); + let ret = await res.json(); + return ret; + } + async findById(id: number, tagload?: boolean | undefined): Promise { + let res = await fetch(`${baseurl}/${id}`); + if (res.status !== 200) throw new FetchFailError("findById Failed"); + let ret = await res.json(); + return ret; + } + /** + * not implement + */ + 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}`, { + method: "POST", + body: JSON.stringify(rest), + headers: { + "content-type": "application/json", + }, + }); + const ret = await res.json(); + return ret; + } + async add(c: DocumentBody): Promise { + throw new Error("not allow"); + const res = await fetch(`${baseurl}`, { + method: "POST", + body: JSON.stringify(c), + 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", + }); + 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}`, { + method: "POST", + body: JSON.stringify(rest), + 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}`, { + method: "DELETE", + body: JSON.stringify(rest), + headers: { + "content-type": "application/json", + }, + }); + const ret = await res.json(); + return ret; + } +} +export const CDocumentAccessor = new ClientDocumentAccessor(); +export const makeThumbnailUrl = (x: Document) => { + return `${baseurl}/${x.id}/${x.content_type}/thumbnail`; +}; + +export default CDocumentAccessor; diff --git a/packages/client/src/accessor/util.ts b/packages/client/src/accessor/util.ts new file mode 100644 index 0000000..8af0f61 --- /dev/null +++ b/packages/client/src/accessor/util.ts @@ -0,0 +1,28 @@ +type Representable = string | number | boolean; + +type ToQueryStringA = { + [name: string]: Representable | Representable[] | undefined; +}; + +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("="); + const pv = param[k]; + if (pv === undefined) { + param[k] = v; + } else if (typeof pv === "string") { + param[k] = [pv, v]; + } else { + pv.push(v); + } + }); + return param; +}; diff --git a/packages/client/src/assets/react.svg b/packages/client/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/packages/client/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/client/src/component/contentinfo.tsx b/packages/client/src/component/contentinfo.tsx new file mode 100644 index 0000000..c2497c3 --- /dev/null +++ b/packages/client/src/component/contentinfo.tsx @@ -0,0 +1,238 @@ +import React, {} from "react"; +import { Link as RouterLink } from "react-router-dom"; +import { Document } from "../accessor/document"; + +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"; + +export const makeContentInfoUrl = (id: number) => `/doc/${id}`; +export const makeContentReaderUrl = (id: number) => `/doc/${id}/reader`; + +const useStyles = (theme: Theme) => ({ + thumbnail_content: { + maxHeight: "400px", + maxWidth: "min(400px, 100vw)", + }, + tag_list: { + display: "flex", + justifyContent: "flex-start", + flexWrap: "wrap", + overflowY: "hidden", + "& > *": { + margin: theme.spacing(0.5), + }, + }, + title: { + marginLeft: theme.spacing(1), + }, + infoContainer: { + padding: theme.spacing(2), + }, + subinfoContainer: { + display: "grid", + gridTemplateColumns: "100px auto", + overflowY: "hidden", + alignItems: "baseline", + }, + short_subinfoContainer: { + [theme.breakpoints.down("md")]: { + display: "none", + }, + }, + short_root: { + overflowY: "hidden", + display: "flex", + flexDirection: "column", + [theme.breakpoints.up("sm")]: { + height: 200, + flexDirection: "row", + }, + }, + short_thumbnail_anchor: { + 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%", + }, +}); + +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; +}) => { + 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} + + + {props.short ? ( + + {document.tags.map((x) => ( + + ))} + + ) : ( + + )} + + {document.deleted_at != null && ( + + )} + + + ); +}; +async function documentDelete(id: number) { + const t = await DocumentAccessor.del(id); + if (t) { + alert("document deleted!"); + } else { + alert("document already deleted."); + } +} + +function ComicDetailTag(prop: { + tags: string[] /*classes:{ + tag_list:string +}*/; + path?: string; + createdAt?: number; + deletedAt?: number; +}) { + let allTag = prop.tags; + const tagKind = ["artist", "group", "series", "type", "character"]; + let tagTable: { [kind: string]: string[] } = {}; + for (const kind of tagKind) { + const tags = allTag.filter((x) => x.startsWith(kind + ":")).map((x) => x.slice(kind.length + 1)); + tagTable[kind] = tags; + allTag = allTag.filter((x) => !x.startsWith(kind + ":")); + } + return ( + + {tagKind.map((key) => ( + + + {key} + + + + {tagTable[key].length !== 0 + ? tagTable[key].map((elem, i) => { + return ( + <> + + {elem} + + {i < tagTable[key].length - 1 ? "," : ""} + + ); + }) + : "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) => ( + + ))} + + + ); +} diff --git a/packages/client/src/component/headline.tsx b/packages/client/src/component/headline.tsx new file mode 100644 index 0000000..712fe81 --- /dev/null +++ b/packages/client/src/component/headline.tsx @@ -0,0 +1,273 @@ +import { AccountCircle, ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon } from "@mui/icons-material"; +import { + 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"; + +const drawerWidth = 270; + +const DrawerHeader = styled("div")(({ theme }) => ({ + ...theme.mixins.toolbar, +})); + +const StyledDrawer = styled(Drawer)(({ theme }) => ({ + flexShrink: 0, + whiteSpace: "nowrap", + [theme.breakpoints.up("sm")]: { + width: drawerWidth, + }, +})); +const StyledSearchBar = styled("div")(({ theme }) => ({ + position: "relative", + borderRadius: theme.shape.borderRadius, + backgroundColor: alpha(theme.palette.common.white, 0.15), + "&:hover": { + backgroundColor: alpha(theme.palette.common.white, 0.25), + }, + marginLeft: 0, + width: "100%", + [theme.breakpoints.up("sm")]: { + marginLeft: theme.spacing(1), + width: "auto", + }, +})); +const StyledInputBase = styled(InputBase)(({ theme }) => ({ + 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", + }, + }, + }, +})); + +const StyledNav = styled("nav")(({ theme }) => ({ + [theme.breakpoints.up("sm")]: { + width: theme.spacing(7), + }, +})); + +const closedMixin = (theme: Theme) => ({ + overflowX: "hidden", + width: `calc(${theme.spacing(7)} + 1px)`, +}); + +export const Headline = (prop: { + children?: React.ReactNode; + classes?: { + content?: string; + toolbar?: string; + }; + rightAppbar?: React.ReactNode; + menu: React.ReactNode; +}) => { + const [v, setv] = useState(false); + const [anchorEl, setAnchorEl] = React.useState(null); + const theme = useTheme(); + const toggleV = () => setv(!v); + const handleProfileMenuOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); + const handleProfileMenuClose = () => setAnchorEl(null); + const isProfileMenuOpened = Boolean(anchorEl); + 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 ( +
+ + + + + + + + Ionian + +
+ {prop.rightAppbar} + +
+ 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("&")}`); + } +}; + +export default Headline; diff --git a/packages/client/src/component/loading.tsx b/packages/client/src/component/loading.tsx new file mode 100644 index 0000000..93b69ed --- /dev/null +++ b/packages/client/src/component/loading.tsx @@ -0,0 +1,10 @@ +import { Box, CircularProgress } from "@mui/material"; +import React from "react"; + +export const LoadingCircle = () => { + return ( + + + + ); +}; diff --git a/src/client/component/mod.ts b/packages/client/src/component/mod.ts similarity index 100% rename from src/client/component/mod.ts rename to packages/client/src/component/mod.ts diff --git a/packages/client/src/component/navlist.tsx b/packages/client/src/component/navlist.tsx new file mode 100644 index 0000000..a78b0f5 --- /dev/null +++ b/packages/client/src/component/navlist.tsx @@ -0,0 +1,54 @@ +import { + ArrowBack as ArrowBackIcon, + Collections as CollectionIcon, + Folder as FolderIcon, + Home as HomeIcon, + List as ListIcon, + 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 NavList = (props: { children?: React.ReactNode }) => { + return {props.children}; +}; + +export const BackItem = (props: { to?: string }) => { + return } />; +}; + +export function CommonMenuList(props?: { url?: string }) { + let url = props?.url ?? ""; + return ( + + {url !== "" && ( + <> + + + )} + } /> + }> + } /> + + } /> + + }> + } /> + + ); +} diff --git a/packages/client/src/component/pagepad.tsx b/packages/client/src/component/pagepad.tsx new file mode 100644 index 0000000..8726512 --- /dev/null +++ b/packages/client/src/component/pagepad.tsx @@ -0,0 +1,5 @@ +import { styled } from "@mui/material"; + +export const PagePad = styled("div")(({ theme }) => ({ + padding: theme.spacing(3), +})); diff --git a/packages/client/src/component/tagchip.tsx b/packages/client/src/component/tagchip.tsx new file mode 100644 index 0000000..dfb90e6 --- /dev/null +++ b/packages/client/src/component/tagchip.tsx @@ -0,0 +1,80 @@ +import * as colors from "@mui/material/colors"; +import Chip, { ChipTypeMap } from "@mui/material/Chip"; +import { emphasize, styled, Theme, useTheme } from "@mui/material/styles"; +import React from "react"; +import { Link as RouterLink } from "react-router-dom"; + +type TagChipStyleProp = { + color: `rgba(${number},${number},${number},${number})` | `#${string}` | "default"; +}; + +const { blue, pink } = colors; +const getTagColorName = (tagname: string): TagChipStyleProp["color"] => { + if (tagname.startsWith("female")) { + return pink[600]; + } else if (tagname.startsWith("male")) { + return blue[600]; + } else return "default"; +}; + +type ColorChipProp = Omit & + TagChipStyleProp & { + component?: React.ElementType; + to?: string; + }; + +export const ColorChip = (props: ColorChipProp) => { + const { color, ...rest } = props; + const theme = useTheme(); + + let newcolor = color; + if (color === "default") { + newcolor = "#ebebeb"; + } + return ( + + ); +}; + +type TagChipProp = Omit & { + tagname: string; +}; + +export const TagChip = (props: TagChipProp) => { + const { tagname, label, clickable, ...rest } = props; + const colorName = getTagColorName(tagname); + + let newlabel: React.ReactNode = label; + if (typeof label === "string") { + const female = "female:"; + const male = "male:"; + if (label.startsWith(female)) { + newlabel = "♀ " + label.slice(female.length); + } else if (label.startsWith(male)) { + newlabel = "♂ " + label.slice(male.length); + } + } + + const inner = clickable ? ( + + ) : ( + + ); + return inner; +}; diff --git a/packages/client/src/index.css b/packages/client/src/index.css new file mode 100644 index 0000000..e69de29 diff --git a/packages/client/src/main.tsx b/packages/client/src/main.tsx new file mode 100644 index 0000000..3d7150d --- /dev/null +++ b/packages/client/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App.tsx' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/client/page/404.tsx b/packages/client/src/page/404.tsx similarity index 50% rename from src/client/page/404.tsx rename to packages/client/src/page/404.tsx index 98e7d84..beecbb4 100644 --- a/src/client/page/404.tsx +++ b/packages/client/src/page/404.tsx @@ -4,12 +4,12 @@ import { CommonMenuList, Headline } from "../component/mod"; import { PagePad } from "../component/pagepad"; export const NotFoundPage = () => { - const menu = CommonMenuList(); - return ( - - - 404 Not Found - - - ); + const menu = CommonMenuList(); + return ( + + + 404 Not Found + + + ); }; diff --git a/packages/client/src/page/contentinfo.tsx b/packages/client/src/page/contentinfo.tsx new file mode 100644 index 0000000..655585c --- /dev/null +++ b/packages/client/src/page/contentinfo.tsx @@ -0,0 +1,136 @@ +import { IconButton, Theme, Typography } from "@mui/material"; +import FullscreenIcon from "@mui/icons-material/Fullscreen"; +import React, { useEffect, useRef, 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"; +import { PagePad } from "../component/pagepad"; + +export const makeContentInfoUrl = (id: number) => `/doc/${id}`; +export const makeComicReaderUrl = (id: number) => `/doc/${id}/reader`; + +type DocumentState = { + doc: Document | undefined; + notfound: boolean; +}; + +export function ReaderPage(props?: {}) { + const location = useLocation(); + const match = useParams<{ id: string }>(); + if (match == null) { + throw new Error("unreachable"); + } + const id = Number.parseInt(match.id ?? "NaN"); + const [info, setInfo] = useState({ doc: undefined, notfound: false }); + const menu_list = (link?: string) => ; + const fullScreenTargetRef = useRef(null); + + useEffect(() => { + (async () => { + if (!isNaN(id)) { + const c = await DocumentAccessor.findById(id); + setInfo({ doc: c, notfound: c === undefined }); + } + })(); + }, []); + + if (isNaN(id)) { + return ( + + Oops. Invalid ID + + ); + } else if (info.notfound) { + return ( + + Content has been removed. + + ); + } else if (info.doc === undefined) { + return ( + + + + ); + } else { + const ReaderPage = getPresenter(info.doc); + return ( + { + if (fullScreenTargetRef.current != null && document.fullscreenEnabled) { + fullScreenTargetRef.current.requestFullscreen(); + } + }} + color="inherit" + > + + + } + > + + + ); + } +} + +export const DocumentAbout = (prop?: {}) => { + const match = useParams<{ id: string }>(); + if (match == null) { + throw new Error("unreachable"); + } + const id = Number.parseInt(match.id ?? "NaN"); + const [info, setInfo] = useState({ doc: undefined, notfound: false }); + const menu_list = (link?: string) => ; + + useEffect(() => { + (async () => { + if (!isNaN(id)) { + const c = await DocumentAccessor.findById(id); + setInfo({ doc: c, notfound: c === undefined }); + } + })(); + }, []); + + if (isNaN(id)) { + return ( + + + Oops. Invalid ID + + + ); + } else if (info.notfound) { + return ( + + + Content has been removed. + + + ); + } else if (info.doc === undefined) { + return ( + + + + + + ); + } else { + return ( + + + + + + ); + } +}; diff --git a/packages/client/src/page/difference.tsx b/packages/client/src/page/difference.tsx new file mode 100644 index 0000000..2dabe32 --- /dev/null +++ b/packages/client/src/page/difference.tsx @@ -0,0 +1,126 @@ +import { Box, Button, Paper, Typography } from "@mui/material"; +import React, { useContext, useEffect, useState } from "react"; +import { CommonMenuList, Headline } from "../component/mod"; +import { UserContext } from "../state"; +import { PagePad } from "../component/pagepad"; + +type FileDifference = { + 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(); + const x = prop.content; + const [button_disable, set_disable] = useState(false); + + return ( + + + {x.type} + + + {x.value.map((y) => ( + + + {y.path} + + ))} + + ); +} + +export function DifferencePage() { + const ctx = useContext(UserContext); + // const classes = useStyles(); + const [diffList, setDiffList] = useState([]); + const doLoad = async () => { + const list = await fetch("/api/diff/list"); + if (list.ok) { + const inner = await list.json(); + setDiffList(inner); + } 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 bb = await res.json(); + if (bb.ok) { + doLoad(); + } 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 bb = await res.json(); + if (bb.ok) { + doLoad(); + } else { + console.error("fail to add document"); + } + }; + useEffect(() => { + doLoad(); + 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 + )} +
+
+ ); +} diff --git a/packages/client/src/page/gallery.tsx b/packages/client/src/page/gallery.tsx new file mode 100644 index 0000000..f0a6cfd --- /dev/null +++ b/packages/client/src/page/gallery.tsx @@ -0,0 +1,133 @@ +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"; +import { PagePad } from "../component/pagepad"; + +export type GalleryProp = { + option?: QueryListOption; + diff: string; +}; +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({}); + + useEffect(() => { + if (isLoadVisible && !loadAll && state.documents != undefined) { + loadMore(); + } + }, [isLoadVisible]); + + useEffect(() => { + const abortController = new AbortController(); + 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. + setState({ documents: c }); + setLoadAll(c.length == 0); + } catch (e) { + if (e instanceof Error) { + setError(e.message); + } else { + setError("unknown error"); + } + } + }; + load(); + }, [props.diff]); + const queryString = toQueryString(props.option ?? {}); + if (state.documents === undefined && error == null) { + 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... + + + + ); + } + function loadMore() { + let option = { ...props.option }; + console.log(elementRef); + if (state.documents === undefined || state.documents.length === 0) { + console.log("loadall"); + setLoadAll(true); + return; + } + const prev_documents = state.documents; + option.cursor = prev_documents[prev_documents.length - 1].id; + console.log("load more", option); + const load = async () => { + const c = await ContentAccessor.findList(option); + if (c.length === 0) { + setLoadAll(true); + } else { + setState({ documents: [...prev_documents, ...c] }); + } + }; + load(); + } +}; + +export const Gallery = () => { + const location = useLocation(); + const query = QueryStringToMap(location.search); + 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 ( + + + + + + ); +}; diff --git a/packages/client/src/page/login.tsx b/packages/client/src/page/login.tsx new file mode 100644 index 0000000..ba94ec0 --- /dev/null +++ b/packages/client/src/page/login.tsx @@ -0,0 +1,90 @@ +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"; +import { PagePad } from "../component/pagepad"; + +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 navigate = useNavigate(); + const handleDialogClose = () => { + setOpenDialog({ ...openDialog, open: false }); + }; + const doLogin = async () => { + try { + const b = await doSessionLogin(userLoginInfo); + 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) { + 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} + + + + + +
+
+ ); +}; diff --git a/src/client/page/mod.ts b/packages/client/src/page/mod.ts similarity index 100% rename from src/client/page/mod.ts rename to packages/client/src/page/mod.ts diff --git a/packages/client/src/page/profile.tsx b/packages/client/src/page/profile.tsx new file mode 100644 index 0000000..1ad0e15 --- /dev/null +++ b/packages/client/src/page/profile.tsx @@ -0,0 +1,149 @@ +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 { UserContext } from "../state"; +import { PagePad } from "../component/pagepad"; + +const useStyles = (theme: Theme) => ({ + paper: { + alignSelf: "center", + padding: theme.spacing(2), + }, + formfield: { + display: "flex", + flexFlow: "column", + }, +}); + +export function ProfilePage() { + const userctx = useContext(UserContext); + // 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 = () => { + 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." }); + 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." }); + } + } else { + const res = await fetch("/user/reset", { + method: "POST", + body: JSON.stringify({ + username: userctx.username, + oldpassword: oldpw, + newpassword: newpw, + }), + headers: { + "content-type": "application/json", + }, + }); + if (res.status != 200) { + set_msg_dialog({ opened: true, msg: "failed to change password." }); + } + } + handle_close(); + }; + return ( + + + + + + {userctx.username} + + + 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} + + + + + +
+
+ ); +} diff --git a/packages/client/src/page/reader/comic.tsx b/packages/client/src/page/reader/comic.tsx new file mode 100644 index 0000000..efccde1 --- /dev/null +++ b/packages/client/src/page/reader/comic.tsx @@ -0,0 +1,83 @@ +import { Typography, styled } from "@mui/material"; +import React, { RefObject, useEffect, useState } from "react"; +import { useSearchParams } from "react-router-dom"; +import { Document } from "../../accessor/document"; + +type ComicType = "comic" | "artist cg" | "donjinshi" | "western"; + +export type PresentableTag = { + artist: string[]; + group: string[]; + series: string[]; + type: ComicType; + character: string[]; + tags: string[]; +}; + +const ViewMain = styled("div")(({ theme }) => ({ + overflow: "hidden", + width: "100%", + height: "calc(100vh - 64px)", + position: "relative", +})); +const CurrentView = styled("img")(({ theme }) => ({ + maxWidth: "100%", + maxHeight: "100%", + top: "50%", + left: "50%", + transform: "translate(-50%,-50%)", + position: "absolute", +})); + +export const ComicReader = (props: { doc: Document; fullScreenTarget?: RefObject }) => { + const additional = props.doc.additional; + const [searchParams, setSearchParams] = useSearchParams(); + + const curPage = parseInt(searchParams.get("page") ?? "0"); + const setCurPage = (n: number) => { + setSearchParams([["page", n.toString()]]); + }; + if (isNaN(curPage)) { + return Error. Page number is not a number.; + } + if (!("page" in additional)) { + console.error("invalid content : page read fail : " + JSON.stringify(additional)); + return Error. DB error. page restriction; + } + + const maxPage: number = additional["page"] as number; + const PageDown = () => setCurPage(Math.max(curPage - 1, 0)); + const PageUp = () => setCurPage(Math.min(curPage + 1, maxPage - 1)); + + const onKeyUp = (e: KeyboardEvent) => { + console.log(`currently: ${curPage}/${maxPage}`); + if (e.code === "ArrowLeft") { + PageDown(); + } else if (e.code === "ArrowRight") { + PageUp(); + } + }; + + useEffect(() => { + document.addEventListener("keydown", onKeyUp); + return () => { + document.removeEventListener("keydown", onKeyUp); + }; + }, [curPage]); + // theme.mixins.toolbar.minHeight; + return ( + +
+ +
+
+ ); +}; + +export default ComicReader; diff --git a/packages/client/src/page/reader/reader.tsx b/packages/client/src/page/reader/reader.tsx new file mode 100644 index 0000000..4f67af6 --- /dev/null +++ b/packages/client/src/page/reader/reader.tsx @@ -0,0 +1,80 @@ +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; + fullScreenTarget?: React.RefObject; +} +interface PagePresenter { + (prop: PagePresenterProp): JSX.Element; +} + +export const getPresenter = (content: Document): PagePresenter => { + switch (content.content_type) { + case "comic": + return ComicReader; + case "video": + return VideoReader; + } + return () => Not implemented reader; +}; +const BackgroundDiv = styled("div")({ + height: "400px", + width: "300px", + backgroundColor: "#272733", + display: "flex", + alignItems: "center", + justifyContent: "center", +}); + +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); + }; + + useEffect(() => { + 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({}); + const [loaded, setLoaded] = useState(false); + useEffect(() => { + if (isVisible) { + setLoaded(true); + } + }, [isVisible]); + const style = { + maxHeight: "400px", + maxWidth: "min(400px, 100vw)", + }; + const thumbnailurl = makeThumbnailUrl(props.content); + if (props.content.content_type === "video") { + return ; + } else { + return ( + + {loaded && } + + ); + } +} diff --git a/src/client/page/reader/thumbnail.css b/packages/client/src/page/reader/thumbnail.css similarity index 100% rename from src/client/page/reader/thumbnail.css rename to packages/client/src/page/reader/thumbnail.css diff --git a/packages/client/src/page/reader/video.tsx b/packages/client/src/page/reader/video.tsx new file mode 100644 index 0000000..d7610aa --- /dev/null +++ b/packages/client/src/page/reader/video.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { Document } from "../../accessor/document"; + +export const VideoReader = (props: { doc: Document }) => { + const id = props.doc.id; + return ( + + ); +}; diff --git a/packages/client/src/page/setting.tsx b/packages/client/src/page/setting.tsx new file mode 100644 index 0000000..19a3b39 --- /dev/null +++ b/packages/client/src/page/setting.tsx @@ -0,0 +1,17 @@ +import { Paper, Typography } from "@mui/material"; +import React from "react"; +import { CommonMenuList, Headline } from "../component/mod"; +import { PagePad } from "../component/pagepad"; + +export const SettingPage = () => { + const menu = CommonMenuList(); + return ( + + + + Setting + + + + ); +}; diff --git a/packages/client/src/page/tags.tsx b/packages/client/src/page/tags.tsx new file mode 100644 index 0000000..637117e --- /dev/null +++ b/packages/client/src/page/tags.tsx @@ -0,0 +1,76 @@ +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"; +import { PagePad } from "../component/pagepad"; + +type TagCount = { + tag_name: string; + occurs: number; +}; + +const tagTableColumn: GridColDef[] = [ + { + 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); + const isLoading = data === undefined; + + useEffect(() => { + loadData(); + }, []); + + if (isLoading) { + return ; + } + if (error !== undefined) { + return {error}; + } + return ( + + + t.tag_name}> + + + ); + + async function loadData() { + try { + const res = await fetch("/api/tags?withCount=true"); + const data = await res.json(); + setData(data); + } catch (e) { + setData([]); + if (e instanceof Error) { + setErrorMsg(e.message); + } else { + console.log(e); + setErrorMsg(""); + } + } + } +} + +export const TagsPage = () => { + const menu = CommonMenuList(); + return ( + + + + + + ); +}; diff --git a/packages/client/src/state.tsx b/packages/client/src/state.tsx new file mode 100644 index 0000000..130780e --- /dev/null +++ b/packages/client/src/state.tsx @@ -0,0 +1,94 @@ +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[]) => {}, +}); + +type LoginLocalStorage = { + username: string; + permission: string[]; + accessExpired: number; +}; + +let localObj: LoginLocalStorage | null = null; + +export const getInitialValue = async () => { + if (localObj === null) { + 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", + }); + 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 { + localObj = { + accessExpired: 0, + username: "", + permission: r.permission, + }; + } + window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); + return { + username: r.username, + permission: r.permission, + }; +}; +export const doLogout = async () => { + const req = await fetch("/user/logout", { + method: "POST", + }); + try { + const res = await req.json(); + localObj = { + accessExpired: 0, + username: "", + 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" }, + }); + const b = await res.json(); + if (res.status !== 200) { + return b.detail as string; + } + localObj = b; + window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); + return b; +}; diff --git a/packages/client/src/vite-env.d.ts b/packages/client/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/packages/client/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/packages/client/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/packages/client/tsconfig.node.json b/packages/client/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/packages/client/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/packages/client/vite.config.ts b/packages/client/vite.config.ts new file mode 100644 index 0000000..861b04b --- /dev/null +++ b/packages/client/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/packages/dbtype/package.json b/packages/dbtype/package.json new file mode 100644 index 0000000..47f24b7 --- /dev/null +++ b/packages/dbtype/package.json @@ -0,0 +1,18 @@ +{ + "name": "dbtype", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/better-sqlite3": "^7.6.9", + "better-sqlite3": "^9.4.3", + "kysely": "^0.27.3", + "kysely-codegen": "^0.14.1" + } +} diff --git a/packages/dbtype/types.ts b/packages/dbtype/types.ts new file mode 100644 index 0000000..7a7beaf --- /dev/null +++ b/packages/dbtype/types.ts @@ -0,0 +1,53 @@ +import type { ColumnType } from "kysely"; + +export type Generated = T extends ColumnType + ? ColumnType + : ColumnType; + +export interface DocTagRelation { + doc_id: number; + tag_name: string; +} + +export interface Document { + additional: string | null; + basepath: string; + content_hash: string | null; + content_type: string; + created_at: number; + deleted_at: number | null; + filename: string; + id: Generated; + modified_at: number; + title: string; +} + +export interface Permissions { + name: string; + username: string; +} + +export interface SchemaMigration { + dirty: number | null; + version: string | null; +} + +export interface Tags { + description: string | null; + name: string | null; +} + +export interface Users { + password_hash: string; + password_salt: string; + username: string | null; +} + +export interface DB { + doc_tag_relation: DocTagRelation; + document: Document; + permissions: Permissions; + schema_migration: SchemaMigration; + tags: Tags; + users: Users; +} diff --git a/packages/server/app.ts b/packages/server/app.ts new file mode 100644 index 0000000..687a4c1 --- /dev/null +++ b/packages/server/app.ts @@ -0,0 +1,145 @@ +import { app, BrowserWindow, dialog, session } from "electron"; +import { ipcMain } from "electron"; +import { join } from "path"; +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; + }); +} +const setting = get_setting(); +if (!setting.cli) { + 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, + }); + } 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(); + } + 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("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); + } + })(); +} +const loading_html = ` + + +loading + + + + + +

Loading...

+
+ +`; diff --git a/packages/server/gen_conf_schema.ts b/packages/server/gen_conf_schema.ts new file mode 100644 index 0000000..f5a02b6 --- /dev/null +++ b/packages/server/gen_conf_schema.ts @@ -0,0 +1,50 @@ +// import { promises } from "fs"; +// const { readdir, writeFile } = promises; +// import { dirname, join } from "path"; +// import { createGenerator } from "ts-json-schema-generator"; + +// async function genSchema(path: string, typename: string) { +// const gen = createGenerator({ +// path: path, +// type: typename, +// tsconfig: "tsconfig.json", +// }); +// const schema = gen.createSchema(typename); +// if (schema.definitions != undefined) { +// const definitions = schema.definitions; +// const definition = definitions[typename]; +// if (typeof definition == "object") { +// let property = definition.properties; +// if (property) { +// property["$schema"] = { +// type: "string", +// }; +// } +// } +// } +// const text = JSON.stringify(schema); +// await writeFile(join(dirname(path), `${typename}.schema.json`), text); +// } +// 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) => { +// const name = x.name; +// const m = /(.+)\.ts/.exec(name); +// if (m !== null) { +// const typename = m[1]; +// 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)); +// } +// } +// setToALL("src"); diff --git a/index.html b/packages/server/index.html similarity index 100% rename from index.html rename to packages/server/index.html diff --git a/knexfile.js b/packages/server/knexfile.js similarity index 100% rename from knexfile.js rename to packages/server/knexfile.js diff --git a/packages/server/migrations/initial.ts b/packages/server/migrations/initial.ts new file mode 100644 index 0000000..803da57 --- /dev/null +++ b/packages/server/migrations/initial.ts @@ -0,0 +1,56 @@ +import { Knex } from "knex"; + +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) => { + b.string("username").primary().comment("user's login id"); + b.string("password_hash", 64).notNullable(); + b.string("password_salt", 64).notNullable(); + }); + 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_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"); + }); + await knex.schema.createTable("tags", (b) => { + b.string("name").primary(); + b.text("description"); + }); + 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"]); + }); + await knex.schema.createTable("permissions", (b) => { + b.string("username").notNullable(); + b.string("name").notNullable(); + b.primary(["username", "name"]); + b.foreign("username").references("users.username"); + }); + // create admin account. + await knex + .insert({ + 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."); +} diff --git a/packages/server/package.json b/packages/server/package.json new file mode 100644 index 0000000..0215d59 --- /dev/null +++ b/packages/server/package.json @@ -0,0 +1,38 @@ +{ + "name": "followed", + "version": "1.0.0", + "description": "", + "main": "build/app.js", + "scripts": { + "compile": "tsc", + "compile:watch": "tsc -w", + "build": "cd src/client && pnpm run build:prod", + "build:watch": "cd src/client && pnpm run build:watch", + "start": "node build/app.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@zip.js/zip.js": "^2.7.40", + "better-sqlite3": "^9.4.3", + "chokidar": "^3.6.0", + "jsonwebtoken": "^8.5.1", + "koa": "^2.15.2", + "koa-bodyparser": "^4.4.1", + "koa-compose": "^4.1.0", + "koa-router": "^12.0.1", + "kysely": "^0.27.3", + "natural-orderby": "^2.0.3", + "tiny-async-pool": "^1.3.0" + }, + "devDependencies": { + "dbtype": "*", + "@types/jsonwebtoken": "^8.5.9", + "@types/koa": "^2.15.0", + "@types/koa-bodyparser": "^4.3.12", + "@types/koa-compose": "^3.2.8", + "@types/koa-router": "^7.4.8", + "@types/node": "^14.18.63", + "@types/tiny-async-pool": "^1.0.5" + } +} diff --git a/packages/server/preload.ts b/packages/server/preload.ts new file mode 100644 index 0000000..396815a --- /dev/null +++ b/packages/server/preload.ts @@ -0,0 +1,7 @@ +// import { contextBridge, ipcRenderer } from "electron"; + +// contextBridge.exposeInMainWorld("electron", { +// passwordReset: async (username: string, toPw: string) => { +// return await ipcRenderer.invoke("reset_password", username, toPw); +// }, +// }); diff --git a/packages/server/src/SettingConfig.schema.json b/packages/server/src/SettingConfig.schema.json new file mode 100644 index 0000000..c4c7d48 --- /dev/null +++ b/packages/server/src/SettingConfig.schema.json @@ -0,0 +1,51 @@ +{ + "$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 + }, + "Permission": { + "type": "string", + "enum": ["ModifyTag", "QueryContent", "ModifyTagDesc"] + } + } +} diff --git a/packages/server/src/SettingConfig.ts b/packages/server/src/SettingConfig.ts new file mode 100644 index 0000000..f012079 --- /dev/null +++ b/packages/server/src/SettingConfig.ts @@ -0,0 +1,79 @@ +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; + /** + * secure only + */ + secure: boolean; + + /** + * guest permission + */ + guest: Permission[]; + /** + * JWT secret key. if you change its value, all access tokens are invalidated. + */ + jwt_secretkey: string; + /** + * the port which running server is binding on. + */ + port: number; + + 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; +} +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; + +const setEmptyToDefault = (target: any, default_table: SettingConfig) => { + let diff_occur = false; + 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)); + } + return ret as SettingConfig; +}; +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") { + throw new Error('process unknown value in NODE_ENV: must be either "development" or "production"'); + } + setting.mode = env ?? setting.mode; + } + return setting; +} diff --git a/packages/server/src/config.ts b/packages/server/src/config.ts new file mode 100644 index 0000000..dcf3463 --- /dev/null +++ b/packages/server/src/config.ts @@ -0,0 +1,22 @@ +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", + }, + debug: true, + }, + production: { + client: "sqlite3", + connection: { + filename: "./db.sqlite3", + }, + }, + }; +} diff --git a/packages/server/src/content/comic.ts b/packages/server/src/content/comic.ts new file mode 100644 index 0000000..1b241c7 --- /dev/null +++ b/packages/server/src/content/comic.ts @@ -0,0 +1,66 @@ +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[]; +} +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) { + super(path); + this.additional = option; + this.pagenum = 0; + } + 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) { + return; + } + const data = (await readAllFromZip(zip, entry)).toString("utf-8"); + this.desc = JSON.parse(data); + if (this.desc === undefined) { + throw new Error(`JSON.parse is returning undefined. ${this.path} desc.json format error`); + } + } + + async createDocumentBody(): Promise { + await this.initDesc(); + const basebody = await super.createDocumentBody(); + this.desc?.title; + 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; + tags.push(`type:${type}`); + return { + ...basebody, + title: this.desc.title, + additional: { + page: this.pagenum, + }, + tags: tags, + }; + } +} +registerContentReferrer(ComicReferrer); diff --git a/packages/server/src/content/file.ts b/packages/server/src/content/file.ts new file mode 100644 index 0000000..350248e --- /dev/null +++ b/packages/server/src/content/file.ts @@ -0,0 +1,93 @@ +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; + 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 { + readonly path: string; + // type = type; + static content_type = type; + protected hash: string | undefined; + protected stat: Stats | undefined; + + 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 ret = { + title: name, + basepath: dir, + additional: {}, + content_type: cons.content_type, + filename: base, + tags: [], + content_hash: await this.getHash(), + modified_at: await this.getMtime(), + } as DocumentBody; + return ret; + } + get type(): string { + return cons.content_type; + } + 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) + // 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; + 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}`); + ContstructorTable[s.content_type] = s; +} +export function createContentFile(type: string, path: string, option?: ContentConstructOption) { + const constructorMethod = ContstructorTable[type]; + 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); +} +export function getContentFileConstructor(type: string): ContentFileConstructor | undefined { + const ret = ContstructorTable[type]; + return ret; +} diff --git a/src/content/mod.ts b/packages/server/src/content/mod.ts similarity index 100% rename from src/content/mod.ts rename to packages/server/src/content/mod.ts diff --git a/src/content/video.ts b/packages/server/src/content/video.ts similarity index 71% rename from src/content/video.ts rename to packages/server/src/content/video.ts index d24e19f..46c7548 100644 --- a/src/content/video.ts +++ b/packages/server/src/content/video.ts @@ -2,8 +2,8 @@ import { ContentConstructOption, ContentFile, registerContentReferrer } from "./ import { createDefaultClass } from "./file"; export class VideoReferrer extends createDefaultClass("video") { - constructor(path: string, desc?: ContentConstructOption) { - super(path, desc); - } + constructor(path: string, desc?: ContentConstructOption) { + super(path, desc); + } } registerContentReferrer(VideoReferrer); diff --git a/packages/server/src/database.ts b/packages/server/src/database.ts new file mode 100644 index 0000000..fc681d9 --- /dev/null +++ b/packages/server/src/database.ts @@ -0,0 +1,47 @@ +import { existsSync } from "fs"; +import Knex from "knex"; +import { Knex as KnexConfig } from "./config"; +import { get_setting } from "./SettingConfig"; + +export async function connectDB() { + const env = get_setting().mode; + const config = KnexConfig.config[env]; + if (!config.connection) { + throw new Error("connection options required."); + } + const connection = config.connection; + if (typeof connection === "string") { + throw new Error("unknown connection options"); + } + if (typeof connection === "function") { + throw new Error("connection provider not supported..."); + } + 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 { + console.log("try to connect db"); + await knex.raw("select 1 + 1;"); + console.log("connect success"); + } catch (err) { + if (tries < 3) { + tries++; + console.error(`connection fail ${err} retry...`); + continue; + } else { + throw err; + } + } + break; + } + if (init_need) { + console.log("first execute: initialize database..."); + const migrate = await import("../migrations/initial"); + await migrate.up(knex); + } + return knex; +} diff --git a/packages/server/src/db/doc.ts b/packages/server/src/db/doc.ts new file mode 100644 index 0000000..544fc0c --- /dev/null +++ b/packages/server/src/db/doc.ts @@ -0,0 +1,235 @@ +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; +}; + +class KnexDocumentAccessor implements DocumentAccessor { + knex: Knex; + tagController: TagAccessor; + constructor(knex: Knex) { + this.knex = knex; + this.tagController = createKnexTagController(knex); + } + 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); + return docs; + } + 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) => { + tagCollected.add(x); + }); + }); + const tagCollectPromiseList = []; + const tagController = createKnexTagController(trx); + for (const it of tagCollected) { + const p = tagController.addTag({ name: it }); + tagCollectPromiseList.push(p); + } + await Promise.all(tagCollectPromiseList); + // add for each contents + const ret = []; + for (const content of content_list) { + const { tags, additional, ...rest } = content; + const id_lst = await trx + .insert({ + 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"); + } + ret.push(id); + } + return ret; + }); + } + 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"); + const id = id_lst[0]; + for (const it of tags) { + this.tagController.addTag({ name: it }); + } + if (tags.length > 0) { + await this.knex + .insert(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 }); + 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; + 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); + } + return { + ...first, + tags: ret_tags, + additional: first.additional !== null ? JSON.parse(first.additional) : {}, + }; + } + async findDeleted(content_type: string) { + const s = await this.knex + .select("*") + .where({ content_type: content_type }) + .whereNotNull("update_at") + .from("document"); + return s.map((x) => ({ + ...x, + tags: [], + additional: {}, + })); + } + async findList(option?: QueryListOption) { + option = option ?? {}; + const allow_tag = option.allow_tag ?? []; + const eager_loading = option.eager_loading ?? true; + const limit = option.limit ?? 20; + const use_offset = option.use_offset ?? false; + const offset = option.offset ?? 0; + const word = option.word; + const content_type = option.content_type; + const cursor = option.cursor; + + const buildquery = () => { + let query = this.knex.select("document.*"); + if (allow_tag.length > 0) { + query = query.from("doc_tag_relation as tags_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("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 (content_type !== undefined) { + query = query.where("content_type", "=", content_type); + } + if (use_offset) { + query = query.offset(offset); + } else { + if (cursor !== undefined) { + query = query.where("id", "<", cursor); + } + } + query = query.limit(limit); + query = query.orderBy("id", "desc"); + return query; + }; + let query = buildquery(); + // 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) { + 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) { + idmap[id].tags.push(tag_name); + } + } 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"); + return true; + } + return false; + } + 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 }).into("doc_tag_relation"); + c.tags.push(tag_name); + return true; + } + 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"); + c.tags.push(tag_name); + return true; + } +} +export const createKnexDocumentAccessor = (knex: Knex): DocumentAccessor => { + return new KnexDocumentAccessor(knex); +}; diff --git a/src/db/mod.ts b/packages/server/src/db/mod.ts similarity index 100% rename from src/db/mod.ts rename to packages/server/src/db/mod.ts diff --git a/packages/server/src/db/tag.ts b/packages/server/src/db/tag.ts new file mode 100644 index 0000000..6e1badc --- /dev/null +++ b/packages/server/src/db/tag.ts @@ -0,0 +1,61 @@ +import { Knex } from "knex"; +import { Tag, TagAccessor, TagCount } from "../model/tag"; +import { DBTagContentRelation } from "./doc"; + +type DBTags = { + name: string; + description?: string; +}; + +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"); + return result; + } + async getAllTagList(onlyname?: boolean) { + onlyname = onlyname ?? false; + 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; + return t[0]; + } + async addTag(tag: Tag) { + if ((await this.getTagByName(tag.name)) === undefined) { + await this.knex + .insert({ + 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"); + 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"); + return true; + } + return false; + } +} +export const createKnexTagController = (knex: Knex): TagAccessor => { + return new KnexTagAccessor(knex); +}; diff --git a/packages/server/src/db/user.ts b/packages/server/src/db/user.ts new file mode 100644 index 0000000..adfd255 --- /dev/null +++ b/packages/server/src/db/user.ts @@ -0,0 +1,88 @@ +import { Knex } from "knex"; +import { IUser, Password, UserAccessor, UserCreateInput } from "../model/user"; + +type PermissionTable = { + username: string; + name: string; +}; +type DBUser = { + 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) { + this.username = username; + this.password = pw; + this.knex = knex; + } + 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 }); + } + 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)) { + const r = await this.knex + .insert({ + username: this.username, + name: name, + }) + .into("permissions"); + return true; + } + return false; + } + async remove(name: string) { + const r = await this.knex + .from("permissions") + .where({ + 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))) { + return undefined; + } + 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"); + 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 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 { + createUser: createUserKnex, + findUser: findUserKenx, + delUser: delUserKnex, + }; +}; diff --git a/packages/server/src/diff/content_handler.ts b/packages/server/src/diff/content_handler.ts new file mode 100644 index 0000000..d70f90c --- /dev/null +++ b/packages/server/src/diff/content_handler.ts @@ -0,0 +1,121 @@ +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. +export class ContentDiffHandler { + /** content file list waiting to add */ + waiting_list: ContentList; + /** deleted contents */ + tombstone: Map; // hash, contentfile + doc_cntr: DocumentAccessor; + /** content type of handle */ + content_type: string; + constructor(cntr: DocumentAccessor, content_type: string) { + this.waiting_list = new ContentList(); + this.tombstone = new Map(); + this.doc_cntr = cntr; + this.content_type = content_type; + } + async setup() { + const deleted = await this.doc_cntr.findDeleted(this.content_type); + for (const it of deleted) { + this.tombstone.set(it.content_hash, it); + } + } + register(diff: IDiffWatcher) { + 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 (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. + if (dbc.length === 0) { + console.log("its not in waiting_list and db!!!: ", cpath); + return; + } + 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. + const cf = this.waiting_list.getByHash(content_hash); + if (cf) { + // 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); + this.waiting_list.deleteByHash(content_hash); + await this.doc_cntr.update({ + id: dbc[0].id, + deleted_at: null, + filename: newFilename, + basepath: newBasepath, + }); + return; + } + // invalidate db and add it to tombstone. + await this.doc_cntr.update({ + id: dbc[0].id, + deleted_at: Date.now(), + }); + this.tombstone.set(dbc[0].content_hash, dbc[0]); + } + private async OnCreated(cpath: string) { + const basepath = dirname(cpath); + const filename = basename(cpath); + console.log("createContentFile", cpath); + const content = createContentFile(this.content_type, cpath); + const hash = await content.getHash(); + const c = this.tombstone.get(hash); + if (c !== undefined) { + await this.doc_cntr.update({ + id: c.id, + deleted_at: null, + filename: filename, + 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); + const prev_filename = basename(prev_path); + const cur_basepath = dirname(cur_path); + 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) { + await this.waiting_list.delete(c); + const content = createContentFile(this.content_type, cur_path); + await this.waiting_list.set(content); + return; + } + const doc = await this.doc_cntr.findByPath(prev_basepath, prev_filename); + + if (doc.length === 0) { + await this.OnCreated(cur_path); + return; + } + + await this.doc_cntr.update({ + ...doc[0], + basepath: cur_basepath, + filename: cur_filename, + }); + } +} diff --git a/packages/server/src/diff/content_list.ts b/packages/server/src/diff/content_list.ts new file mode 100644 index 0000000..51035fb --- /dev/null +++ b/packages/server/src/diff/content_list.ts @@ -0,0 +1,59 @@ +import { ContentFile } from "../content/mod"; + +export class ContentList { + /** path map */ + private cl: Map; + /** hash map */ + private hl: Map; + + constructor() { + this.cl = new Map(); + this.hl = new Map(); + } + hasByHash(s: string) { + return this.hl.has(s); + } + hasByPath(p: string) { + return this.cl.has(p); + } + getByHash(s: string) { + return this.hl.get(s); + } + getByPath(p: string) { + return this.cl.get(p); + } + async set(c: ContentFile) { + const path = c.path; + const hash = await c.getHash(); + this.cl.set(path, c); + this.hl.set(hash, c); + } + /** delete content file */ + 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) { + const o = this.getByPath(p); + if (o === undefined) return false; + return await this.delete(o); + } + deleteByHash(s: string) { + const o = this.getByHash(s); + if (o === undefined) return false; + let r = true; + r = this.cl.delete(o.path) && r; + r = this.hl.delete(s) && r; + return r; + } + clear() { + this.cl.clear(); + this.hl.clear(); + } + getAll() { + return [...this.cl.values()]; + } +} diff --git a/packages/server/src/diff/diff.ts b/packages/server/src/diff/diff.ts new file mode 100644 index 0000000..df75b22 --- /dev/null +++ b/packages/server/src/diff/diff.ts @@ -0,0 +1,45 @@ +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 }; + doc_cntr: 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); + } + this.watching[content_type].register(watcher); + await watcher.setup(this.doc_cntr); + } + async commit(type: string, path: string) { + const list = this.watching[type].waiting_list; + const c = list.getByPath(path); + if (c === undefined) { + throw new Error("path is not exist"); + } + await list.delete(c); + const body = await c.createDocumentBody(); + const id = await this.doc_cntr.add(body); + return id; + } + 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 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(), + })); + } +} diff --git a/src/diff/mod.ts b/packages/server/src/diff/mod.ts similarity index 100% rename from src/diff/mod.ts rename to packages/server/src/diff/mod.ts diff --git a/packages/server/src/diff/router.ts b/packages/server/src/diff/router.ts new file mode 100644 index 0000000..21b7358 --- /dev/null +++ b/packages/server/src/diff/router.ts @@ -0,0 +1,83 @@ +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 }; +} + +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.type = "json"; +}; + +type PostAddedBody = { + 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); + } + return false; +} + +export const postAdded = (diffmgr: DiffManager) => async (ctx: Router.IRouterContext, next: Koa.Next) => { + const reqbody = ctx.request.body; + if (!checkPostAddedBody(reqbody)) { + sendError(400, "format exception"); + return; + } + 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"); + return; + } + 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"'); + return; + } + await diffmgr.commitAll(t); + ctx.body = { + ok: true, + }; + ctx.type = "json"; +}; +/* +export const getNotWatched = (diffmgr : DiffManager)=> (ctx:Router.IRouterContext,next:Koa.Next)=>{ + ctx.body = { + added: diffmgr.added.map(content_file_to_return), + deleted: diffmgr.deleted.map(content_file_to_return), + }; + ctx.type = 'json'; +}*/ + +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)); + return ret; +} diff --git a/packages/server/src/diff/watcher.ts b/packages/server/src/diff/watcher.ts new file mode 100644 index 0000000..0513374 --- /dev/null +++ b/packages/server/src/diff/watcher.ts @@ -0,0 +1,25 @@ +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 IDiffWatcher extends event.EventEmitter { + 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)); +} diff --git a/packages/server/src/diff/watcher/ComicConfig.schema.json b/packages/server/src/diff/watcher/ComicConfig.schema.json new file mode 100644 index 0000000..bd7b2ec --- /dev/null +++ b/packages/server/src/diff/watcher/ComicConfig.schema.json @@ -0,0 +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 + } + } +} diff --git a/src/diff/watcher/ComicConfig.ts b/packages/server/src/diff/watcher/ComicConfig.ts similarity index 92% rename from src/diff/watcher/ComicConfig.ts rename to packages/server/src/diff/watcher/ComicConfig.ts index 0ff864c..3cc93ea 100644 --- a/src/diff/watcher/ComicConfig.ts +++ b/packages/server/src/diff/watcher/ComicConfig.ts @@ -1,7 +1,7 @@ import { ConfigManager } from "../../util/configRW"; import ComicSchema from "./ComicConfig.schema.json"; export interface ComicConfig { - watch: string[]; + watch: string[]; } export const ComicConfig = new ConfigManager("comic_config.json", { watch: [] }, ComicSchema); diff --git a/src/diff/watcher/comic_watcher.ts b/packages/server/src/diff/watcher/comic_watcher.ts similarity index 61% rename from src/diff/watcher/comic_watcher.ts rename to packages/server/src/diff/watcher/comic_watcher.ts index ebad62b..5fd4942 100644 --- a/src/diff/watcher/comic_watcher.ts +++ b/packages/server/src/diff/watcher/comic_watcher.ts @@ -7,10 +7,10 @@ import { RecursiveWatcher } from "./recursive_watcher"; import { WatcherFilter } from "./watcher_filter"; const createComicWatcherBase = (path: string) => { - return new WatcherFilter(new RecursiveWatcher(path), (x) => x.endsWith(".zip")); + 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))); + const file = ComicConfig.get_config_file(); + console.log(`register comic ${file.watch.join(",")}`); + return new WatcherCompositer(file.watch.map((path) => createComicWatcherBase(path))); }; diff --git a/packages/server/src/diff/watcher/common_watcher.ts b/packages/server/src/diff/watcher/common_watcher.ts new file mode 100644 index 0000000..5d9444d --- /dev/null +++ b/packages/server/src/diff/watcher/common_watcher.ts @@ -0,0 +1,44 @@ +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; + +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); + } + private _path: string; + private _watcher: FSWatcher; + + constructor(path: string) { + super(); + this._path = path; + 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)); + } + } + }); + } + async setup(cntr: DocumentAccessor): Promise { + await setupHelp(this, this.path, cntr); + } + public get path() { + return this._path; + } + watchClose() { + this._watcher.close(); + } +} diff --git a/packages/server/src/diff/watcher/compositer.ts b/packages/server/src/diff/watcher/compositer.ts new file mode 100644 index 0000000..f993f61 --- /dev/null +++ b/packages/server/src/diff/watcher/compositer.ts @@ -0,0 +1,23 @@ +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); + } + emit(event: U, ...arg: Parameters): boolean { + return super.emit(event, ...arg); + } + constructor(refWatchers: IDiffWatcher[]) { + super(); + this.refWatchers = refWatchers; + for (const refWatcher of this.refWatchers) { + linkWatcher(refWatcher, this); + } + } + async setup(cntr: DocumentAccessor): Promise { + await Promise.all(this.refWatchers.map((x) => x.setup(cntr))); + } +} diff --git a/packages/server/src/diff/watcher/recursive_watcher.ts b/packages/server/src/diff/watcher/recursive_watcher.ts new file mode 100644 index 0000000..d14a31f --- /dev/null +++ b/packages/server/src/diff/watcher/recursive_watcher.ts @@ -0,0 +1,68 @@ +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 = { + /** @default true */ + watchFile?: boolean; + /** @default false */ + watchDir?: boolean; +}; + +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); + } + readonly path: string; + private watcher: FSWatcher; + + constructor( + path: string, + option: RecursiveWatcherOption = { + watchDir: false, + watchFile: true, + }, + ) { + super(); + this.path = path; + this.watcher = watch(path, { + persistent: true, + ignoreInitial: true, + depth: 100, + }); + option.watchFile ??= true; + if (option.watchFile) { + this.watcher + .on("add", (path) => { + const cpath = path; + // console.log("add ", cpath); + this.emit("create", cpath); + }) + .on("unlink", (path) => { + const cpath = path; + // console.log("unlink ", cpath); + this.emit("delete", cpath); + }); + } + if (option.watchDir) { + this.watcher + .on("addDir", (path) => { + const cpath = path; + this.emit("create", cpath); + }) + .on("unlinkDir", (path) => { + const cpath = path; + this.emit("delete", cpath); + }); + } + } + async setup(cntr: DocumentAccessor): Promise { + await setupRecursive(this, this.path, cntr); + } +} diff --git a/packages/server/src/diff/watcher/util.ts b/packages/server/src/diff/watcher/util.ts new file mode 100644 index 0000000..1823deb --- /dev/null +++ b/packages/server/src/diff/watcher/util.ts @@ -0,0 +1,38 @@ +import { EventEmitter } from "events"; +import { promises } from "fs"; +import { join } from "path"; +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. + let added = cur.filter((x) => !initial_filenames.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); + } + for (const it of deleted) { + const cpath = join(basepath, it); + watcher.emit("delete", cpath); + } +} +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 cur = await readdir(basepath); + setupCommon(watcher, basepath, initial_filenames, cur); +} +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))]); +} diff --git a/packages/server/src/diff/watcher/watcher_filter.ts b/packages/server/src/diff/watcher/watcher_filter.ts new file mode 100644 index 0000000..a8a41f9 --- /dev/null +++ b/packages/server/src/diff/watcher/watcher_filter.ts @@ -0,0 +1,46 @@ +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); + } + /** + * emit event + * @param event + * @param arg + * @returns `true` if the event had listeners, `false` otherwise. + */ + 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); + } else { + return super.emit("delete", cur); + } + } else { + if (this.filter(cur)) { + return super.emit("create", cur); + } + } + return false; + } else if (!this.filter(arg[0])) { + return false; + } else return super.emit(event, ...arg); + } + constructor(refWatcher: IDiffWatcher, filter: (filename: string) => boolean) { + super(); + this.refWatcher = refWatcher; + this.filter = filter; + linkWatcher(refWatcher, this); + } + setup(cntr: DocumentAccessor): Promise { + return this.refWatcher.setup(cntr); + } +} diff --git a/packages/server/src/login.ts b/packages/server/src/login.ts new file mode 100644 index 0000000..4d69feb --- /dev/null +++ b/packages/server/src/login.ts @@ -0,0 +1,245 @@ +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 { createKnexUserController } from "./db/mod"; +import { IUser, UserAccessor } from "./model/mod"; +import { sendError } from "./route/error_handler"; +import { get_setting } from "./SettingConfig"; + +type PayloadInfo = { + username: string; + permission: string[]; +}; + +export type UserState = { + 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; +}; +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"; +}; + +export const accessTokenName = "access_token"; +export const refreshTokenName = "refresh_token"; +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); +}; +export const getAdminRefreshTokenValue = () => { + const { jwt_secretkey } = get_setting(); + return publishRefreshToken(jwt_secretkey, "admin", refreshExpiredTime); +}; +const publishAccessToken = (secretKey: string, username: string, permission: string[], expiredtime: number) => { + const payload = sign( + { + username: username, + permission: permission, + }, + secretKey, + { expiresIn: expiredtime }, + ); + return payload; +}; +const publishRefreshToken = (secretKey: string, username: string, expiredtime: number) => { + const payload = sign({ username: username }, secretKey, { expiresIn: expiredtime }); + return payload; +}; +function setToken(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(); + const secretKey = setting.jwt_secretkey; + const body = ctx.request.body; + // 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."); + } + const username = body["username"]; + const password = body["password"]; + // check type + if (typeof username !== "string" || typeof password !== "string") { + return sendError(400, "invalid form : username or password is not string"); + } + // if admin login is forbidden? + if (username === "admin" && setting.forbid_remote_admin_login) { + return sendError(403, "forbidden remote admin login"); + } + const user = await userController.findUser(username); + // username not exist + if (user === undefined) return sendError(401, "not authorized"); + // password not matched + if (!user.password.check_password(password)) { + return sendError(401, "not authorized"); + } + // create token + const userPermission = await user.get_permissions(); + const payload = publishAccessToken(secretKey, user.username, userPermission, accessExpiredTime); + const payload2 = publishRefreshToken(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, + }; + 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(); + }; + 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(); + } + 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 { + 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 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"); + } + const user = await cntr.findUser(username); + if (user === undefined) { + return sendError(403, "not authorized"); + } + if (!user.password.check_password(oldpw)) { + return sendError(403, "not authorized"); + } + user.reset_password(newpw); + 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; +} + +export const getAdmin = async (cntr: UserAccessor) => { + 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"; +}; diff --git a/packages/server/src/model/doc.ts b/packages/server/src/model/doc.ts new file mode 100644 index 0000000..3e01e75 --- /dev/null +++ b/packages/server/src/model/doc.ts @@ -0,0 +1,129 @@ +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 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; +} + +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[]; + /** + * limit of list + * @default 20 + */ + limit?: number; + /** + * use offset if true, otherwise + * @default false + */ + use_offset?: boolean; + /** + * cursor of documents + */ + cursor?: number; + /** + * offset of documents + */ + offset?: number; + /** + * tag eager loading + * @default true + */ + eager_loading?: boolean; + /** + * content type + */ + content_type?: string; +}; + +export interface DocumentAccessor { + /** + * find list by option + * @returns documents list + */ + findList: (option?: QueryListOption) => Promise; + /** + * @returns document if exist, otherwise undefined + */ + 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; + /** + * find deleted content + */ + findDeleted: (content_type: string) => Promise; + /** + * search by in document + */ + search: (search_word: string) => Promise; + /** + * update document except tag. + */ + update: (c: Partial & { id: number }) => Promise; + /** + * add document + */ + add: (c: DocumentBody) => Promise; + /** + * add document list + */ + addList: (content_list: DocumentBody[]) => Promise; + /** + * delete document + * @returns if it exists, return true. + */ + 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; + /** + * @returns if success, return true + */ + delTag: (c: Document, tag_name: string) => Promise; +} diff --git a/src/model/mod.ts b/packages/server/src/model/mod.ts similarity index 100% rename from src/model/mod.ts rename to packages/server/src/model/mod.ts diff --git a/packages/server/src/model/tag.ts b/packages/server/src/model/tag.ts new file mode 100644 index 0000000..f2592fc --- /dev/null +++ b/packages/server/src/model/tag.ts @@ -0,0 +1,18 @@ +export interface Tag { + readonly name: string; + description?: string; +} + +export interface TagCount { + tag_name: string; + occurs: number; +} + +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; +} diff --git a/packages/server/src/model/user.ts b/packages/server/src/model/user.ts new file mode 100644 index 0000000..700793d --- /dev/null +++ b/packages/server/src/model/user.ts @@ -0,0 +1,84 @@ +import { createHmac, randomBytes } from "crypto"; + +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"); + return { + salt: secret, + 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; + this._hash = hash; + this._salt = salt; + } + 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); + } + get salt() { + return this._salt; + } + get hash() { + return this._hash; + } +} + +export interface UserCreateInput { + username: string; + password: string; +} + +export interface IUser { + readonly username: string; + readonly password: Password; + /** + * return user's permission list. + */ + get_permissions(): Promise; + /** + * add permission + * @param name permission name to add + * @returns if `name` doesn't exist, return true + */ + add(name: string): Promise; + /** + * remove permission + * @param name permission name to remove + * @returns if `name` exist, return true + */ + remove(name: string): Promise; + /** + * reset password. + * @param password password to set + */ + reset_password(password: string): Promise; +} + +export interface UserAccessor { + /** + * create user + * @returns if user exist, return undefined + */ + createUser: (input: UserCreateInput) => Promise; + /** + * find user + */ + findUser: (username: string) => Promise; + /** + * remove user + * @returns if user exist, true + */ + delUser: (username: string) => Promise; +} diff --git a/packages/server/src/permission/permission.ts b/packages/server/src/permission/permission.ts new file mode 100644 index 0000000..e6212e1 --- /dev/null +++ b/packages/server/src/permission/permission.ts @@ -0,0 +1,59 @@ +import Koa from "koa"; +import { UserState } from "../login"; +import { sendError } from "../route/error_handler"; + +export enum Permission { + // ======== + // not implemented + // admin only + /** remove document */ + // removeContent = 'removeContent', + + /** upload document */ + // uploadContent = 'uploadContent', + + /** modify document except base path, filename, content_hash. but admin can modify all. */ + // modifyContent = 'modifyContent', + + /** add tag into document */ + // addTagContent = 'addTagContent', + /** remove tag from document */ + // removeTagContent = 'removeTagContent', + /** ModifyTagInDoc */ + ModifyTag = "ModifyTag", + + /** find documents with query */ + // findAllContent = 'findAllContent', + /** find one document. */ + // findOneContent = 'findOneContent', + /** view content*/ + // viewContent = 'viewContent', + QueryContent = "QueryContent", + + /** modify description about the one tag. */ + 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."); + } 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(); +}; diff --git a/packages/server/src/route/all.ts b/packages/server/src/route/all.ts new file mode 100644 index 0000000..e99ba16 --- /dev/null +++ b/packages/server/src/route/all.ts @@ -0,0 +1,57 @@ +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); + }; +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/:rest(.*)", async (ctx, next) => { + const cont = ctx.params["content_type"] as string; + return await all_middleware(cont, ctx.params["rest"])(ctx, next); + }); + } +} diff --git a/packages/server/src/route/comic.ts b/packages/server/src/route/comic.ts new file mode 100644 index 0000000..652f9bc --- /dev/null +++ b/packages/server/src/route/comic.ts @@ -0,0 +1,96 @@ +import { Context, DefaultContext, DefaultState, Next } from "koa"; +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. + */ + +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; + } +} + +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]; + } +} + +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); + }); + + 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); + }); + } +} + +export default ComicRouter; diff --git a/packages/server/src/route/contents.ts b/packages/server/src/route/contents.ts new file mode 100644 index 0000000..dbb0cf6 --- /dev/null +++ b/packages/server/src/route/contents.ts @@ -0,0 +1,167 @@ +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."); + } + ctx.body = document; + 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."); + } + ctx.body = document.tags; + 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 || + query_content_type instanceof Array || + query_offset instanceof 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"); + } + 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 = { + limit: limit, + allow_tag: allow_tag, + word: word, + cursor: cursor, + eager_loading: true, + offset: offset, + use_offset: use_offset, + content_type: content_type, + }; + 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"]); + + 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"); + } + 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"; +}; +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"; +}; +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."); + } + 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) => { + 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()); + return ret; +}; + +export default getContentRouter; diff --git a/packages/server/src/route/context.ts b/packages/server/src/route/context.ts new file mode 100644 index 0000000..afd4874 --- /dev/null +++ b/packages/server/src/route/context.ts @@ -0,0 +1,8 @@ +export type ContentLocation = { + path: string; + type: string; + additional: object | undefined; +}; +export interface ContentContext { + location: ContentLocation; +} diff --git a/packages/server/src/route/error_handler.ts b/packages/server/src/route/error_handler.ts new file mode 100644 index 0000000..deb1da9 --- /dev/null +++ b/packages/server/src/route/error_handler.ts @@ -0,0 +1,49 @@ +import { Context, Next } from "koa"; + +export interface ErrorFormat { + code: number; + message: string; + detail?: string; +} + +class ClientRequestError implements Error { + name: string; + message: string; + stack?: string | undefined; + code: number; + + 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", +}; + +export const error_handler = async (ctx: Context, next: Next) => { + try { + await next(); + } catch (err) { + if (err instanceof ClientRequestError) { + const body: ErrorFormat = { + code: err.code, + message: code_to_message_table[err.code] ?? "", + detail: err.message, + }; + ctx.status = err.code; + ctx.body = body; + } else { + throw err; + } + } +}; + +export const sendError = (code: number, message?: string) => { + throw new ClientRequestError(code, message ?? ""); +}; + +export default error_handler; diff --git a/packages/server/src/route/tags.ts b/packages/server/src/route/tags.ts new file mode 100644 index 0000000..edbbb75 --- /dev/null +++ b/packages/server/src/route/tags.ts @@ -0,0 +1,29 @@ +import { Context, Next } from "koa"; +import Router, { RouterContext } from "koa-router"; +import { TagAccessor } from "../model/tag"; +import { createPermissionCheckMiddleware as PerCheck, Permission } from "../permission/permission"; +import { sendError } from "./error_handler"; + +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"); + } + ctx.body = c; + ctx.type = "json"; + }); + return router; +} diff --git a/packages/server/src/route/util.ts b/packages/server/src/route/util.ts new file mode 100644 index 0000000..c07f9c4 --- /dev/null +++ b/packages/server/src/route/util.ts @@ -0,0 +1,37 @@ +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; + else return Number.parseInt(s); +} +export function ParseQueryArray(s: string[] | string | undefined) { + s = s ?? []; + const r = s instanceof Array ? s : [s]; + return r.map((x) => decodeURIComponent(x)); +} +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; + + if (s === "true") { + value = true; + } else if (s === "false") { + value = false; + } else if (s === undefined) { + value = undefined; + } else return [false, undefined]; + return [true, value]; +} + +export function since_last_modified(ctx: Context, last_modified: Date): boolean { + const con = ctx.get("If-Modified-Since"); + if (con === "") return false; + const mdate = new Date(con); + if (last_modified > mdate) return false; + ctx.status = 304; + return true; +} diff --git a/packages/server/src/route/video.ts b/packages/server/src/route/video.ts new file mode 100644 index 0000000..cc96367 --- /dev/null +++ b/packages/server/src/route/video.ts @@ -0,0 +1,67 @@ +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}`); + return; + } + ctx.response.type = ext; + const range_text = ctx.request.get("range"); + 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("Accept-Ranges", "bytes"); + if (range_text === "") { + end = 1024 * 512; + end = Math.min(end, stat.size - 1); + if (start > end) { + ctx.status = 416; + return; + } + ctx.status = 200; + ctx.length = stat.size; + let stream = createReadStream(path); + ctx.body = stream; + } else { + const m = range_text.match(/^bytes=(\d+)-(\d*)/); + 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) { + 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. + } +} + +export class VideoRouter extends Router { + constructor() { + super(); + 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); + }); + } +} + +export default VideoRouter; diff --git a/packages/server/src/search/indexer.ts b/packages/server/src/search/indexer.ts new file mode 100644 index 0000000..3879b70 --- /dev/null +++ b/packages/server/src/search/indexer.ts @@ -0,0 +1,12 @@ +export interface PaginationOption { + cursor: number; + limit: number; +} + +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[]; +} diff --git a/packages/server/src/search/tokenizer.ts b/packages/server/src/search/tokenizer.ts new file mode 100644 index 0000000..87d180e --- /dev/null +++ b/packages/server/src/search/tokenizer.ts @@ -0,0 +1,9 @@ +export interface ITokenizer { + tokenize(s: string): string[]; +} + +export class DefaultTokenizer implements ITokenizer { + tokenize(s: string): string[] { + return s.split(" "); + } +} diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts new file mode 100644 index 0000000..a9cdc81 --- /dev/null +++ b/packages/server/src/server.ts @@ -0,0 +1,237 @@ +import Koa from "koa"; +import Router from "koa-router"; + +import { connectDB } from "./database"; +import { createDiffRouter, DiffManager } from "./diff/mod"; +import { get_setting, SettingConfig } from "./SettingConfig"; + +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 { createComicWatcher } from "./diff/watcher/comic_watcher"; +import { DocumentAccessor, TagAccessor, UserAccessor } from "./model/mod"; +import { getTagRounter } from "./route/tags"; + +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; + }) { + 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"); + } + private async setup() { + const setting = get_setting(); + const app = this.app; + + if (setting.cli) { + const userAdmin = await getAdmin(this.userController); + if (await isAdminFirst(userAdmin)) { + const rl = createReadlineInterface({ + input: process.stdin, + output: process.stdout, + }); + const pw = await new Promise((res: (data: string) => void, err) => { + rl.question("put admin password :", (data) => { + res(data); + }); + }); + rl.close(); + userAdmin.reset_password(pw); + } + } + app.use(bodyparser()); + app.use(error_handler); + app.use(createUserMiddleWare(this.userController)); + + let diff_router = createDiffRouter(this.diffManger); + this.diffManger.register("comic", createComicWatcher()); + + console.log("setup router"); + + let router = new Router(); + 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()); + + const content_router = getContentRouter(this.documentController); + 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()); + + 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`); + }); + } + 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; + 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"); + } + }); + }; + 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) => { + const docId = Number.parseInt(ctx.params["id"]); + const doc = await this.documentController.findById(docId, true); + let meta; + if (doc === undefined) { + ctx.status = 404; + meta = NotFoundContent(); + } else { + ctx.status = 200; + 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); + }; + 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 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"); + } + } + + 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"); + } + } + 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"); + } 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"); + } + } + start_server() { + let setting = get_setting(); + // 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"); + } + static async createServer() { + const setting = get_setting(); + let db = await connectDB(); + + const app = new ServerApplication({ + userController: createKnexUserController(db), + documentController: createKnexDocumentAccessor(db), + tagController: createKnexTagController(db), + }); + await app.setup(); + return app; + } +} + +export async function create_server() { + return await ServerApplication.createServer(); +} + +export default { create_server }; diff --git a/packages/server/src/types/db.d.ts b/packages/server/src/types/db.d.ts new file mode 100644 index 0000000..7e1a63e --- /dev/null +++ b/packages/server/src/types/db.d.ts @@ -0,0 +1,34 @@ +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; + }; + } +} diff --git a/src/types/json.ts b/packages/server/src/types/json.ts similarity index 100% rename from src/types/json.ts rename to packages/server/src/types/json.ts diff --git a/packages/server/src/util/configRW.ts b/packages/server/src/util/configRW.ts new file mode 100644 index 0000000..c769c9c --- /dev/null +++ b/packages/server/src/util/configRW.ts @@ -0,0 +1,51 @@ +import { existsSync, promises as fs, readFileSync, writeFileSync } from "fs"; +import { validate } from "jsonschema"; + +export class ConfigManager { + path: string; + default_config: T; + 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() }; + return this.config; + } + private emptyToDefault(target: T) { + let occur = false; + for (const key in this.default_config) { + if (key === undefined || key in target) { + continue; + } + target[key] = this.default_config[key]; + occur = true; + } + return occur; + } + 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 result = validate(ret, this.schema); + if (!result.valid) { + throw new Error(result.toString()); + } + return ret; + } + 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); + } +} diff --git a/packages/server/src/util/type_check.ts b/packages/server/src/util/type_check.ts new file mode 100644 index 0000000..160eab9 --- /dev/null +++ b/packages/server/src/util/type_check.ts @@ -0,0 +1,15 @@ +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; + defined = defined.trim(); + if (defined.endsWith("[]")) { + if (!(obj[it] instanceof Array)) { + return false; + } + } else if (defined !== typeof obj[it]) { + return false; + } + } + return true; +} diff --git a/packages/server/src/util/zipwrap.ts b/packages/server/src/util/zipwrap.ts new file mode 100644 index 0000000..46270e5 --- /dev/null +++ b/packages/server/src/util/zipwrap.ts @@ -0,0 +1,33 @@ +import { ZipEntry } 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 { + return new StreamZip.async({ + file: path, + storeEntries: true, + }); +} +export async function entriesByNaturalOrder(zip: ZipAsync) { + const entries = await zip.entries(); + const ret = orderBy(Object.values(entries), (v) => v.name); + return ret; +} + +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))); + }); +} diff --git a/tsconfig.json b/packages/server/tsconfig.json similarity index 100% rename from tsconfig.json rename to packages/server/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a70546..e92ae14 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,382 +1,1070 @@ -lockfileVersion: 5.4 +lockfileVersion: '6.0' -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 - dprint: ^0.36.1 - 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 +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false -dependencies: - '@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_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 +importers: -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 + .: + devDependencies: + '@biomejs/biome': + specifier: 1.6.3 + version: 1.6.3 + + packages/client: + dependencies: + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + devDependencies: + '@types/react': + specifier: ^18.2.66 + version: 18.2.71 + '@types/react-dom': + specifier: ^18.2.22 + version: 18.2.22 + '@typescript-eslint/eslint-plugin': + specifier: ^7.2.0 + version: 7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.3) + '@typescript-eslint/parser': + specifier: ^7.2.0 + version: 7.4.0(eslint@8.57.0)(typescript@5.4.3) + '@vitejs/plugin-react-swc': + specifier: ^3.5.0 + version: 3.6.0(vite@5.2.6) + eslint: + specifier: ^8.57.0 + version: 8.57.0 + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.0(eslint@8.57.0) + eslint-plugin-react-refresh: + specifier: ^0.4.6 + version: 0.4.6(eslint@8.57.0) + typescript: + specifier: ^5.2.2 + version: 5.4.3 + vite: + specifier: ^5.2.0 + version: 5.2.6 + + packages/dbtype: + devDependencies: + '@types/better-sqlite3': + specifier: ^7.6.9 + version: 7.6.9 + better-sqlite3: + specifier: ^9.4.3 + version: 9.4.3 + kysely: + specifier: ^0.27.3 + version: 0.27.3 + kysely-codegen: + specifier: ^0.14.1 + version: 0.14.1(better-sqlite3@9.4.3)(kysely@0.27.3) + + packages/server: + dependencies: + '@zip.js/zip.js': + specifier: ^2.7.40 + version: 2.7.40 + better-sqlite3: + specifier: ^9.4.3 + version: 9.4.3 + chokidar: + specifier: ^3.6.0 + version: 3.6.0 + jsonwebtoken: + specifier: ^8.5.1 + version: 8.5.1 + koa: + specifier: ^2.15.2 + version: 2.15.2 + koa-bodyparser: + specifier: ^4.4.1 + version: 4.4.1 + koa-compose: + specifier: ^4.1.0 + version: 4.1.0 + koa-router: + specifier: ^12.0.1 + version: 12.0.1 + kysely: + specifier: ^0.27.3 + version: 0.27.3 + natural-orderby: + specifier: ^2.0.3 + version: 2.0.3 + tiny-async-pool: + specifier: ^1.3.0 + version: 1.3.0 + devDependencies: + '@types/jsonwebtoken': + specifier: ^8.5.9 + version: 8.5.9 + '@types/koa': + specifier: ^2.15.0 + version: 2.15.0 + '@types/koa-bodyparser': + specifier: ^4.3.12 + version: 4.3.12 + '@types/koa-compose': + specifier: ^3.2.8 + version: 3.2.8 + '@types/koa-router': + specifier: ^7.4.8 + version: 7.4.8 + '@types/node': + specifier: ^14.18.63 + version: 14.18.63 + '@types/tiny-async-pool': + specifier: ^1.0.5 + version: 1.0.5 packages: - /7zip-bin/5.1.1: - resolution: {integrity: sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==} + /@aashutoshrathi/word-wrap@1.2.6: + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} dev: true - /@develar/schema-utils/2.6.5: - resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} - engines: {node: '>= 8.9.0'} + /@biomejs/biome@1.6.3: + resolution: {integrity: sha512-Xnp/TIpIcTnRA4LwerJuoGYQJEqwXtn5AL0U0OPXll/QGbAKmcUAfizU880xTwZRD4f53iceqODLDaD3wxYlIw==} + engines: {node: '>=14.*'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@biomejs/cli-darwin-arm64': 1.6.3 + '@biomejs/cli-darwin-x64': 1.6.3 + '@biomejs/cli-linux-arm64': 1.6.3 + '@biomejs/cli-linux-arm64-musl': 1.6.3 + '@biomejs/cli-linux-x64': 1.6.3 + '@biomejs/cli-linux-x64-musl': 1.6.3 + '@biomejs/cli-win32-arm64': 1.6.3 + '@biomejs/cli-win32-x64': 1.6.3 + dev: true + + /@biomejs/cli-darwin-arm64@1.6.3: + resolution: {integrity: sha512-0E8PGu3/8HSkBJdtjno+niJE1ANS/12D7sPK65vw5lTBYmmaYwJdfclDp6XO0IAX7uVd3/YtXlsEua0SVrNt3Q==} + engines: {node: '>=14.*'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-darwin-x64@1.6.3: + resolution: {integrity: sha512-UWu0We/aIRtWXgJKe6ygWt2xR0yXs64BwWqtZbfxBojRn3jgW8UdFAkV5yiUOX3TQlsV6BZH1EQaUAVsccUeeA==} + engines: {node: '>=14.*'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64-musl@1.6.3: + resolution: {integrity: sha512-AntGCSfLN1nPcQj4VOk3X2JgnDw07DaPC8BuBmRcsRmn+7GPSWLllVN5awIKlRPZEbGJtSnLkTiDc5Bxw8OiuA==} + engines: {node: '>=14.*'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-arm64@1.6.3: + resolution: {integrity: sha512-wFVkQw38kOssfnkbpSh6ums5TaElw3RAt5i/VZwHmgR2nQgE0fHXLO7HwIE9VBkOEdbiIFq+2PxvFIHuJF3z3Q==} + engines: {node: '>=14.*'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64-musl@1.6.3: + resolution: {integrity: sha512-GelAvGsUwbxfFpKLG+7+dvDmbrfkGqn08sL8CMQrGnhjE1krAqHWiXQsjfmi0UMFdMsk7hbc4oSAP+1+mrXcHQ==} + engines: {node: '>=14.*'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-linux-x64@1.6.3: + resolution: {integrity: sha512-vyn8TQaTZg617hjqFitwGmb1St5XXvq6I3vmxU/QFalM74BryMSvYCrYWb2Yw/TkykdEwZTMGYp+SWHRb04fTg==} + engines: {node: '>=14.*'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-arm64@1.6.3: + resolution: {integrity: sha512-Gx8N2Tixke6pAI1BniteCVZgUUmaFEDYosdWxoaCus15BZI/7RcBxhsRM0ZL/lC66StSQ8vHl8JBrrld1k570Q==} + engines: {node: '>=14.*'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@biomejs/cli-win32-x64@1.6.3: + resolution: {integrity: sha512-meungPJw64SqoR7LXY1wG7GC4+4wgpyThdFUMGXa6PCe0BLFOIOcZ9VMj9PstuczMPdgmt/BUMPsj25dK1VO8A==} + engines: {node: '>=14.*'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/aix-ppc64@0.20.2: + resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.20.2: + resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.20.2: + resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.20.2: + resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.20.2: + resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.20.2: + resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.20.2: + resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.20.2: + resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.20.2: + resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.20.2: + resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.20.2: + resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.20.2: + resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.20.2: + resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.20.2: + resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.20.2: + resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.20.2: + resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.20.2: + resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.20.2: + resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.20.2: + resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.20.2: + resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.20.2: + resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.20.2: + resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.20.2: + resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 8.57.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@eslint-community/regexpp@4.10.0: + resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true + + /@eslint/eslintrc@2.1.4: + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - ajv-keywords: 3.5.2_ajv@6.12.6 - dev: true - - /@electron/get/1.14.1: - resolution: {integrity: sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==} - engines: {node: '>=8.6'} - dependencies: debug: 4.3.4 - env-paths: 2.2.1 - fs-extra: 8.1.0 - got: 9.6.0 - progress: 2.0.3 - semver: 6.3.0 - sumchecker: 3.0.1 - optionalDependencies: - global-agent: 3.0.0 - global-tunnel-ng: 2.7.1 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color dev: true - /@electron/universal/1.0.5: - resolution: {integrity: sha512-zX9O6+jr2NMyAdSkwEUlyltiI4/EBLu2Ls/VD3pUQdi3cAYeYfdQnT2AJJ38HE4QxLccbU13LSpccw1IWlkyag==} - engines: {node: '>=8.6'} + /@eslint/js@8.57.0: + resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /@humanwhocodes/config-array@0.11.14: + resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} + engines: {node: '>=10.10.0'} dependencies: - '@malept/cross-spawn-promise': 1.1.1 - asar: 3.1.0 + '@humanwhocodes/object-schema': 2.0.2 debug: 4.3.4 - dir-compare: 2.4.0 - fs-extra: 9.1.0 + minimatch: 3.1.2 transitivePeerDependencies: - supports-color dev: true - /@gar/promisify/1.1.3: - resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} - dev: false + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + + /@humanwhocodes/object-schema@2.0.2: + resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} + dev: true + + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: true + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: true + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + dev: true + + /@rollup/rollup-android-arm-eabi@4.13.0: + resolution: {integrity: sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true optional: true - /@louislam/sqlite3/6.0.1: - resolution: {integrity: sha512-QGLj5bjQ+O4YSPj/qxtEAArbIqW9wNzBUamlIcRbvFjFiNokItwdubqL2Gl5iX0q1mUn3Z6NoFO1rrAZ/qqlsA==} + /@rollup/rollup-android-arm64@4.13.0: + resolution: {integrity: sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==} + cpu: [arm64] + os: [android] requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64@4.13.0: + resolution: {integrity: sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64@4.13.0: + resolution: {integrity: sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf@4.13.0: + resolution: {integrity: sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu@4.13.0: + resolution: {integrity: sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl@4.13.0: + resolution: {integrity: sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu@4.13.0: + resolution: {integrity: sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu@4.13.0: + resolution: {integrity: sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl@4.13.0: + resolution: {integrity: sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc@4.13.0: + resolution: {integrity: sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc@4.13.0: + resolution: {integrity: sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc@4.13.0: + resolution: {integrity: sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-darwin-arm64@1.4.11: + resolution: {integrity: sha512-C1j1Qp/IHSelVWdEnT7f0iONWxQz6FAqzjCF2iaL+0vFg4V5f2nlgrueY8vj5pNNzSGhrAlxsMxEIp4dj1MXkg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-darwin-x64@1.4.11: + resolution: {integrity: sha512-0TTy3Ni8ncgaMCchSQ7FK8ZXQLlamy0FXmGWbR58c+pVZWYZltYPTmheJUvVcR0H2+gPAymRKyfC0iLszDALjg==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm-gnueabihf@1.4.11: + resolution: {integrity: sha512-XJLB71uw0rog4DjYAPxFGAuGCBQpgJDlPZZK6MTmZOvI/1t0+DelJ24IjHIxk500YYM26Yv47xPabqFPD7I2zQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-gnu@1.4.11: + resolution: {integrity: sha512-vYQwzJvm/iu052d5Iw27UFALIN5xSrGkPZXxLNMHPySVko2QMNNBv35HLatkEQHbQ3X+VKSW9J9SkdtAvAVRAQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-musl@1.4.11: + resolution: {integrity: sha512-eV+KduiRYUFjPsvbZuJ9aknQH9Tj0U2/G9oIZSzLx/18WsYi+upzHbgxmIIHJ2VJgfd7nN40RI/hMtxNsUzR/g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-gnu@1.4.11: + resolution: {integrity: sha512-WA1iGXZ2HpqM1OR9VCQZJ8sQ1KP2or9O4bO8vWZo6HZJIeoQSo7aa9waaCLRpkZvkng1ct/TF/l6ymqSNFXIzQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-musl@1.4.11: + resolution: {integrity: sha512-UkVJToKf0owwQYRnGvjHAeYVDfeimCEcx0VQSbJoN7Iy0ckRZi7YPlmWJU31xtKvikE2bQWCOVe0qbSDqqcWXA==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-arm64-msvc@1.4.11: + resolution: {integrity: sha512-35khwkyly7lF5NDSyvIrukBMzxPorgc5iTSDfVO/LvnmN5+fm4lTlrDr4tUfTdOhv3Emy7CsKlsNAeFRJ+Pm+w==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-ia32-msvc@1.4.11: + resolution: {integrity: sha512-Wx8/6f0ufgQF2pbVPsJ2dAmFLwIOW+xBE5fxnb7VnEbGkTgP1qMDWiiAtD9rtvDSuODG3i1AEmAak/2HAc6i6A==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-x64-msvc@1.4.11: + resolution: {integrity: sha512-0xRFW6K9UZQH2NVC/0pVB0GJXS45lY24f+6XaPBF1YnMHd8A8GoHl7ugyM5yNUTe2AKhSgk5fJV00EJt/XBtdQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core@1.4.11: + resolution: {integrity: sha512-WKEakMZxkVwRdgMN4AMJ9K5nysY8g8npgQPczmjBeNK5In7QEAZAJwnyccrWwJZU0XjVeHn2uj+XbOKdDW17rg==} + engines: {node: '>=10'} + requiresBuild: true + peerDependencies: + '@swc/helpers': ^0.5.0 peerDependenciesMeta: - node-gyp: + '@swc/helpers': optional: true dependencies: - '@mapbox/node-pre-gyp': 1.0.9 - node-addon-api: 3.2.1 + '@swc/counter': 0.1.3 + '@swc/types': 0.1.6 optionalDependencies: - node-gyp: 8.4.1 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /@malept/cross-spawn-promise/1.1.1: - resolution: {integrity: sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==} - engines: {node: '>= 10'} - dependencies: - cross-spawn: 7.0.3 + '@swc/core-darwin-arm64': 1.4.11 + '@swc/core-darwin-x64': 1.4.11 + '@swc/core-linux-arm-gnueabihf': 1.4.11 + '@swc/core-linux-arm64-gnu': 1.4.11 + '@swc/core-linux-arm64-musl': 1.4.11 + '@swc/core-linux-x64-gnu': 1.4.11 + '@swc/core-linux-x64-musl': 1.4.11 + '@swc/core-win32-arm64-msvc': 1.4.11 + '@swc/core-win32-ia32-msvc': 1.4.11 + '@swc/core-win32-x64-msvc': 1.4.11 dev: true - /@malept/flatpak-bundler/0.4.0: - resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} - engines: {node: '>= 10.0.0'} + /@swc/counter@0.1.3: + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + dev: true + + /@swc/types@0.1.6: + resolution: {integrity: sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==} dependencies: + '@swc/counter': 0.1.3 + dev: true + + /@types/accepts@1.3.7: + resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} + dependencies: + '@types/node': 14.18.63 + dev: true + + /@types/better-sqlite3@7.6.9: + resolution: {integrity: sha512-FvktcujPDj9XKMJQWFcl2vVl7OdRIqsSRX9b0acWwTmwLK9CF2eqo/FRcmMLNpugKoX/avA6pb7TorDLmpgTnQ==} + dependencies: + '@types/node': 14.18.63 + dev: true + + /@types/body-parser@1.19.5: + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + dependencies: + '@types/connect': 3.4.38 + '@types/node': 14.18.63 + dev: true + + /@types/connect@3.4.38: + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + dependencies: + '@types/node': 14.18.63 + dev: true + + /@types/content-disposition@0.5.8: + resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==} + dev: true + + /@types/cookies@0.9.0: + resolution: {integrity: sha512-40Zk8qR147RABiQ7NQnBzWzDcjKzNrntB5BAmeGCb2p/MIyOE+4BVvc17wumsUqUw00bJYqoXFHYygQnEFh4/Q==} + dependencies: + '@types/connect': 3.4.38 + '@types/express': 4.17.21 + '@types/keygrip': 1.0.6 + '@types/node': 14.18.63 + dev: true + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: true + + /@types/express-serve-static-core@4.17.43: + resolution: {integrity: sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==} + dependencies: + '@types/node': 14.18.63 + '@types/qs': 6.9.14 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + dev: true + + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.17.43 + '@types/qs': 6.9.14 + '@types/serve-static': 1.15.5 + dev: true + + /@types/http-assert@1.5.5: + resolution: {integrity: sha512-4+tE/lwdAahgZT1g30Jkdm9PzFRde0xwxBNUyRsCitRvCQB90iuA2uJYdUnhnANRcqGXaWOGY4FEoxeElNAK2g==} + dev: true + + /@types/http-errors@2.0.4: + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: true + + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true + + /@types/jsonwebtoken@8.5.9: + resolution: {integrity: sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==} + dependencies: + '@types/node': 14.18.63 + dev: true + + /@types/keygrip@1.0.6: + resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + dev: true + + /@types/koa-bodyparser@4.3.12: + resolution: {integrity: sha512-hKMmRMVP889gPIdLZmmtou/BijaU1tHPyMNmcK7FAHAdATnRcGQQy78EqTTxLH1D4FTsrxIzklAQCso9oGoebQ==} + dependencies: + '@types/koa': 2.15.0 + dev: true + + /@types/koa-compose@3.2.8: + resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} + dependencies: + '@types/koa': 2.15.0 + dev: true + + /@types/koa-router@7.4.8: + resolution: {integrity: sha512-SkWlv4F9f+l3WqYNQHnWjYnyTxYthqt8W9az2RTdQW7Ay8bc00iRZcrb8MC75iEfPqnGcg2csEl8tTG1NQPD4A==} + dependencies: + '@types/koa': 2.15.0 + dev: true + + /@types/koa@2.15.0: + resolution: {integrity: sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==} + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.8 + '@types/cookies': 0.9.0 + '@types/http-assert': 1.5.5 + '@types/http-errors': 2.0.4 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 14.18.63 + dev: true + + /@types/mime@1.3.5: + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: true + + /@types/mime@3.0.4: + resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} + dev: true + + /@types/node@14.18.63: + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + dev: true + + /@types/prop-types@15.7.12: + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + dev: true + + /@types/qs@6.9.14: + resolution: {integrity: sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==} + dev: true + + /@types/range-parser@1.2.7: + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: true + + /@types/react-dom@18.2.22: + resolution: {integrity: sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==} + dependencies: + '@types/react': 18.2.71 + dev: true + + /@types/react@18.2.71: + resolution: {integrity: sha512-PxEsB9OjmQeYGffoWnYAd/r5FiJuUw2niFQHPc2v2idwh8wGPkkYzOHuinNJJY6NZqfoTCiOIizDOz38gYNsyw==} + dependencies: + '@types/prop-types': 15.7.12 + '@types/scheduler': 0.23.0 + csstype: 3.1.3 + dev: true + + /@types/scheduler@0.23.0: + resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==} + dev: true + + /@types/semver@7.5.8: + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} + dev: true + + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + dependencies: + '@types/mime': 1.3.5 + '@types/node': 14.18.63 + dev: true + + /@types/serve-static@1.15.5: + resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + dependencies: + '@types/http-errors': 2.0.4 + '@types/mime': 3.0.4 + '@types/node': 14.18.63 + dev: true + + /@types/tiny-async-pool@1.0.5: + resolution: {integrity: sha512-8hqr+s4rBthBtb+k02NXejl7BGVbj7CD/ZB2rMSvoSjXO52aXbtm9q/JEey5uDjzADs/zXEo7bU9iX+M6glAUA==} + dev: true + + /@typescript-eslint/eslint-plugin@7.4.0(@typescript-eslint/parser@7.4.0)(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@eslint-community/regexpp': 4.10.0 + '@typescript-eslint/parser': 7.4.0(eslint@8.57.0)(typescript@5.4.3) + '@typescript-eslint/scope-manager': 7.4.0 + '@typescript-eslint/type-utils': 7.4.0(eslint@8.57.0)(typescript@5.4.3) + '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.3) + '@typescript-eslint/visitor-keys': 7.4.0 debug: 4.3.4 - fs-extra: 9.1.0 - lodash: 4.17.21 - tmp-promise: 3.0.3 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.3) + typescript: 5.4.3 transitivePeerDependencies: - supports-color dev: true - /@mapbox/node-pre-gyp/1.0.9: - resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==} - hasBin: true + /@typescript-eslint/parser@7.4.0(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: - detect-libc: 2.0.1 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.6.7 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.3.7 - tar: 6.1.11 + '@typescript-eslint/scope-manager': 7.4.0 + '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.3) + '@typescript-eslint/visitor-keys': 7.4.0 + debug: 4.3.4 + eslint: 8.57.0 + typescript: 5.4.3 transitivePeerDependencies: - - encoding - supports-color + dev: true + + /@typescript-eslint/scope-manager@7.4.0: + resolution: {integrity: sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/visitor-keys': 7.4.0 + dev: true + + /@typescript-eslint/type-utils@7.4.0(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.3) + '@typescript-eslint/utils': 7.4.0(eslint@8.57.0)(typescript@5.4.3) + debug: 4.3.4 + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.4.3) + typescript: 5.4.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types@7.4.0: + resolution: {integrity: sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw==} + engines: {node: ^18.18.0 || >=20.0.0} + dev: true + + /@typescript-eslint/typescript-estree@7.4.0(typescript@5.4.3): + resolution: {integrity: sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/visitor-keys': 7.4.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.6.0 + ts-api-utils: 1.3.0(typescript@5.4.3) + typescript: 5.4.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils@7.4.0(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.8 + '@typescript-eslint/scope-manager': 7.4.0 + '@typescript-eslint/types': 7.4.0 + '@typescript-eslint/typescript-estree': 7.4.0(typescript@5.4.3) + eslint: 8.57.0 + semver: 7.6.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/visitor-keys@7.4.0: + resolution: {integrity: sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA==} + engines: {node: ^18.18.0 || >=20.0.0} + dependencies: + '@typescript-eslint/types': 7.4.0 + eslint-visitor-keys: 3.4.3 + dev: true + + /@ungap/structured-clone@1.2.0: + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + dev: true + + /@vitejs/plugin-react-swc@3.6.0(vite@5.2.6): + resolution: {integrity: sha512-XFRbsGgpGxGzEV5i5+vRiro1bwcIaZDIdBRP16qwm+jP68ue/S8FJTBEgOeojtVDYrbSua3XFp71kC8VJE6v+g==} + peerDependencies: + vite: ^4 || ^5 + dependencies: + '@swc/core': 1.4.11 + vite: 5.2.6 + transitivePeerDependencies: + - '@swc/helpers' + dev: true + + /@zip.js/zip.js@2.7.40: + resolution: {integrity: sha512-kSYwO0Wth6G66QM4CejZqG0nRhBsVVTaR18M/Ta8EcqcvaV0dYrnDDyKAstfy0V5+ejK4b9w5xc1W0ECATJTvA==} + engines: {bun: '>=0.7.0', deno: '>=1.0.0', node: '>=16.5.0'} dev: false - /@npmcli/fs/1.1.1: - resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} - dependencies: - '@gar/promisify': 1.1.3 - semver: 7.3.7 - dev: false - optional: true - - /@npmcli/move-file/1.1.2: - resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==} - engines: {node: '>=10'} - dependencies: - mkdirp: 1.0.4 - rimraf: 3.0.2 - dev: false - optional: true - - /@sindresorhus/is/0.14.0: - resolution: {integrity: sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==} - engines: {node: '>=6'} - dev: true - - /@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: - resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==} - engines: {node: '>= 6'} - dev: false - optional: true - - /@tootallnate/once/2.0.0: - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - dev: true - - /@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: - resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} - dependencies: - '@types/connect': 3.4.35 - '@types/node': 14.18.21 - - /@types/connect/3.4.35: - resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} - dependencies: - '@types/node': 14.18.21 - - /@types/content-disposition/0.5.5: - resolution: {integrity: sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==} - - /@types/cookies/0.7.7: - resolution: {integrity: sha512-h7BcvPUogWbKCzBR2lY4oqaZbO3jXZksexYJVFvkrFeLgbZjQkU4x8pRq6eg2MHXQhY0McQdqmmsxRWlVAHooA==} - dependencies: - '@types/connect': 3.4.35 - '@types/express': 4.17.13 - '@types/keygrip': 1.0.2 - '@types/node': 14.18.21 - - /@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: - 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: - resolution: {integrity: sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==} - dependencies: - '@types/body-parser': 1.19.2 - '@types/express-serve-static-core': 4.17.29 - '@types/qs': 6.9.7 - '@types/serve-static': 1.13.10 - - /@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: - resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} - requiresBuild: true - dependencies: - '@types/minimatch': 3.0.5 - '@types/node': 14.18.21 - dev: true - optional: true - - /@types/http-assert/1.5.3: - resolution: {integrity: sha512-FyAOrDuQmBi8/or3ns4rwPno7/9tJTijVW6aQQjK02+kOQ8zmoNg2XJtAuQhvQcy1ASJq38wirX5//9J1EqoUA==} - - /@types/http-errors/1.8.2: - resolution: {integrity: sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==} - - /@types/json-schema/7.0.11: - resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} - dev: true - - /@types/jsonwebtoken/8.5.8: - resolution: {integrity: sha512-zm6xBQpFDIDM6o9r6HSgDeIcLy82TKWctCXEPbJJcXb5AKmi5BNNdLXneixK4lplX3PqIVcwLBCGE/kAGnlD4A==} - dependencies: - '@types/node': 14.18.21 - dev: true - - /@types/keygrip/1.0.2: - resolution: {integrity: sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==} - - /@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: - resolution: {integrity: sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==} - dependencies: - '@types/koa': 2.13.4 - - /@types/koa-router/7.4.4: - resolution: {integrity: sha512-3dHlZ6CkhgcWeF6wafEUvyyqjWYfKmev3vy1PtOmr0mBc3wpXPU5E8fBBd4YQo5bRpHPfmwC5yDaX7s4jhIN6A==} - dependencies: - '@types/koa': 2.13.4 - dev: true - - /@types/koa/2.13.4: - resolution: {integrity: sha512-dfHYMfU+z/vKtQB7NUrthdAEiSvnLebvBjwHtfFmpZmB7em2N3WVQdHgnFq+xvyVgxW5jKDmjWfLD3lw4g4uTw==} - dependencies: - '@types/accepts': 1.3.5 - '@types/content-disposition': 0.5.5 - '@types/cookies': 0.7.7 - '@types/http-assert': 1.5.3 - '@types/http-errors': 1.8.2 - '@types/keygrip': 1.0.2 - '@types/koa-compose': 3.2.5 - '@types/node': 14.18.21 - - /@types/mime/1.3.2: - resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} - - /@types/minimatch/3.0.5: - resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} - dev: true - optional: true - - /@types/ms/0.7.31: - resolution: {integrity: sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==} - dev: true - - /@types/node/12.20.55: - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - dev: true - - /@types/node/14.18.21: - resolution: {integrity: sha512-x5W9s+8P4XteaxT/jKF0PSb7XEvo5VmqEWgsMlyeY4ZlLK8I6aH6g5TPPyDlLAep+GYf4kefb7HFyc7PAO3m+Q==} - - /@types/plist/3.0.2: - resolution: {integrity: sha512-ULqvZNGMv0zRFvqn8/4LSPtnmN4MfhlPNtJCTpKuIIxGVGZ2rYWzFXrvEBoh9CVyqSE7D6YFRJ1hydLHI6kbWw==} - requiresBuild: true - dependencies: - '@types/node': 14.18.21 - xmlbuilder: 15.1.1 - dev: true - optional: true - - /@types/qs/6.9.7: - resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} - - /@types/range-parser/1.2.4: - resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} - - /@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: - resolution: {integrity: sha512-7msTTzXOAI9zS/u4DJLMAfAT/sz1laaZTFYVB83h0L78q4yB8RzRyxcqUlfkg12/hMC6G/cG//vbDwmdTpZ+gQ==} - dev: true - - /@types/verror/1.10.5: - resolution: {integrity: sha512-9UjMCHK5GPgQRoNbqdLIAvAy0EInuiqbW0PBMtVP6B5B2HQJlvoJHM+KodPZMEjOa5VkSc+5LH7xy+cUzQdmHw==} - requiresBuild: true - dev: true - optional: true - - /@types/yargs-parser/21.0.0: - resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} - dev: true - - /@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: - 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: @@ -384,44 +1072,21 @@ packages: negotiator: 0.6.3 dev: false - /agent-base/6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - - /agentkeepalive/4.2.1: - resolution: {integrity: sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==} - engines: {node: '>= 8.0.0'} - dependencies: - debug: 4.3.4 - depd: 1.1.2 - humanize-ms: 1.2.1 - transitivePeerDependencies: - - supports-color - dev: false - optional: true - - /aggregate-error/3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - dev: false - optional: true - - /ajv-keywords/3.5.2_ajv@6.12.6: - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + /acorn-jsx@5.3.2(acorn@8.11.3): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: - ajv: ^6.9.1 + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - ajv: 6.12.6 + acorn: 8.11.3 dev: true - /ajv/6.12.6: + /acorn@8.11.3: + resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + + /ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 @@ -430,308 +1095,108 @@ packages: uri-js: 4.4.1 dev: true - /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'} + dev: true - /ansi-styles/4.3.0: + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + + /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: - resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 dev: false - /app-builder-bin/3.7.1: - resolution: {integrity: sha512-ql93vEUq6WsstGXD+SBLSIQw6SNnhbDEM0swzgugytMxLp3rT24Ag/jcC80ZHxiPRTdew1niuR7P3/FCrDqIjw==} - dev: true - - /app-builder-lib/22.14.13: - resolution: {integrity: sha512-SufmrtxU+D0Tn948fjEwAOlCN9757UXLkzzTWXMwZKR/5hisvgqeeBepWfphMIE6OkDGz0fbzEhL1P2Pty4XMg==} - engines: {node: '>=14.0.0'} - dependencies: - '@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 - builder-util-runtime: 8.9.2 - chromium-pickle-js: 0.2.0 - debug: 4.3.4 - ejs: 3.1.8 - electron-osx-sign: 0.5.0 - electron-publish: 22.14.13 - form-data: 4.0.0 - fs-extra: 10.1.0 - hosted-git-info: 4.1.0 - is-ci: 3.0.1 - isbinaryfile: 4.0.10 - js-yaml: 4.1.0 - lazy-val: 1.0.5 - minimatch: 3.1.2 - read-config-file: 6.2.0 - sanitize-filename: 1.6.3 - semver: 7.3.7 - temp-file: 3.4.0 - transitivePeerDependencies: - - supports-color - dev: true - - /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: - resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} - engines: {node: '>=10'} - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.0 - dev: false - - /are-we-there-yet/3.0.0: - resolution: {integrity: sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16} - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.0 - dev: false - optional: true - - /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: - resolution: {integrity: sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ==} - engines: {node: '>=10.12.0'} - hasBin: true - dependencies: - chromium-pickle-js: 0.2.0 - commander: 5.1.0 - glob: 7.2.3 - minimatch: 3.1.2 - optionalDependencies: - '@types/glob': 7.2.0 - dev: true - - /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: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} - requiresBuild: true - dev: true - optional: true - - /async-exit-hook/2.0.1: - resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} - engines: {node: '>=0.12.0'} dev: true - /async/3.2.4: - resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} - dev: true - - /asynckit/0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true - - /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: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - requiresBuild: true dev: true - /binary-extensions/2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + /better-sqlite3@9.4.3: + resolution: {integrity: sha512-ud0bTmD9O3uWJGuXDltyj3R47Nz0OHX8iqPOT5PMspGqlu/qQFn+5S2eFBUCrySpavTjFXbi4EgrfVvPAHlImw==} + requiresBuild: true + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.2 + + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} dev: false - /bluebird-lst/1.0.9: - resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==} + /bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} dependencies: - bluebird: 3.7.2 - dev: true + file-uri-to-path: 1.0.0 - /bluebird/3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - dev: true - - /boolean/3.2.0: - resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} - dev: true - optional: true - - /boxen/5.1.2: - resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} - engines: {node: '>=10'} + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: - ansi-align: 3.0.1 - camelcase: 6.3.0 - chalk: 4.1.2 - cli-boxes: 2.2.1 - string-width: 4.2.3 - type-fest: 0.20.2 - widest-line: 3.1.0 - wrap-ansi: 7.0.0 - dev: true + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 - /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 + dev: true - /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: - resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} - dev: true - - /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: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - - /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: - resolution: {integrity: sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==} - engines: {node: '>=0.4.0'} - dev: true - - /buffer-fill/1.0.0: - resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} - dev: true - - /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: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true - optional: true - /builder-util-runtime/8.9.2: - resolution: {integrity: sha512-rhuKm5vh7E0aAmT6i8aoSfEjxzdYEFX7zDApK+eNgOhjofnWb74d9SRJv0H/8nsgOkos0TZ4zxW0P8J4N7xQ2A==} - engines: {node: '>=12.0.0'} - dependencies: - debug: 4.3.4 - sax: 1.2.4 - transitivePeerDependencies: - - supports-color - dev: true - - /builder-util/22.14.13: - resolution: {integrity: sha512-oePC/qrrUuerhmH5iaCJzPRAKlSBylrhzuAJmRQClTyWnZUv6jbaHh+VoHMbEiE661wrj2S2aV7/bQh12cj1OA==} - dependencies: - '@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 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - fs-extra: 10.1.0 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - is-ci: 3.0.1 - js-yaml: 4.1.0 - source-map-support: 0.5.21 - stat-mode: 1.0.0 - temp-file: 3.4.0 - transitivePeerDependencies: - - 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: - resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} - engines: {node: '>= 10'} - dependencies: - '@npmcli/fs': 1.1.1 - '@npmcli/move-file': 1.1.2 - chownr: 2.0.0 - fs-minipass: 2.1.0 - glob: 7.2.3 - infer-owner: 1.0.4 - lru-cache: 6.0.0 - minipass: 3.3.3 - minipass-collect: 1.0.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - mkdirp: 1.0.4 - p-map: 4.0.0 - promise-inflight: 1.0.1 - rimraf: 3.0.2 - ssri: 8.0.1 - tar: 6.1.11 - unique-filename: 1.1.1 - 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: @@ -739,32 +1204,32 @@ packages: ylru: 1.3.2 dev: false - /cacheable-request/6.1.0: - resolution: {integrity: sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==} - engines: {node: '>=8'} + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} dependencies: - clone-response: 1.0.2 - get-stream: 5.2.0 - http-cache-semantics: 4.1.0 - keyv: 3.1.0 - lowercase-keys: 2.0.0 - normalize-url: 4.5.1 - responselike: 1.0.2 - dev: true - - /call-bind/1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.1.2 + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 dev: false - /camelcase/6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} dev: true - /chalk/4.1.2: + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} dependencies: @@ -772,11 +1237,11 @@ packages: supports-color: 7.2.0 dev: true - /chokidar/3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + /chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} dependencies: - anymatch: 3.1.2 + anymatch: 3.1.3 braces: 3.0.2 glob-parent: 5.1.2 is-binary-path: 2.1.0 @@ -784,218 +1249,76 @@ packages: normalize-path: 3.0.0 readdirp: 3.6.0 optionalDependencies: - fsevents: 2.3.2 + fsevents: 2.3.3 dev: false - /chownr/2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - dev: false + /chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - /chromium-pickle-js/0.2.0: - resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} - dev: true - - /ci-info/2.0.0: - resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} - dev: true - - /ci-info/3.3.2: - resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==} - dev: true - - /clean-stack/2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - dev: false - optional: true - - /cli-boxes/2.2.1: - resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} - engines: {node: '>=6'} - dev: true - - /cli-truncate/2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} - requiresBuild: true - dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 - dev: true - optional: true - - /cliui/7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - dev: true - - /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 - qs: 6.10.5 - raw-body: 2.5.1 + inflation: 2.1.0 + qs: 6.12.0 + raw-body: 2.5.2 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@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + + /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.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + + /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true - /color-support/1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - dev: false - - /colorette/2.0.16: - resolution: {integrity: sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==} - dev: false - - /colors/1.0.3: - resolution: {integrity: sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==} - engines: {node: '>=0.1.90'} - dev: true - - /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: - resolution: {integrity: sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==} - engines: {node: '>= 0.6.x'} - dependencies: - graceful-readlink: 1.0.1 - dev: true - - /commander/5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - dev: true - - /commander/6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - dev: true - - /commander/7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - dev: false - - /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: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 2.3.7 - typedarray: 0.0.6 dev: true - /config-chain/1.1.13: - resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} - dependencies: - ini: 1.3.8 - proto-list: 1.2.4 - dev: true - optional: true - - /configstore/5.0.1: - resolution: {integrity: sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==} - engines: {node: '>=8'} - dependencies: - dot-prop: 5.3.0 - graceful-fs: 4.2.10 - make-dir: 3.1.0 - unique-string: 2.0.0 - write-file-atomic: 3.0.3 - xdg-basedir: 4.0.0 - dev: true - - /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: - resolution: {integrity: sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==} + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} dev: false - /cookies/0.8.0: - resolution: {integrity: sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==} + /cookies@0.9.1: + resolution: {integrity: sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==} engines: {node: '>= 0.8'} dependencies: depd: 2.0.0 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: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - requiresBuild: true - dev: true - optional: true - - /core-util-is/1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: true - - /crc/3.8.0: - resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} - requiresBuild: true - dependencies: - buffer: 5.7.1 - dev: true - optional: true - - /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: @@ -1004,30 +1327,11 @@ packages: which: 2.0.2 dev: true - /crypto-random-string/2.0.0: - resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} - engines: {node: '>=8'} + /csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} dev: true - /debug/2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - dependencies: - ms: 2.0.0 - dev: true - - /debug/4.3.2: - resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - 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: @@ -1038,462 +1342,405 @@ packages: dependencies: ms: 2.1.2 - /decompress-response/3.3.0: - resolution: {integrity: sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==} - engines: {node: '>=4'} + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} dependencies: - mimic-response: 1.0.1 - dev: true + mimic-response: 3.1.0 - /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'} + + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /defer-to-connect/1.1.3: - resolution: {integrity: sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==} - dev: true - - /define-properties/1.1.4: - resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 - dev: true - optional: true + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + dev: false - /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: - resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} + /detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} - dev: false - /detect-node/2.1.0: - resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} - dev: true - optional: true - - /diff/4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + /diff@3.5.0: + resolution: {integrity: sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==} engines: {node: '>=0.3.1'} dev: true - /dir-compare/2.4.0: - resolution: {integrity: sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==} - hasBin: true - dependencies: - buffer-equal: 1.0.0 - colors: 1.0.3 - commander: 2.9.0 - minimatch: 3.0.4 - dev: true - - /dmg-builder/22.14.13: - resolution: {integrity: sha512-xNOugB6AbIRETeU2uID15sUfjdZZcKdxK8xkFnwIggsM00PJ12JxpLNPTjcRoUnfwj3WrPjilrO64vRMwNItQg==} - dependencies: - app-builder-lib: 22.14.13 - builder-util: 22.14.13 - builder-util-runtime: 8.9.2 - fs-extra: 10.1.0 - iconv-lite: 0.6.3 - js-yaml: 4.1.0 - optionalDependencies: - dmg-license: 1.0.11 - transitivePeerDependencies: - - supports-color - dev: true - - /dmg-license/1.0.11: - resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} - engines: {node: '>=8'} - os: [darwin] - deprecated: 'Disk image license agreements are deprecated by Apple and will probably be removed in a future macOS release. Discussion at: https://github.com/argv-minus-one/dmg-license/issues/11' - hasBin: true - requiresBuild: true - dependencies: - '@types/plist': 3.0.2 - '@types/verror': 1.10.5 - ajv: 6.12.6 - crc: 3.8.0 - iconv-corefoundation: 1.1.7 - plist: 3.0.5 - smart-buffer: 4.2.0 - verror: 1.10.1 - dev: true - optional: true - - /dot-prop/5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} dependencies: - is-obj: 2.0.0 + path-type: 4.0.0 dev: true - /dotenv-expand/5.1.0: - resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} - dev: true - - /dotenv/9.0.2: - resolution: {integrity: sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==} - engines: {node: '>=10'} - dev: true - - /dprint/0.36.1: - resolution: {integrity: sha512-F8TaWlieYtJc4ktqr0tjNwg/HN4wNPTU6rTq29MdoIKXi9gQmh7Ibqr0oyW1hsLpqBuAXjjwEYIos9plYkwENQ==} - hasBin: true - requiresBuild: true + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} 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==} + esutils: 2.0.3 dev: true - /ecdsa-sig-formatter/1.0.11: + /dotenv-expand@11.0.6: + resolution: {integrity: sha512-8NHi73otpWsZGBSZwwknTXS5pqMOrk9+Ssrna8xCaxkzEpU9OTf9R5ArQGVw03//Zmk9MOwLPng9WwndvpAJ5g==} + engines: {node: '>=12'} + dependencies: + dotenv: 16.4.5 + dev: true + + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: true + + /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: - resolution: {integrity: sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==} - engines: {node: '>=0.10.0'} - hasBin: true - dependencies: - jake: 10.8.5 - dev: true - - /electron-builder/22.14.13: - resolution: {integrity: sha512-3fgLxqF2TXVKiUPeg74O4V3l0l3j7ERLazo8sUbRkApw0+4iVAf2BJkHsHMaXiigsgCoEzK/F4/rB5rne/VAnw==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - '@types/yargs': 17.0.10 - app-builder-lib: 22.14.13 - builder-util: 22.14.13 - builder-util-runtime: 8.9.2 - chalk: 4.1.2 - dmg-builder: 22.14.13 - fs-extra: 10.1.0 - is-ci: 3.0.1 - lazy-val: 1.0.5 - read-config-file: 6.2.0 - update-notifier: 5.1.0 - yargs: 17.5.1 - transitivePeerDependencies: - - supports-color - dev: true - - /electron-osx-sign/0.5.0: - resolution: {integrity: sha512-icoRLHzFz/qxzDh/N4Pi2z4yVHurlsCAYQvsCSG7fCedJ4UJXBS6PoQyGH71IfcqKupcKeK7HX/NkyfG+v6vlQ==} - engines: {node: '>=4.0.0'} - hasBin: true - dependencies: - bluebird: 3.7.2 - compare-version: 0.1.2 - debug: 2.6.9 - isbinaryfile: 3.0.3 - minimist: 1.2.6 - plist: 3.0.5 - dev: true - - /electron-publish/22.14.13: - resolution: {integrity: sha512-0oP3QiNj3e8ewOaEpEJV/o6Zrmy2VarVvZ/bH7kyO/S/aJf9x8vQsKVWpsdmSiZ5DJEHgarFIXrnO0ZQf0P9iQ==} - dependencies: - '@types/fs-extra': 9.0.13 - builder-util: 22.14.13 - builder-util-runtime: 8.9.2 - chalk: 4.1.2 - fs-extra: 10.1.0 - lazy-val: 1.0.5 - mime: 2.6.0 - transitivePeerDependencies: - - supports-color - dev: true - - /electron/11.5.0: - resolution: {integrity: sha512-WjNDd6lGpxyiNjE3LhnFCAk/D9GIj1rU3GSDealVShhkkkPR3Vh4q8ErXGDl1OAO/faomVa10KoFPUN/pLbNxg==} - engines: {node: '>= 8.6'} - hasBin: true - requiresBuild: true - dependencies: - '@electron/get': 1.14.1 - '@types/node': 12.20.55 - extract-zip: 1.7.0 - transitivePeerDependencies: - - supports-color - dev: true - - /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: - resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} - requiresBuild: true - dependencies: - iconv-lite: 0.6.3 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: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - - /err-code/2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.4 dev: false - optional: true - /es6-error/4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - dev: true - optional: true + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + dev: false - /escalade/3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - - /escape-goat/2.1.1: - resolution: {integrity: sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==} - engines: {node: '>=8'} + /esbuild@0.20.2: + resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.20.2 + '@esbuild/android-arm': 0.20.2 + '@esbuild/android-arm64': 0.20.2 + '@esbuild/android-x64': 0.20.2 + '@esbuild/darwin-arm64': 0.20.2 + '@esbuild/darwin-x64': 0.20.2 + '@esbuild/freebsd-arm64': 0.20.2 + '@esbuild/freebsd-x64': 0.20.2 + '@esbuild/linux-arm': 0.20.2 + '@esbuild/linux-arm64': 0.20.2 + '@esbuild/linux-ia32': 0.20.2 + '@esbuild/linux-loong64': 0.20.2 + '@esbuild/linux-mips64el': 0.20.2 + '@esbuild/linux-ppc64': 0.20.2 + '@esbuild/linux-riscv64': 0.20.2 + '@esbuild/linux-s390x': 0.20.2 + '@esbuild/linux-x64': 0.20.2 + '@esbuild/netbsd-x64': 0.20.2 + '@esbuild/openbsd-x64': 0.20.2 + '@esbuild/sunos-x64': 0.20.2 + '@esbuild/win32-arm64': 0.20.2 + '@esbuild/win32-ia32': 0.20.2 + '@esbuild/win32-x64': 0.20.2 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@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} dev: true - optional: true - /esm/3.2.25: - resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} - engines: {node: '>=6'} - dev: false + /eslint-plugin-react-hooks@4.6.0(eslint@8.57.0): + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.57.0 + dev: true - /extract-zip/1.7.0: - resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==} + /eslint-plugin-react-refresh@0.4.6(eslint@8.57.0): + resolution: {integrity: sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==} + peerDependencies: + eslint: '>=7' + dependencies: + eslint: 8.57.0 + dev: true + + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: true + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.57.0: + resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - concat-stream: 1.6.2 - debug: 2.6.9 - mkdirp: 0.5.6 - yauzl: 2.10.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@eslint-community/regexpp': 4.10.0 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.0 + '@humanwhocodes/config-array': 0.11.14 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.1 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color dev: true - /extsprintf/1.4.1: - resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} - engines: {'0': node >=0.6.0} - requiresBuild: true + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.11.3 + acorn-jsx: 5.3.2(acorn@8.11.3) + eslint-visitor-keys: 3.4.3 dev: true - optional: true - /fast-deep-equal/3.1.3: + /esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: true + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: true + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true + + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + /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-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.5 + dev: true + + /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} dev: true - /fd-slicer/1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - dependencies: - pend: 1.2.0 - - /filelist/1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - dependencies: - minimatch: 5.1.0 + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true - /fill-range/7.0.1: + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + dependencies: + reusify: 1.0.4 + dev: true + + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flat-cache: 3.2.0 + dev: true + + /file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + /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: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 + locate-path: 6.0.0 + path-exists: 4.0.0 dev: true - /fresh/0.5.2: + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + dev: true + + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + dev: true + + /fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} dev: false - /fs-extra/10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - dependencies: - graceful-fs: 4.2.10 - jsonfile: 6.1.0 - universalify: 2.0.0 - dev: true + /fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - /fs-extra/8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} - dependencies: - graceful-fs: 4.2.10 - jsonfile: 4.0.0 - universalify: 0.1.2 - dev: true - - /fs-extra/9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.10 - jsonfile: 6.1.0 - universalify: 2.0.0 - dev: true - - /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==} + dev: true - /fsevents/2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: false optional: true - /function-bind/1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - /gauge/3.0.2: - resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} - engines: {node: '>=10'} + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - dev: false - - /gauge/4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - dev: false - optional: true - - /get-caller-file/2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - dev: true - - /get-intrinsic/1.1.2: - resolution: {integrity: sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==} - dependencies: - function-bind: 1.1.1 - has: 1.0.3 + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 has-symbols: 1.0.3 - - /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: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - dependencies: - pump: 3.0.0 - dev: true - - /getopts/2.2.5: - resolution: {integrity: sha512-9jb7AW5p3in+IiJWhQiZmmwkpLaR/ccTWdWQCtZM66HJcHHLegowh4q4tSD7gouUyeNvFWRavfK9GXosQHDpFA==} + hasown: 2.0.2 dev: false - /glob-parent/5.1.2: + /git-diff@2.0.6: + resolution: {integrity: sha512-/Iu4prUrydE3Pb3lCBMbcSNIf81tgGt0W1ZwknnyF62t3tHmtiJTRj0f+1ZIhp3+Rh0ktz1pJVoa7ZXUCskivA==} + engines: {node: '>= 4.8.0'} + dependencies: + chalk: 2.4.2 + diff: 3.5.0 + loglevel: 1.9.1 + shelljs: 0.8.5 + shelljs.exec: 1.1.8 + dev: true + + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + /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-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: true + + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: fs.realpath: 1.0.0 @@ -1502,118 +1749,77 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - - /global-agent/3.0.0: - resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} - engines: {node: '>=10.0'} - requiresBuild: true - dependencies: - boolean: 3.2.0 - es6-error: 4.1.1 - matcher: 3.0.0 - roarr: 2.15.4 - semver: 7.3.7 - serialize-error: 7.0.1 dev: true - optional: true - /global-dirs/3.0.0: - resolution: {integrity: sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==} + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} dependencies: - ini: 2.0.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 3.0.0 dev: true - /global-tunnel-ng/2.7.1: - resolution: {integrity: sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==} - engines: {node: '>=0.10'} - requiresBuild: true + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: - encodeurl: 1.0.2 - lodash: 4.17.21 - npm-conf: 1.1.3 - tunnel: 0.0.6 - dev: true - optional: true + get-intrinsic: 1.2.4 + dev: false - /globalthis/1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.1.4 - dev: true - optional: true - - /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 - cacheable-request: 6.1.0 - decompress-response: 3.3.0 - duplexer3: 0.1.4 - get-stream: 4.1.0 - lowercase-keys: 1.0.1 - mimic-response: 1.0.1 - p-cancelable: 1.1.0 - to-readable-stream: 1.0.0 - url-parse-lax: 3.0.0 + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /graceful-fs/4.2.10: - resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} - - /graceful-readlink/1.0.1: - resolution: {integrity: sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==} + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} 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: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: - get-intrinsic: 1.1.2 - dev: true - optional: true + es-define-property: 1.0.0 + dev: false - /has-symbols/1.0.3: + /has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + dev: false + + /has-symbols@1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} + dev: false - /has-tostringtag/1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} + /has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} dependencies: has-symbols: 1.0.3 dev: false - /has-unicode/2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - dev: false - - /has-yarn/2.1.0: - resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} - engines: {node: '>=8'} - dev: true - - /has/1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} dependencies: - function-bind: 1.1.1 + function-bind: 1.1.2 - /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: @@ -1621,10 +1827,7 @@ packages: http-errors: 1.8.1 dev: false - /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: @@ -1635,7 +1838,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: @@ -1646,305 +1849,124 @@ packages: toidentifier: 1.0.1 dev: false - /http-proxy-agent/4.0.1: - resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} - engines: {node: '>= 6'} - dependencies: - '@tootallnate/once': 1.1.2 - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: false - optional: true - - /http-proxy-agent/5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - - /https-proxy-agent/5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - - /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: - resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} - engines: {node: ^8.11.2 || >=10} - os: [darwin] - requiresBuild: true - dependencies: - cli-truncate: 2.1.0 - node-addon-api: 1.7.2 - 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: - 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: - resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} - engines: {node: '>=4'} + /ignore@5.3.1: + resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + engines: {node: '>= 4'} dev: true - /imurmurhash/0.1.4: + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: true + + /imurmurhash@0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + dev: true - /indent-string/4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - dev: false - optional: true - - /infer-owner/1.0.4: - resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} - dev: false - optional: true - - /inflation/2.0.0: - resolution: {integrity: sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==} + /inflation@2.1.0: + resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==} 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 + dev: true - /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: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} - dev: true - - /interpret/2.2.0: - resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} + /interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} - dev: false + dev: true - /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 + binary-extensions: 2.3.0 dev: false - /is-ci/2.0.0: - resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} - hasBin: true + /is-core-module@2.13.1: + resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: - ci-info: 2.0.0 + hasown: 2.0.2 dev: true - /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: - 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: - 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 + has-tostringtag: 1.0.2 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: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} - dependencies: - global-dirs: 3.0.0 - is-path-inside: 3.0.3 - dev: true - - /is-lambda/1.0.1: - resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} - dev: false - optional: true - - /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: - 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: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - dev: true - - /is-yarn-global/0.3.0: - resolution: {integrity: sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==} - dev: true - - /isarray/1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: true - - /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: - 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: - resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} - engines: {node: '>=10'} - hasBin: true - dependencies: - async: 3.2.4 - chalk: 4.1.2 - filelist: 1.0.4 - minimatch: 3.1.2 dev: true - /js-yaml/4.1.0: + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true dependencies: argparse: 2.0.1 dev: true - /json-buffer/3.0.0: - resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} 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: - resolution: {integrity: sha512-i/J297TW6xyj7sDFa7AmBPkQvLIxWr2kKPWI26tXydnZrzVAocNqn5DMNT1Mzk0vit1V5UkRM7C1KdVNp7Lmcg==} - dependencies: - jsonify: 0.0.0 + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true - /json-stringify-safe/5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - dev: true - optional: true - - /json5/2.2.1: - resolution: {integrity: sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==} - engines: {node: '>=6'} - hasBin: true - dev: true - - /jsonfile/4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - optionalDependencies: - graceful-fs: 4.2.10 - dev: true - - /jsonfile/6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.0 - optionalDependencies: - graceful-fs: 4.2.10 - dev: true - - /jsonify/0.0.0: - resolution: {integrity: sha512-trvBk1ki43VZptdBI5rIlG4YOzyeH/WefQt5rj1grasPn4iiZWKet8nkgc4GlsAylaztn0qZfUYOiTsASJFdNA==} - dev: true - - /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: @@ -1957,10 +1979,10 @@ packages: lodash.isstring: 4.0.1 lodash.once: 4.1.1 ms: 2.1.3 - semver: 5.7.1 + semver: 5.7.2 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 @@ -1968,82 +1990,40 @@ 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: - resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: - json-buffer: 3.0.0 + json-buffer: 3.0.1 dev: true - /knex/0.95.15_sqlite3@5.0.8: - resolution: {integrity: sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w==} - engines: {node: '>=10'} - hasBin: true - peerDependencies: - mysql: '*' - mysql2: '*' - pg: '*' - pg-native: '*' - sqlite3: '*' - tedious: '*' - peerDependenciesMeta: - mysql: - optional: true - mysql2: - optional: true - pg: - optional: true - pg-native: - optional: true - sqlite3: - optional: true - tedious: - optional: true - dependencies: - colorette: 2.0.16 - commander: 7.2.0 - debug: 4.3.2 - escalade: 3.1.1 - esm: 3.2.25 - getopts: 2.2.5 - interpret: 2.2.0 - lodash: 4.17.21 - pg-connection-string: 2.5.0 - rechoir: 0.7.0 - resolve-from: 5.0.0 - sqlite3: 5.0.8 - tarn: 3.0.2 - tildify: 2.0.0 - transitivePeerDependencies: - - supports-color - dev: false - - /koa-bodyparser/4.3.0: - resolution: {integrity: sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw==} + /koa-bodyparser@4.4.1: + resolution: {integrity: sha512-kBH3IYPMb+iAXnrxIhXnW+gXV8OTzCu8VPDqvcDHW9SQrbkHmqPQtiZwrltNmSq6/lpipHnT7k7PsjlVD7kK0w==} engines: {node: '>=8.0.0'} dependencies: co-body: 6.1.0 copy-to: 2.0.1 + type-is: 1.6.18 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: @@ -2051,12 +2031,12 @@ packages: koa-compose: 4.1.0 dev: false - /koa-router/10.1.1: - resolution: {integrity: sha512-z/OzxVjf5NyuNO3t9nJpx7e1oR3FSBAauiwXtMQu4ppcnuNZzTaQ4p21P8A6r2Es8uJJM339oc4oVW+qX7SqnQ==} - engines: {node: '>= 8.0.0'} + /koa-router@12.0.1: + resolution: {integrity: sha512-gaDdj3GtzoLoeosacd50kBBTnnh3B9AYxDThQUo4sfUyXdOhY6ku1qyZKW88tQCRgc3Sw6ChXYXWZwwgjOxE0w==} + engines: {node: '>= 12'} dependencies: debug: 4.3.4 - http-errors: 1.8.1 + http-errors: 2.0.0 koa-compose: 4.1.0 methods: 1.1.2 path-to-regexp: 6.2.1 @@ -2064,15 +2044,15 @@ packages: - supports-color dev: false - /koa/2.13.4: - resolution: {integrity: sha512-43zkIKubNbnrULWlHdN5h1g3SEKXOEzoAlRsHOTFpnlDu8JlAOZSMJBLULusuXRequboiwJcj5vtYXKB3k7+2g==} + /koa@2.15.2: + resolution: {integrity: sha512-MXTeZH3M6AJ8ukW2QZ8wqO3Dcdfh2WRRmjCBkEP+NhKNCiqlO5RDqHmSnsyNrbRJrdjyvIGSJho4vQiWgQJSVA==} engines: {node: ^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4} dependencies: accepts: 1.3.8 cache-content-type: 1.0.1 content-disposition: 0.5.4 - content-type: 1.0.4 - cookies: 0.8.0 + content-type: 1.0.5 + cookies: 0.9.1 debug: 4.3.4 delegates: 1.0.0 depd: 2.0.0 @@ -2095,515 +2075,364 @@ packages: - supports-color dev: false - /latest-version/5.1.0: - resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} - engines: {node: '>=8'} + /kysely-codegen@0.14.1(better-sqlite3@9.4.3)(kysely@0.27.3): + resolution: {integrity: sha512-W6ULVWYnlBcGalHlKqw3ls0QjPl6n6z1xJ9ixPCh1lxDSbe98s4gjEbKC3hh8ZTCUTZSLSasi+QCii4AL/j7pw==} + hasBin: true + peerDependencies: + '@libsql/kysely-libsql': ^0.3.0 + '@tediousjs/connection-string': ^0.5.0 + better-sqlite3: '>=7.6.2' + kysely: ^0.27.0 + kysely-bun-worker: ^0.5.3 + mysql2: ^2.3.3 || ^3.0.0 + pg: ^8.8.0 + tarn: ^3.0.0 + tedious: ^16.6.0 || ^17.0.0 + peerDependenciesMeta: + '@libsql/kysely-libsql': + optional: true + '@tediousjs/connection-string': + optional: true + better-sqlite3: + optional: true + kysely-bun-worker: + optional: true + mysql2: + optional: true + pg: + optional: true + tarn: + optional: true + tedious: + optional: true dependencies: - package-json: 6.5.0 + better-sqlite3: 9.4.3 + chalk: 4.1.2 + dotenv: 16.4.5 + dotenv-expand: 11.0.6 + git-diff: 2.0.6 + kysely: 0.27.3 + micromatch: 4.0.5 + minimist: 1.2.8 dev: true - /lazy-val/1.0.5: - resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + /kysely@0.27.3: + resolution: {integrity: sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==} + engines: {node: '>=14.0.0'} + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 dev: true - /lodash.includes/4.3.0: + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: true + + /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.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true + + /lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} dev: false - /lodash/4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - /lowercase-keys/1.0.1: - resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} - engines: {node: '>=0.10.0'} + /loglevel@1.9.1: + resolution: {integrity: sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==} + engines: {node: '>= 0.6.0'} dev: true - /lowercase-keys/2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - dev: true + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false - /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: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.0 - - /make-error/1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true - - /make-fetch-happen/9.1.0: - resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==} - engines: {node: '>= 10'} - dependencies: - agentkeepalive: 4.2.1 - cacache: 15.3.0 - http-cache-semantics: 4.1.0 - http-proxy-agent: 4.0.1 - https-proxy-agent: 5.0.1 - is-lambda: 1.0.1 - lru-cache: 6.0.0 - minipass: 3.3.3 - minipass-collect: 1.0.2 - minipass-fetch: 1.4.1 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - negotiator: 0.6.3 - promise-retry: 2.0.1 - socks-proxy-agent: 6.2.1 - ssri: 8.0.1 - transitivePeerDependencies: - - supports-color - dev: false - optional: true - - /matcher/3.0.0: - resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} - engines: {node: '>=10'} - dependencies: - escape-string-regexp: 4.0.0 - 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: + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: true + + /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} dev: false - /mime-db/1.52.0: + /micromatch@4.0.5: + resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.2 + picomatch: 2.3.1 + dev: true + + /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + dev: false - /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 + dev: false - /mime/2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} - engines: {node: '>=4.0.0'} - hasBin: true - dev: true + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} - /mimic-response/1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - dev: true - - /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 + dev: true - /minimatch/5.1.0: - resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==} - engines: {node: '>=10'} + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 dev: true - /minimist/1.2.6: - resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} - dev: true + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - /minipass-collect/1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} - dependencies: - minipass: 3.3.3 - dev: false - optional: true + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - /minipass-fetch/1.4.1: - resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==} - engines: {node: '>=8'} - dependencies: - minipass: 3.3.3 - minipass-sized: 1.0.3 - minizlib: 2.1.2 - optionalDependencies: - encoding: 0.1.13 - dev: false - optional: true - - /minipass-flush/1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - dependencies: - minipass: 3.3.3 - dev: false - optional: true - - /minipass-pipeline/1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} - dependencies: - minipass: 3.3.3 - dev: false - optional: true - - /minipass-sized/1.0.3: - resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} - engines: {node: '>=8'} - dependencies: - minipass: 3.3.3 - dev: false - optional: true - - /minipass/3.3.3: - resolution: {integrity: sha512-N0BOsdFAlNRfmwMhjAsLVWOk7Ljmeb39iqFlsV1At+jqRhSUP9yeof8FyJu4imaJiSUp8vQebWD/guZwGQC8iA==} - engines: {node: '>=8'} - dependencies: - yallist: 4.0.0 - dev: false - - /minizlib/2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - dependencies: - minipass: 3.3.3 - yallist: 4.0.0 - dev: false - - /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: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: false - - /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: + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true + + /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: - resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} - requiresBuild: true - dev: true - optional: true - - /node-addon-api/3.2.1: - resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} - dev: false - - /node-addon-api/4.3.0: - resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} - dev: false - - /node-fetch/2.6.7: - resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true + /node-abi@3.56.0: + resolution: {integrity: sha512-fZjdhDOeRcaS+rcpve7XuwHBmktS1nS1gzgghwKUQQ8nTy2FdSDr6ZT8k6YhvlJeHmmQMYiT/IH9hfco5zeW2Q==} + engines: {node: '>=10'} dependencies: - whatwg-url: 5.0.0 - dev: false + semver: 7.6.0 - /node-gyp/8.4.1: - resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==} - engines: {node: '>= 10.12.0'} - hasBin: true - requiresBuild: true - dependencies: - env-paths: 2.2.1 - glob: 7.2.3 - graceful-fs: 4.2.10 - make-fetch-happen: 9.1.0 - nopt: 5.0.0 - npmlog: 6.0.2 - rimraf: 3.0.2 - semver: 7.3.7 - tar: 6.1.11 - which: 2.0.2 - transitivePeerDependencies: - - supports-color - dev: false - optional: true - - /node-stream-zip/1.15.0: - resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} - engines: {node: '>=0.12.0'} - dev: false - - /nopt/5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} - hasBin: true - dependencies: - 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: - resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} - engines: {node: '>=8'} - dev: true - - /npm-conf/1.1.3: - resolution: {integrity: sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==} - engines: {node: '>=4'} - dependencies: - config-chain: 1.1.13 - pify: 3.0.0 - dev: true - optional: true - - /npmlog/5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 + /object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} dev: false - /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: - are-we-there-yet: 3.0.0 - console-control-strings: 1.1.0 - gauge: 4.0.4 - set-blocking: 2.0.0 - dev: false - optional: true - - /object-assign/4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: false - - /object-inspect/1.12.2: - resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} - dev: false - - /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: - resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} - engines: {node: '>=6'} + /optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 dev: true - /p-map/4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} dependencies: - aggregate-error: 3.1.0 - dev: false - optional: true - - /package-json/6.5.0: - resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} - engines: {node: '>=8'} - dependencies: - got: 9.6.0 - registry-auth-token: 4.2.2 - registry-url: 5.1.0 - semver: 6.3.0 + yocto-queue: 0.1.0 dev: true - /parseurl/1.3.3: + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: true + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: true + + /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} dev: false - /path-is-absolute/1.0.1: + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true + + /path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + dev: true - /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 + dev: true - /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: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true - /pg-connection-string/2.5.0: - resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==} - dev: false + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true - /picomatch/2.3.1: + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: false - /pify/3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} - dev: true - optional: true - - /plist/3.0.5: - resolution: {integrity: sha512-83vX4eYdQp3vP9SxuYgEM/G/pJQqLUz/V/xzPrzruLs7fz7jxGQ1msZ/mg1nwZxUSuOp4sb+/bEIbRrbzZRxDA==} - engines: {node: '>=6'} + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} + engines: {node: ^10 || ^12 || >=14} dependencies: - base64-js: 1.5.1 - xmlbuilder: 9.0.7 + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.2.0 dev: true - /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: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: true - - /progress/2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - dev: true - - /promise-inflight/1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - dev: false - optional: true - - /promise-retry/2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + /prebuild-install@7.1.2: + resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} engines: {node: '>=10'} + hasBin: true dependencies: - err-code: 2.0.3 - retry: 0.12.0 - dev: false - optional: true + detect-libc: 2.0.3 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.56.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 - /proto-list/1.2.4: - resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} 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: - resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - requiresBuild: true dev: true - /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: - resolution: {integrity: sha512-O5RlPh0VFtR78y79rgcgKK4wbAI0C5zGVLztOIdpWX6ep368q5Hv6XRxDvXuZ9q3C6v+e3n8UfZZJw7IIG27eQ==} + /qs@6.12.0: + resolution: {integrity: sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==} engines: {node: '>=0.6'} dependencies: - side-channel: 1.0.4 + side-channel: 1.0.6 dev: false - /raw-body/2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true + + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} dependencies: bytes: 3.1.2 @@ -2612,487 +2441,328 @@ packages: unpipe: 1.0.0 dev: false - /rc/1.2.8: + /rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true dependencies: deep-extend: 0.6.0 ini: 1.3.8 - minimist: 1.2.6 + minimist: 1.2.8 strip-json-comments: 2.0.1 - dev: true - /read-config-file/6.2.0: - resolution: {integrity: sha512-gx7Pgr5I56JtYz+WuqEbQHj/xWo+5Vwua2jhb1VwM4Wid5PqYmZ4i00ZB0YEGIfkVBsCv9UrjgyqCiQfS/Oosg==} - engines: {node: '>=12.0.0'} + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 dependencies: - dotenv: 9.0.2 - dotenv-expand: 5.1.0 - js-yaml: 4.1.0 - json5: 2.2.1 - lazy-val: 1.0.5 - dev: true + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false - /readable-stream/2.3.7: - resolution: {integrity: sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==} + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - dev: true + loose-envify: 1.4.0 + dev: false - /readable-stream/3.6.0: - resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} dependencies: inherits: 2.0.4 string_decoder: 1.3.0 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: - resolution: {integrity: sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==} + /rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} dependencies: - resolve: 1.22.1 - dev: false - - /registry-auth-token/4.2.2: - resolution: {integrity: sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==} - engines: {node: '>=6.0.0'} - dependencies: - rc: 1.2.8 + resolve: 1.22.8 dev: true - /registry-url/5.1.0: - resolution: {integrity: sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==} - engines: {node: '>=8'} - dependencies: - rc: 1.2.8 + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} dev: true - /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: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - dev: false - - /resolve/1.22.1: - resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: - is-core-module: 2.9.0 + is-core-module: 2.13.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: false - - /responselike/1.0.2: - resolution: {integrity: sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==} - dependencies: - lowercase-keys: 1.0.1 dev: true - /retry/0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - dev: false - optional: true + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true - /rimraf/3.0.2: + /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} hasBin: true dependencies: glob: 7.2.3 + dev: true - /roarr/2.15.4: - resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} - engines: {node: '>=8.0'} + /rollup@4.13.0: + resolution: {integrity: sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true dependencies: - boolean: 3.2.0 - detect-node: 2.1.0 - globalthis: 1.0.3 - json-stringify-safe: 5.0.1 - semver-compare: 1.0.0 - sprintf-js: 1.1.2 - dev: true - optional: true - - /safe-buffer/5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + '@types/estree': 1.0.5 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.13.0 + '@rollup/rollup-android-arm64': 4.13.0 + '@rollup/rollup-darwin-arm64': 4.13.0 + '@rollup/rollup-darwin-x64': 4.13.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.13.0 + '@rollup/rollup-linux-arm64-gnu': 4.13.0 + '@rollup/rollup-linux-arm64-musl': 4.13.0 + '@rollup/rollup-linux-riscv64-gnu': 4.13.0 + '@rollup/rollup-linux-x64-gnu': 4.13.0 + '@rollup/rollup-linux-x64-musl': 4.13.0 + '@rollup/rollup-win32-arm64-msvc': 4.13.0 + '@rollup/rollup-win32-ia32-msvc': 4.13.0 + '@rollup/rollup-win32-x64-msvc': 4.13.0 + fsevents: 2.3.3 dev: true - /safe-buffer/5.2.1: + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: true + + /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==} + dev: false - /sanitize-filename/1.6.3: - resolution: {integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==} + /scheduler@0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: - truncate-utf8-bytes: 1.0.2 - dev: true + loose-envify: 1.4.0 + dev: false - /sax/1.2.4: - resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} - dev: true - - /semver-compare/1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - dev: true - optional: true - - /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: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + /semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true dev: false - /semver/6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - - /semver/7.3.7: - resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} + /semver@7.6.0: + resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} engines: {node: '>=10'} hasBin: true dependencies: lru-cache: 6.0.0 - /serialize-error/7.0.1: - resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} - engines: {node: '>=10'} + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} dependencies: - type-fest: 0.13.1 - dev: true - optional: true - - /set-blocking/2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 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: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + /shelljs.exec@1.1.8: + resolution: {integrity: sha512-vFILCw+lzUtiwBAHV8/Ex8JsFjelFMdhONIsgKNLgTzeRckp2AOYRQtHJE/9LhNvdMmE27AGtzWx0+DHpwIwSw==} + engines: {node: '>= 4.0.0'} + dev: true + + /shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.1.2 - object-inspect: 1.12.2 + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + dev: true + + /side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.1 dev: false - /signal-exit/3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} - /slice-ansi/3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - requiresBuild: true - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - optional: true - - /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: - resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==} - engines: {node: '>= 10'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 - socks: 2.6.2 - transitivePeerDependencies: - - supports-color - dev: false - optional: true - - /socks/2.6.2: - resolution: {integrity: sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==} - engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} - dependencies: - ip: 1.1.8 - smart-buffer: 4.2.0 - dev: false - optional: true - - /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: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} dev: true - /sprintf-js/1.1.2: - resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==} - dev: true - optional: true - - /sqlite3/5.0.8: - resolution: {integrity: sha512-f2ACsbSyb2D1qFFcqIXPfFscLtPVOWJr5GmUzYxf4W+0qelu5MWrR+FAQE1d5IUArEltBrzSDxDORG8P/IkqyQ==} - requiresBuild: true - peerDependenciesMeta: - node-gyp: - optional: true - dependencies: - '@mapbox/node-pre-gyp': 1.0.9 - node-addon-api: 4.3.0 - tar: 6.1.11 - optionalDependencies: - node-gyp: 8.4.1 - transitivePeerDependencies: - - encoding - - supports-color - dev: false - - /ssri/8.0.1: - resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==} - engines: {node: '>= 8'} - dependencies: - minipass: 3.3.3 - dev: false - optional: true - - /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: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.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 + dev: true - /strip-json-comments/2.0.1: + /strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} dev: true - /sumchecker/3.0.1: - resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} - engines: {node: '>= 8.0'} + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color + has-flag: 3.0.0 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: - resolution: {integrity: sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==} - engines: {node: '>= 10'} - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 3.3.3 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - dev: false - - /tarn/3.0.2: - resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} - engines: {node: '>=8.0.0'} - dev: false - - /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: - resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==} - engines: {node: '>=8'} - dev: false + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 - /tiny-async-pool/1.3.0: + /tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true + + /tiny-async-pool@1.3.0: resolution: {integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==} dependencies: - semver: 5.7.1 + semver: 5.7.2 dev: false - /tmp-promise/3.0.3: - resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} - dependencies: - tmp: 0.2.1 - dev: true - - /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: - 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: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - dev: false - - /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: - resolution: {integrity: sha512-g5/h3jmN7DqYTOx7OhTvWS638CJWzyoqlsKwe2eksvK8mnFHTnOZGvZITmPkRDPFUFwSUMo0J+Vm3A+Y8EDsEg==} - engines: {node: '>=10.0.0'} - hasBin: true - dependencies: - '@types/json-schema': 7.0.11 - commander: 6.2.1 - fast-json-stable-stringify: 2.1.0 - glob: 7.2.3 - json-stable-stringify: 1.0.1 - typescript: 4.1.6 - dev: true - - /ts-node/9.1.1_typescript@4.7.4: - resolution: {integrity: sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg==} - engines: {node: '>=10.0.0'} - hasBin: true + /ts-api-utils@1.3.0(typescript@5.4.3): + resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + engines: {node: '>=16'} peerDependencies: - typescript: '>=2.7' + typescript: '>=4.2.0' dependencies: - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - source-map-support: 0.5.21 - typescript: 4.7.4 - yn: 3.1.1 + typescript: 5.4.3 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: - resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} - engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} - dev: true - optional: true + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 - /type-fest/0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 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: @@ -3100,229 +2770,86 @@ packages: mime-types: 2.1.35 dev: false - /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: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - dev: true - - /typescript/4.1.6: - resolution: {integrity: sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow==} - engines: {node: '>=4.2.0'} + /typescript@5.4.3: + resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} + engines: {node: '>=14.17'} hasBin: true dev: true - /typescript/4.7.4: - resolution: {integrity: sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true - - /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: - resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==} - dependencies: - imurmurhash: 0.1.4 - dev: false - optional: true - - /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: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - dev: true - - /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: - resolution: {integrity: sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==} - engines: {node: '>=10'} - dependencies: - boxen: 5.1.2 - chalk: 4.1.2 - configstore: 5.0.1 - has-yarn: 2.1.0 - import-lazy: 2.1.0 - is-ci: 2.0.0 - is-installed-globally: 0.4.0 - is-npm: 5.0.0 - is-yarn-global: 0.3.0 - latest-version: 5.1.0 - pupa: 2.1.1 - semver: 7.3.7 - semver-diff: 3.1.1 - 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 + punycode: 2.3.1 dev: true - /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: - 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: - resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} - engines: {node: '>=0.6.0'} - requiresBuild: true + /vite@5.2.6: + resolution: {integrity: sha512-FPtnxFlSIKYjZ2eosBQamz4CbyrTizbZ3hnGJlh/wMtCrlp1Hah6AzBLjGI5I2urTfNnpovpHdrL6YRuBOPnCA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.4.1 + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 dev: true - optional: true - /webidl-conversions/3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - dev: false - - /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: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - dependencies: - string-width: 4.2.3 - dev: false - - /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: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - 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: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} - dependencies: - imurmurhash: 0.1.4 - is-typedarray: 1.0.0 - signal-exit: 3.0.7 - typedarray-to-buffer: 3.1.5 - dev: true - - /xdg-basedir/4.0.0: - resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} - engines: {node: '>=8'} - dev: true - - /xmlbuilder/15.1.1: - resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} - engines: {node: '>=8.0'} - requiresBuild: true - dev: true - optional: true - - /xmlbuilder/9.0.7: - resolution: {integrity: sha512-7YXTQc3P2l9+0rjaUbLwMKRhtmwg1M1eDf6nag7urC7pIPYLD9W/jmzQ4ptRSUbodw5S0jfoGTflLemQibSpeQ==} - engines: {node: '>=4.0'} - requiresBuild: true - dev: true - - /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: - resolution: {integrity: sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==} - engines: {node: '>=12'} - dev: true - - /yargs/17.5.1: - resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} - engines: {node: '>=12'} - dependencies: - cliui: 7.0.4 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.0.1 - dev: true - - /yauzl/2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - - /ylru/1.3.2: + /ylru@1.3.2: resolution: {integrity: sha512-RXRJzMiK6U2ye0BlGGZnmpwJDPgakn6aNQ0A7gHRbD4I0uvK4TW6UqkK1V0pp9jskjJBAXd3dRrbzWkqJ+6cxA==} engines: {node: '>= 4.0.0'} dev: false - /yn/3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} dev: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..4340350 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' \ No newline at end of file diff --git a/preload.ts b/preload.ts deleted file mode 100644 index 05b82f8..0000000 --- a/preload.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { contextBridge, ipcRenderer } from "electron"; - -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 deleted file mode 100644 index 0ff349b..0000000 --- a/src/SettingConfig.schema.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "$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 - }, - "Permission": { - "type": "string", - "enum": [ - "ModifyTag", - "QueryContent", - "ModifyTagDesc" - ] - } - } -} diff --git a/src/SettingConfig.ts b/src/SettingConfig.ts deleted file mode 100644 index 0f8b080..0000000 --- a/src/SettingConfig.ts +++ /dev/null @@ -1,79 +0,0 @@ -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; - /** - * secure only - */ - secure: boolean; - - /** - * guest permission - */ - guest: (Permission)[]; - /** - * JWT secret key. if you change its value, all access tokens are invalidated. - */ - jwt_secretkey: string; - /** - * the port which running server is binding on. - */ - port: number; - - 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; -} -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; - -const setEmptyToDefault = (target: any, default_table: SettingConfig) => { - let diff_occur = false; - 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)); - } - return ret as SettingConfig; -}; -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")) { - throw new Error("process unknown value in NODE_ENV: must be either \"development\" or \"production\""); - } - setting.mode = env ?? setting.mode; - } - return setting; -} diff --git a/src/client/accessor/document.ts b/src/client/accessor/document.ts deleted file mode 100644 index 21046b0..0000000 --- a/src/client/accessor/document.ts +++ /dev/null @@ -1,99 +0,0 @@ -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 ClientDocumentAccessor implements DocumentAccessor { - search: (search_word: string) => Promise; - addList: (content_list: DocumentBody[]) => Promise; - async findByPath(basepath: string, filename?: string): Promise { - throw new Error("not allowed"); - } - async findDeleted(content_type: string): Promise { - throw new Error("not allowed"); - } - 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"); - let ret = await res.json(); - return ret; - } - async findById(id: number, tagload?: boolean | undefined): Promise { - let res = await fetch(`${baseurl}/${id}`); - if (res.status !== 200) throw new FetchFailError("findById Failed"); - let ret = await res.json(); - return ret; - } - /** - * not implement - */ - 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}`, { - method: "POST", - body: JSON.stringify(rest), - headers: { - "content-type": "application/json", - }, - }); - const ret = await res.json(); - return ret; - } - async add(c: DocumentBody): Promise { - throw new Error("not allow"); - const res = await fetch(`${baseurl}`, { - method: "POST", - body: JSON.stringify(c), - 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", - }); - 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}`, { - method: "POST", - body: JSON.stringify(rest), - 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}`, { - method: "DELETE", - body: JSON.stringify(rest), - headers: { - "content-type": "application/json", - }, - }); - const ret = await res.json(); - return ret; - } -} -export const CDocumentAccessor = new ClientDocumentAccessor(); -export const makeThumbnailUrl = (x: Document) => { - return `${baseurl}/${x.id}/${x.content_type}/thumbnail`; -}; - -export default CDocumentAccessor; diff --git a/src/client/accessor/util.ts b/src/client/accessor/util.ts deleted file mode 100644 index d2fa9ce..0000000 --- a/src/client/accessor/util.ts +++ /dev/null @@ -1,32 +0,0 @@ -type Representable = string | number | boolean; - -type ToQueryStringA = { - [name: string]: Representable | Representable[] | undefined; -}; - -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("="); - const pv = param[k]; - if (pv === undefined) { - param[k] = v; - } else if (typeof pv === "string") { - param[k] = [pv, v]; - } else { - pv.push(v); - } - }); - return param; -}; diff --git a/src/client/app.tsx b/src/client/app.tsx deleted file mode 100644 index e69a9ed..0000000 --- a/src/client/app.tsx +++ /dev/null @@ -1,65 +0,0 @@ -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 { - DifferencePage, - DocumentAbout, - Gallery, - LoginPage, - NotFoundPage, - ProfilePage, - ReaderPage, - SettingPage, - TagsPage, -} from "./page/mod"; -import { getInitialValue, UserContext } from "./state"; - -import "./css/style.css"; - -const theme = createTheme(); - -const App = () => { - const [user, setUser] = useState(""); - const [userPermission, setUserPermission] = useState([]); - (async () => { - const { username, permission } = await getInitialValue(); - if (username !== user) { - setUser(username); - setUserPermission(permission); - } - })(); - // useEffect(()=>{}); - return ( - - - - - } /> - } /> - }> - }> - } /> - }> - }> - }> - }> - } /> - - - - - ); -}; - -ReactDom.render( - , - document.getElementById("root"), -); diff --git a/src/client/build.ts b/src/client/build.ts deleted file mode 100644 index de54a07..0000000 --- a/src/client/build.ts +++ /dev/null @@ -1,32 +0,0 @@ -import esbuild from "esbuild"; - -async function main() { - try { - const result = await esbuild.build({ - entryPoints: ["app.tsx"], - bundle: true, - outfile: "../../dist/bundle.js", - platform: "browser", - sourcemap: true, - minify: true, - target: ["chrome100", "firefox100"], - watch: { - onRebuild: async (err, _result) => { - if (err) { - console.error("watch build failed: ", err); - } else { - console.log("watch build success"); - } - }, - }, - }); - console.log("watching..."); - return result; - } catch (error) { - console.error(error); - process.exit(1); - } -} - -main().then((res) => { -}); diff --git a/src/client/component/contentinfo.tsx b/src/client/component/contentinfo.tsx deleted file mode 100644 index 8f699b9..0000000 --- a/src/client/component/contentinfo.tsx +++ /dev/null @@ -1,226 +0,0 @@ -import React, {} from "react"; -import { Link as RouterLink } from "react-router-dom"; -import { Document } from "../accessor/document"; - -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"; - -export const makeContentInfoUrl = (id: number) => `/doc/${id}`; -export const makeContentReaderUrl = (id: number) => `/doc/${id}/reader`; - -const useStyles = (theme: Theme) => ({ - thumbnail_content: { - maxHeight: "400px", - maxWidth: "min(400px, 100vw)", - }, - tag_list: { - display: "flex", - justifyContent: "flex-start", - flexWrap: "wrap", - overflowY: "hidden", - "& > *": { - margin: theme.spacing(0.5), - }, - }, - title: { - marginLeft: theme.spacing(1), - }, - infoContainer: { - padding: theme.spacing(2), - }, - subinfoContainer: { - display: "grid", - gridTemplateColumns: "100px auto", - overflowY: "hidden", - alignItems: "baseline", - }, - short_subinfoContainer: { - [theme.breakpoints.down("md")]: { - display: "none", - }, - }, - short_root: { - overflowY: "hidden", - display: "flex", - flexDirection: "column", - [theme.breakpoints.up("sm")]: { - height: 200, - flexDirection: "row", - }, - }, - short_thumbnail_anchor: { - 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%", - }, -}); - -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; -}) => { - 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} - - - {props.short - ? ( - - {document.tags.map(x => ( - - ))} - - ) - : ( - - )} - - {document.deleted_at != null - && ( - - )} - - - ); -}; -async function documentDelete(id: number) { - const t = await DocumentAccessor.del(id); - if (t) { - alert("document deleted!"); - } else { - alert("document already deleted."); - } -} - -function ComicDetailTag(prop: { - tags: string[]; /*classes:{ - tag_list:string -}*/ - path?: string; - createdAt?: number; - deletedAt?: number; -}) { - let allTag = prop.tags; - const tagKind = ["artist", "group", "series", "type", "character"]; - let tagTable: { [kind: string]: string[] } = {}; - for (const kind of tagKind) { - const tags = allTag.filter(x => x.startsWith(kind + ":")).map(x => x.slice(kind.length + 1)); - tagTable[kind] = tags; - allTag = allTag.filter(x => !x.startsWith(kind + ":")); - } - return ( - - {tagKind.map(key => ( - - - {key} - - - {tagTable[key].length !== 0 ? tagTable[key].map((elem, i)=>{ - return <> - {elem} - {(i < tagTable[key].length - 1) ? ',' : ''} - }) : "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 => )} - - - ); -} diff --git a/src/client/component/headline.tsx b/src/client/component/headline.tsx deleted file mode 100644 index 248ada0..0000000 --- a/src/client/component/headline.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import { AccountCircle, ChevronLeft, ChevronRight, Menu as MenuIcon, Search as SearchIcon } from "@mui/icons-material"; -import { - 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"; - -const drawerWidth = 270; - -const DrawerHeader = styled("div")(({ theme }) => ({ - ...theme.mixins.toolbar, -})); - -const StyledDrawer = styled(Drawer)(({ theme }) => ({ - flexShrink: 0, - whiteSpace: "nowrap", - [theme.breakpoints.up("sm")]: { - width: drawerWidth, - }, -})); -const StyledSearchBar = styled("div")(({ theme }) => ({ - position: "relative", - borderRadius: theme.shape.borderRadius, - backgroundColor: alpha(theme.palette.common.white, 0.15), - "&:hover": { - backgroundColor: alpha(theme.palette.common.white, 0.25), - }, - marginLeft: 0, - width: "100%", - [theme.breakpoints.up("sm")]: { - marginLeft: theme.spacing(1), - width: "auto", - }, -})); -const StyledInputBase = styled(InputBase)(({ theme }) => ({ - 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", - }, - }, - }, -})); - -const StyledNav = styled("nav")(({ theme }) => ({ - [theme.breakpoints.up("sm")]: { - width: theme.spacing(7), - }, -})); - -const closedMixin = (theme: Theme) => ({ - overflowX: "hidden", - width: `calc(${theme.spacing(7)} + 1px)`, -}); - -export const Headline = (prop: { - children?: React.ReactNode; - classes?: { - content?: string; - toolbar?: string; - }; - rightAppbar?: React.ReactNode; - menu: React.ReactNode; -}) => { - const [v, setv] = useState(false); - const [anchorEl, setAnchorEl] = React.useState(null); - const theme = useTheme(); - const toggleV = () => setv(!v); - const handleProfileMenuOpen = (e: React.MouseEvent) => setAnchorEl(e.currentTarget); - const handleProfileMenuClose = () => setAnchorEl(null); - const isProfileMenuOpened = Boolean(anchorEl); - 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 ( -
- - - - - - - - Ionian - -
- {prop.rightAppbar} - -
- 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("&")}`); - } -}; - -export default Headline; diff --git a/src/client/component/loading.tsx b/src/client/component/loading.tsx deleted file mode 100644 index 6aa9172..0000000 --- a/src/client/component/loading.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Box, CircularProgress } from "@mui/material"; -import React from "react"; - -export const LoadingCircle = () => { - return ( - - - - ); -}; diff --git a/src/client/component/navlist.tsx b/src/client/component/navlist.tsx deleted file mode 100644 index c7af904..0000000 --- a/src/client/component/navlist.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { - ArrowBack as ArrowBackIcon, - Collections as CollectionIcon, - Folder as FolderIcon, - Home as HomeIcon, - List as ListIcon, - 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 NavList = (props: { children?: React.ReactNode }) => { - return ( - - {props.children} - - ); -}; - -export const BackItem = (props: { to?: string }) => { - return } />; -}; - -export function CommonMenuList(props?: { url?: string }) { - let url = props?.url ?? ""; - return ( - - {url !== "" && ( - <> - - - )} - } /> - }> - } /> - - } /> - - }> - } /> - - ); -} diff --git a/src/client/component/pagepad.tsx b/src/client/component/pagepad.tsx deleted file mode 100644 index 93cf098..0000000 --- a/src/client/component/pagepad.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { styled } from "@mui/material"; - -export const PagePad = styled("div")(({theme})=>({ - padding: theme.spacing(3) -})) \ No newline at end of file diff --git a/src/client/component/tagchip.tsx b/src/client/component/tagchip.tsx deleted file mode 100644 index 3e84519..0000000 --- a/src/client/component/tagchip.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as colors from "@mui/material/colors"; -import Chip, { ChipTypeMap } from "@mui/material/Chip"; -import { emphasize, styled, Theme, useTheme } from "@mui/material/styles"; -import React from "react"; -import { Link as RouterLink } from "react-router-dom"; - -type TagChipStyleProp = { - color: `rgba(${number},${number},${number},${number})` | `#${string}` | 'default'; -}; - -const { blue, pink } = colors; -const getTagColorName = (tagname: string): TagChipStyleProp['color'] => { - if (tagname.startsWith("female")) { - return pink[600]; - } else if (tagname.startsWith("male")) { - return blue[600]; - } else return "default"; -}; - -type ColorChipProp = Omit & TagChipStyleProp & { - component?: React.ElementType; - to?: string; -}; - -export const ColorChip = (props: ColorChipProp) => { - const { color, ...rest } = props; - const theme = useTheme(); - - let newcolor = color; - if (color === "default"){ - newcolor = "#ebebeb"; - } - return ; -}; - -type TagChipProp = Omit & { - tagname: string; -}; - -export const TagChip = (props: TagChipProp) => { - const { tagname, label, clickable, ...rest } = props; - const colorName = getTagColorName(tagname); - - let newlabel: React.ReactNode = label; - if (typeof label === "string") { - const female = "female:"; - const male = "male:"; - if (label.startsWith(female)) { - newlabel = "♀ " + label.slice(female.length); - } else if (label.startsWith(male)) { - newlabel = "♂ " + label.slice(male.length); - } - } - - const inner = clickable - ? ( - - ) - : ( - - ); - return inner; -}; diff --git a/src/client/css/style.css b/src/client/css/style.css deleted file mode 100644 index 257cfc8..0000000 --- a/src/client/css/style.css +++ /dev/null @@ -1,9 +0,0 @@ -body { - margin: 0; - padding: 0; - font-family: sans-serif; -} - -h1 { - text-decoration: underline; -} \ No newline at end of file diff --git a/src/client/package.json b/src/client/package.json deleted file mode 100644 index 3831fab..0000000 --- a/src/client/package.json +++ /dev/null @@ -1,24 +0,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/contentinfo.tsx b/src/client/page/contentinfo.tsx deleted file mode 100644 index 48756ee..0000000 --- a/src/client/page/contentinfo.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { IconButton, Theme, Typography } from "@mui/material"; -import FullscreenIcon from '@mui/icons-material/Fullscreen'; -import React, { useEffect, useRef, 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"; -import { PagePad } from "../component/pagepad"; - -export const makeContentInfoUrl = (id: number) => `/doc/${id}`; -export const makeComicReaderUrl = (id: number) => `/doc/${id}/reader`; - -type DocumentState = { - doc: Document | undefined; - notfound: boolean; -}; - -export function ReaderPage(props?: {}) { - const location = useLocation(); - const match = useParams<{ id: string }>(); - if (match == null) { - throw new Error("unreachable"); - } - const id = Number.parseInt(match.id ?? "NaN"); - const [info, setInfo] = useState({ doc: undefined, notfound: false }); - const menu_list = (link?: string) => ; - const fullScreenTargetRef = useRef(null); - - useEffect(() => { - (async () => { - if (!isNaN(id)) { - const c = await DocumentAccessor.findById(id); - setInfo({ doc: c, notfound: c === undefined }); - } - })(); - }, []); - - if (isNaN(id)) { - return ( - - Oops. Invalid ID - - ); - } else if (info.notfound) { - return ( - - Content has been removed. - - ); - } else if (info.doc === undefined) { - return ( - - - - ); - } else { - const ReaderPage = getPresenter(info.doc); - return ( - { - if (fullScreenTargetRef.current != null && document.fullscreenEnabled) { - fullScreenTargetRef.current.requestFullscreen(); - } - }} - color="inherit"> - - }> - - - ); - } -} - -export const DocumentAbout = (prop?: {}) => { - const match = useParams<{ id: string }>(); - if (match == null) { - throw new Error("unreachable"); - } - const id = Number.parseInt(match.id ?? "NaN"); - const [info, setInfo] = useState({ doc: undefined, notfound: false }); - const menu_list = (link?: string) => ; - - useEffect(() => { - (async () => { - if (!isNaN(id)) { - const c = await DocumentAccessor.findById(id); - setInfo({ doc: c, notfound: c === undefined }); - } - })(); - }, []); - - if (isNaN(id)) { - return ( - - - Oops. Invalid ID - - - ); - } else if (info.notfound) { - return ( - - - Content has been removed. - - - ); - } else if (info.doc === undefined) { - return ( - - - - - - ); - } else { - return ( - - - - - - ); - } -}; diff --git a/src/client/page/difference.tsx b/src/client/page/difference.tsx deleted file mode 100644 index 1cf82de..0000000 --- a/src/client/page/difference.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { Box, Button, Paper, Typography } from "@mui/material"; -import React, { useContext, useEffect, useState } from "react"; -import { CommonMenuList, Headline } from "../component/mod"; -import { UserContext } from "../state"; -import { PagePad } from "../component/pagepad"; - -type FileDifference = { - 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(); - const x = prop.content; - const [button_disable, set_disable] = useState(false); - - return ( - - - {x.type} - - - {x.value.map(y => ( - - - {y.path} - - ))} - - ); -} - -export function DifferencePage() { - const ctx = useContext(UserContext); - // const classes = useStyles(); - const [diffList, setDiffList] = useState< - FileDifference[] - >([]); - const doLoad = async () => { - const list = await fetch("/api/diff/list"); - if (list.ok) { - const inner = await list.json(); - setDiffList(inner); - } 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 bb = await res.json(); - if (bb.ok) { - doLoad(); - } 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 bb = await res.json(); - if (bb.ok) { - doLoad(); - } else { - console.error("fail to add document"); - } - }; - useEffect( - () => { - doLoad(); - 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} -
-
- ); -} diff --git a/src/client/page/gallery.tsx b/src/client/page/gallery.tsx deleted file mode 100644 index 5a7e126..0000000 --- a/src/client/page/gallery.tsx +++ /dev/null @@ -1,134 +0,0 @@ -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"; -import { PagePad } from "../component/pagepad"; - -export type GalleryProp = { - option?: QueryListOption; - diff: string; -}; -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({}); - - useEffect(() => { - if (isLoadVisible && (!loadAll) && (state.documents != undefined)) { - loadMore(); - } - }, [isLoadVisible]); - - useEffect(() => { - const abortController = new AbortController(); - 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. - setState({ documents: c }); - setLoadAll(c.length == 0); - } catch (e) { - if (e instanceof Error) { - setError(e.message); - } else { - setError("unknown error"); - } - } - }; - load(); - }, [props.diff]); - const queryString = toQueryString(props.option ?? {}); - if (state.documents === undefined && error == null) { - 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... - - - - ); - } - function loadMore() { - let option = { ...props.option }; - console.log(elementRef); - if (state.documents === undefined || state.documents.length === 0) { - console.log("loadall"); - setLoadAll(true); - return; - } - const prev_documents = state.documents; - option.cursor = prev_documents[prev_documents.length - 1].id; - console.log("load more", option); - const load = async () => { - const c = await ContentAccessor.findList(option); - if (c.length === 0) { - setLoadAll(true); - } else { - setState({ documents: [...prev_documents, ...c] }); - } - }; - load(); - } -}; - -export const Gallery = () => { - const location = useLocation(); - const query = QueryStringToMap(location.search); - 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 ( - - - - - - ); -}; diff --git a/src/client/page/login.tsx b/src/client/page/login.tsx deleted file mode 100644 index 82d0720..0000000 --- a/src/client/page/login.tsx +++ /dev/null @@ -1,89 +0,0 @@ -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"; -import { PagePad } from "../component/pagepad"; - -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 navigate = useNavigate(); - const handleDialogClose = () => { - setOpenDialog({ ...openDialog, open: false }); - }; - const doLogin = async () => { - try { - const b = await doSessionLogin(userLoginInfo); - 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) { - 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} - - - - - -
-
- ); -}; diff --git a/src/client/page/profile.tsx b/src/client/page/profile.tsx deleted file mode 100644 index 8df722e..0000000 --- a/src/client/page/profile.tsx +++ /dev/null @@ -1,151 +0,0 @@ -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 { UserContext } from "../state"; -import { PagePad } from "../component/pagepad"; - -const useStyles = (theme: Theme) => ({ - paper: { - alignSelf: "center", - padding: theme.spacing(2), - }, - formfield: { - display: "flex", - flexFlow: "column", - }, -}); - -export function ProfilePage() { - const userctx = useContext(UserContext); - // 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 = () => { - 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." }); - 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." }); - } - } else { - const res = await fetch("/user/reset", { - method: "POST", - body: JSON.stringify({ - username: userctx.username, - oldpassword: oldpw, - newpassword: newpw, - }), - headers: { - "content-type": "application/json", - }, - }); - if (res.status != 200) { - set_msg_dialog({ opened: true, msg: "failed to change password." }); - } - } - handle_close(); - }; - return ( - - - - - - - {userctx.username} - - - - 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} - - - - - -
-
- ); -} diff --git a/src/client/page/reader/comic.tsx b/src/client/page/reader/comic.tsx deleted file mode 100644 index 70d7baf..0000000 --- a/src/client/page/reader/comic.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { Typography, styled } from "@mui/material"; -import React, { RefObject, useEffect, useState } from "react"; -import { useSearchParams } from "react-router-dom"; -import { Document } from "../../accessor/document"; - -type ComicType = "comic" | "artist cg" | "donjinshi" | "western"; - -export type PresentableTag = { - artist: string[]; - group: string[]; - series: string[]; - type: ComicType; - character: string[]; - tags: string[]; -}; - -const ViewMain = styled("div")(({ theme }) => ({ - overflow: "hidden", - width: "100%", - height: "calc(100vh - 64px)", - position: "relative", -})); -const CurrentView = styled("img")(({theme})=>({ - maxWidth: "100%", - maxHeight: "100%", - top:"50%", - left:"50%", - transform: "translate(-50%,-50%)", - position: "absolute" -})); - -export const ComicReader = (props: { doc: Document, - fullScreenTarget?: RefObject}) => { - const additional = props.doc.additional; - const [searchParams, setSearchParams] = useSearchParams(); - - const curPage = (parseInt(searchParams.get("page") ?? "0")); - const setCurPage = (n: number) => { - setSearchParams([ - ["page", n.toString()] - ]); - } - if (isNaN(curPage)){ - return Error. Page number is not a number. - } - if (!("page" in additional)) { - console.error("invalid content : page read fail : " + JSON.stringify(additional)); - return Error. DB error. page restriction; - } - - const maxPage: number = additional["page"] as number; - const PageDown = () => setCurPage(Math.max(curPage - 1, 0)); - const PageUp = () => setCurPage(Math.min(curPage + 1, maxPage - 1)); - - const onKeyUp = (e: KeyboardEvent) => { - console.log(`currently: ${curPage}/${maxPage}`) - if (e.code === "ArrowLeft") { - PageDown(); - } else if (e.code === "ArrowRight") { - PageUp(); - } - }; - - useEffect(() => { - document.addEventListener("keydown", onKeyUp); - return () => { - document.removeEventListener("keydown", onKeyUp); - }; - }, [curPage]); - // theme.mixins.toolbar.minHeight; - return ( - -
- -
-
- ); -}; - -export default ComicReader; diff --git a/src/client/page/reader/reader.tsx b/src/client/page/reader/reader.tsx deleted file mode 100644 index 16877d6..0000000 --- a/src/client/page/reader/reader.tsx +++ /dev/null @@ -1,78 +0,0 @@ -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; - fullScreenTarget?: React.RefObject; -} -interface PagePresenter { - (prop: PagePresenterProp): JSX.Element; -} - -export const getPresenter = (content: Document): PagePresenter => { - switch (content.content_type) { - case "comic": - return ComicReader; - case "video": - return VideoReader; - } - return () => Not implemented reader; -}; -const BackgroundDiv = styled("div")({ - height: "400px", - width: "300px", - backgroundColor: "#272733", - display: "flex", - alignItems: "center", - justifyContent: "center", -}); - -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); - }; - - useEffect(() => { - 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({}); - const [loaded, setLoaded] = useState(false); - useEffect(() => { - if (isVisible) { - setLoaded(true); - } - }, [isVisible]); - const style = { - maxHeight: "400px", - maxWidth: "min(400px, 100vw)", - }; - const thumbnailurl = makeThumbnailUrl(props.content); - 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 deleted file mode 100644 index af6d137..0000000 --- a/src/client/page/reader/video.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react"; -import { Document } from "../../accessor/document"; - -export const VideoReader = (props: { doc: Document }) => { - const id = props.doc.id; - return ( - - ); -}; diff --git a/src/client/page/setting.tsx b/src/client/page/setting.tsx deleted file mode 100644 index a2d102b..0000000 --- a/src/client/page/setting.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Paper, Typography } from "@mui/material"; -import React from "react"; -import { CommonMenuList, Headline } from "../component/mod"; -import { PagePad } from "../component/pagepad"; - -export const SettingPage = () => { - const menu = CommonMenuList(); - return ( - - - - Setting - - - - ); -}; diff --git a/src/client/page/tags.tsx b/src/client/page/tags.tsx deleted file mode 100644 index 9c65cea..0000000 --- a/src/client/page/tags.tsx +++ /dev/null @@ -1,76 +0,0 @@ -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"; -import { PagePad } from "../component/pagepad"; - -type TagCount = { - tag_name: string; - occurs: number; -}; - -const tagTableColumn: GridColDef[] = [ - { - 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); - const isLoading = data === undefined; - - useEffect(() => { - loadData(); - }, []); - - if (isLoading) { - return ; - } - if (error !== undefined) { - return {error}; - } - return ( - - - t.tag_name}> - - - ); - - async function loadData() { - try { - const res = await fetch("/api/tags?withCount=true"); - const data = await res.json(); - setData(data); - } catch (e) { - setData([]); - if (e instanceof Error) { - setErrorMsg(e.message); - } else { - console.log(e); - setErrorMsg(""); - } - } - } -} - -export const TagsPage = () => { - const menu = CommonMenuList(); - return ( - - - - - - ); -}; diff --git a/src/client/pnpm-lock.yaml b/src/client/pnpm-lock.yaml deleted file mode 100644 index f8f0e79..0000000 --- a/src/client/pnpm-lock.yaml +++ /dev/null @@ -1,1421 +0,0 @@ -lockfileVersion: '6.0' - -dependencies: - '@emotion/react': - specifier: ^11.9.0 - version: 11.9.3(@babel/core@7.22.1)(@types/react@18.0.12)(react@18.1.0) - '@emotion/styled': - specifier: ^11.8.1 - version: 11.9.3(@babel/core@7.22.1)(@emotion/react@11.9.3)(@types/react@18.0.12)(react@18.1.0) - '@mui/icons-material': - specifier: ^5.6.2 - version: 5.8.3(@mui/material@5.8.3)(@types/react@18.0.12)(react@18.1.0) - '@mui/material': - specifier: ^5.6.2 - version: 5.8.3(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(@types/react@18.0.12)(react-dom@18.1.0)(react@18.1.0) - '@mui/x-data-grid': - specifier: ^5.12.3 - version: 5.12.3(@mui/material@5.8.3)(@mui/system@5.8.3)(react-dom@18.1.0)(react@18.1.0) - '@types/react': - specifier: ^18.0.5 - version: 18.0.12 - '@types/react-dom': - specifier: ^18.0.1 - version: 18.0.5 - react: - specifier: ^18.0.0 - version: 18.1.0 - react-dom: - specifier: ^18.0.0 - version: 18.1.0(react@18.1.0) - react-router-dom: - specifier: ^6.3.0 - version: 6.3.0(react-dom@18.1.0)(react@18.1.0) - -devDependencies: - esbuild: - specifier: ^0.14.36 - version: 0.14.43 - ts-node: - specifier: ^10.7.0 - version: 10.8.1(@types/node@20.2.5)(typescript@5.0.4) - -packages: - - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.9 - dev: false - - /@babel/code-frame@7.16.7: - resolution: {integrity: sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.17.12 - dev: false - - /@babel/code-frame@7.21.4: - resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.18.6 - dev: false - - /@babel/compat-data@7.22.3: - resolution: {integrity: sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/core@7.22.1: - resolution: {integrity: sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.22.3 - '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1) - '@babel/helper-module-transforms': 7.22.1 - '@babel/helpers': 7.22.3 - '@babel/parser': 7.22.4 - '@babel/template': 7.21.9 - '@babel/traverse': 7.22.4 - '@babel/types': 7.22.4 - convert-source-map: 1.8.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/generator@7.22.3: - resolution: {integrity: sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.4 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - jsesc: 2.5.2 - dev: false - - /@babel/helper-compilation-targets@7.22.1(@babel/core@7.22.1): - resolution: {integrity: sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.22.3 - '@babel/core': 7.22.1 - '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.7 - lru-cache: 5.1.1 - semver: 6.3.0 - dev: false - - /@babel/helper-environment-visitor@7.22.1: - resolution: {integrity: sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-function-name@7.21.0: - resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.21.9 - '@babel/types': 7.22.4 - dev: false - - /@babel/helper-hoist-variables@7.18.6: - resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.4 - dev: false - - /@babel/helper-module-imports@7.16.7: - resolution: {integrity: sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.18.4 - dev: false - - /@babel/helper-module-imports@7.21.4: - resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.4 - dev: false - - /@babel/helper-module-transforms@7.22.1: - resolution: {integrity: sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.22.1 - '@babel/helper-module-imports': 7.21.4 - '@babel/helper-simple-access': 7.21.5 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.21.9 - '@babel/traverse': 7.22.4 - '@babel/types': 7.22.4 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/helper-plugin-utils@7.17.12: - resolution: {integrity: sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-simple-access@7.21.5: - resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.4 - dev: false - - /@babel/helper-split-export-declaration@7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.22.4 - dev: false - - /@babel/helper-string-parser@7.21.5: - resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-validator-identifier@7.16.7: - resolution: {integrity: sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-validator-identifier@7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-validator-option@7.21.0: - resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helpers@7.22.3: - resolution: {integrity: sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.21.9 - '@babel/traverse': 7.22.4 - '@babel/types': 7.22.4 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/highlight@7.17.12: - resolution: {integrity: sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.16.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: false - - /@babel/highlight@7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.19.1 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: false - - /@babel/parser@7.22.4: - resolution: {integrity: sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.22.4 - dev: false - - /@babel/plugin-syntax-jsx@7.17.12(@babel/core@7.22.1): - resolution: {integrity: sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.22.1 - '@babel/helper-plugin-utils': 7.17.12 - dev: false - - /@babel/runtime@7.18.3: - resolution: {integrity: sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.13.9 - dev: false - - /@babel/template@7.21.9: - resolution: {integrity: sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.21.4 - '@babel/parser': 7.22.4 - '@babel/types': 7.22.4 - dev: false - - /@babel/traverse@7.22.4: - resolution: {integrity: sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.22.3 - '@babel/helper-environment-visitor': 7.22.1 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-hoist-variables': 7.18.6 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/parser': 7.22.4 - '@babel/types': 7.22.4 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/types@7.18.4: - resolution: {integrity: sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.16.7 - to-fast-properties: 2.0.0 - dev: false - - /@babel/types@7.22.4: - resolution: {integrity: sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.21.5 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 - dev: false - - /@cspotcode/source-map-support@0.8.1: - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - dev: true - - /@emotion/babel-plugin@11.9.2(@babel/core@7.22.1): - resolution: {integrity: sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.22.1 - '@babel/helper-module-imports': 7.16.7 - '@babel/plugin-syntax-jsx': 7.17.12(@babel/core@7.22.1) - '@babel/runtime': 7.18.3 - '@emotion/hash': 0.8.0 - '@emotion/memoize': 0.7.5 - '@emotion/serialize': 1.0.4 - babel-plugin-macros: 2.8.0 - convert-source-map: 1.8.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.0.13 - dev: false - - /@emotion/cache@11.9.3: - resolution: {integrity: sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg==} - dependencies: - '@emotion/memoize': 0.7.5 - '@emotion/sheet': 1.1.1 - '@emotion/utils': 1.1.0 - '@emotion/weak-memoize': 0.2.5 - stylis: 4.0.13 - dev: false - - /@emotion/hash@0.8.0: - resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} - dev: false - - /@emotion/is-prop-valid@1.1.3: - resolution: {integrity: sha512-RFg04p6C+1uO19uG8N+vqanzKqiM9eeV1LDOG3bmkYmuOj7NbKNlFC/4EZq5gnwAIlcC/jOT24f8Td0iax2SXA==} - dependencies: - '@emotion/memoize': 0.7.5 - dev: false - - /@emotion/memoize@0.7.5: - resolution: {integrity: sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==} - dev: false - - /@emotion/react@11.9.3(@babel/core@7.22.1)(@types/react@18.0.12)(react@18.1.0): - resolution: {integrity: sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ==} - peerDependencies: - '@babel/core': ^7.0.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@babel/core': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/core': 7.22.1 - '@babel/runtime': 7.18.3 - '@emotion/babel-plugin': 11.9.2(@babel/core@7.22.1) - '@emotion/cache': 11.9.3 - '@emotion/serialize': 1.0.4 - '@emotion/utils': 1.1.0 - '@emotion/weak-memoize': 0.2.5 - '@types/react': 18.0.12 - hoist-non-react-statics: 3.3.2 - react: 18.1.0 - dev: false - - /@emotion/serialize@1.0.4: - resolution: {integrity: sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg==} - dependencies: - '@emotion/hash': 0.8.0 - '@emotion/memoize': 0.7.5 - '@emotion/unitless': 0.7.5 - '@emotion/utils': 1.1.0 - csstype: 3.1.0 - dev: false - - /@emotion/sheet@1.1.1: - resolution: {integrity: sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA==} - dev: false - - /@emotion/styled@11.9.3(@babel/core@7.22.1)(@emotion/react@11.9.3)(@types/react@18.0.12)(react@18.1.0): - resolution: {integrity: sha512-o3sBNwbtoVz9v7WB1/Y/AmXl69YHmei2mrVnK7JgyBJ//Rst5yqPZCecEJlMlJrFeWHp+ki/54uN265V2pEcXA==} - peerDependencies: - '@babel/core': ^7.0.0 - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@babel/core': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/core': 7.22.1 - '@babel/runtime': 7.18.3 - '@emotion/babel-plugin': 11.9.2(@babel/core@7.22.1) - '@emotion/is-prop-valid': 1.1.3 - '@emotion/react': 11.9.3(@babel/core@7.22.1)(@types/react@18.0.12)(react@18.1.0) - '@emotion/serialize': 1.0.4 - '@emotion/utils': 1.1.0 - '@types/react': 18.0.12 - react: 18.1.0 - dev: false - - /@emotion/unitless@0.7.5: - resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} - dev: false - - /@emotion/utils@1.1.0: - resolution: {integrity: sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==} - dev: false - - /@emotion/weak-memoize@0.2.5: - resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==} - dev: false - - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.13 - '@jridgewell/trace-mapping': 0.3.18 - dev: false - - /@jridgewell/resolve-uri@3.0.7: - resolution: {integrity: sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==} - engines: {node: '>=6.0.0'} - - /@jridgewell/resolve-uri@3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} - dev: false - - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: false - - /@jridgewell/sourcemap-codec@1.4.13: - resolution: {integrity: sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==} - - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - dev: false - - /@jridgewell/trace-mapping@0.3.18: - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - dev: false - - /@jridgewell/trace-mapping@0.3.9: - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - dependencies: - '@jridgewell/resolve-uri': 3.0.7 - '@jridgewell/sourcemap-codec': 1.4.13 - - /@mui/base@5.0.0-alpha.84(@types/react@18.0.12)(react-dom@18.1.0)(react@18.1.0): - resolution: {integrity: sha512-uDx+wGVytS+ZHiWHyzUyijY83GSIXJpzSJ0PGc/8/s+8nBzeHvaPKrAyJz15ASLr52hYRA6PQGqn0eRAsB7syQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.18.3 - '@emotion/is-prop-valid': 1.1.3 - '@mui/types': 7.1.3(@types/react@18.0.12) - '@mui/utils': 5.8.0(react@18.1.0) - '@popperjs/core': 2.11.5 - '@types/react': 18.0.12 - clsx: 1.1.1 - prop-types: 15.8.1 - react: 18.1.0 - react-dom: 18.1.0(react@18.1.0) - react-is: 17.0.2 - dev: false - - /@mui/icons-material@5.8.3(@mui/material@5.8.3)(@types/react@18.0.12)(react@18.1.0): - resolution: {integrity: sha512-dAdhimSLKOV0Q8FR7AYGEaCrTUh9OV7zU4Ueo5REoUt4cC3Vy+UBKDjZk66x5ezaYb63AFgQIFwtnZj3B/QDbQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@mui/material': ^5.0.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.18.3 - '@mui/material': 5.8.3(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(@types/react@18.0.12)(react-dom@18.1.0)(react@18.1.0) - '@types/react': 18.0.12 - react: 18.1.0 - dev: false - - /@mui/material@5.8.3(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(@types/react@18.0.12)(react-dom@18.1.0)(react@18.1.0): - resolution: {integrity: sha512-8UecY/W9SMtEZm5PMCUcMbujajVP6fobu0BgBPiIWwwWRblZVEzqprY6v1P2me7qCyrve4L4V/rqAKPKhVHOSg==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.18.3 - '@emotion/react': 11.9.3(@babel/core@7.22.1)(@types/react@18.0.12)(react@18.1.0) - '@emotion/styled': 11.9.3(@babel/core@7.22.1)(@emotion/react@11.9.3)(@types/react@18.0.12)(react@18.1.0) - '@mui/base': 5.0.0-alpha.84(@types/react@18.0.12)(react-dom@18.1.0)(react@18.1.0) - '@mui/system': 5.8.3(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(@types/react@18.0.12)(react@18.1.0) - '@mui/types': 7.1.3(@types/react@18.0.12) - '@mui/utils': 5.8.0(react@18.1.0) - '@types/react': 18.0.12 - '@types/react-transition-group': 4.4.4 - clsx: 1.1.1 - csstype: 3.1.0 - hoist-non-react-statics: 3.3.2 - prop-types: 15.8.1 - react: 18.1.0 - react-dom: 18.1.0(react@18.1.0) - react-is: 17.0.2 - react-transition-group: 4.4.2(react-dom@18.1.0)(react@18.1.0) - dev: false - - /@mui/private-theming@5.8.0(@types/react@18.0.12)(react@18.1.0): - resolution: {integrity: sha512-MjRAneTmCKLR9u2S4jtjLUe6gpHxlbb4g2bqpDJ2PdwlvwsWIUzbc/gVB4dvccljXeWxr5G2M/Co2blXisvFIw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.18.3 - '@mui/utils': 5.8.0(react@18.1.0) - '@types/react': 18.0.12 - prop-types: 15.8.1 - react: 18.1.0 - dev: false - - /@mui/styled-engine@5.8.0(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(react@18.1.0): - resolution: {integrity: sha512-Q3spibB8/EgeMYHc+/o3RRTnAYkSl7ROCLhXJ830W8HZ2/iDiyYp16UcxKPurkXvLhUaILyofPVrP3Su2uKsAw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - dependencies: - '@babel/runtime': 7.18.3 - '@emotion/cache': 11.9.3 - '@emotion/react': 11.9.3(@babel/core@7.22.1)(@types/react@18.0.12)(react@18.1.0) - '@emotion/styled': 11.9.3(@babel/core@7.22.1)(@emotion/react@11.9.3)(@types/react@18.0.12)(react@18.1.0) - prop-types: 15.8.1 - react: 18.1.0 - dev: false - - /@mui/system@5.8.3(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(@types/react@18.0.12)(react@18.1.0): - resolution: {integrity: sha512-/tyGQcYqZT0nl98qV9XnGiedTO+V7VHc28k4POfhMJNedB1CRrwWRm767DeEdc5f/8CU2See3WD16ikP6pYiOA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.18.3 - '@emotion/react': 11.9.3(@babel/core@7.22.1)(@types/react@18.0.12)(react@18.1.0) - '@emotion/styled': 11.9.3(@babel/core@7.22.1)(@emotion/react@11.9.3)(@types/react@18.0.12)(react@18.1.0) - '@mui/private-theming': 5.8.0(@types/react@18.0.12)(react@18.1.0) - '@mui/styled-engine': 5.8.0(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(react@18.1.0) - '@mui/types': 7.1.3(@types/react@18.0.12) - '@mui/utils': 5.8.0(react@18.1.0) - '@types/react': 18.0.12 - clsx: 1.1.1 - csstype: 3.1.0 - prop-types: 15.8.1 - react: 18.1.0 - dev: false - - /@mui/types@7.1.3(@types/react@18.0.12): - resolution: {integrity: sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==} - peerDependencies: - '@types/react': '*' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.0.12 - dev: false - - /@mui/utils@5.8.0(react@18.1.0): - resolution: {integrity: sha512-7LgUtCvz78676iC0wpTH7HizMdCrTphhBmRWimIMFrp5Ph6JbDFVuKS1CwYnWWxRyYKL0QzXrDL0lptAU90EXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.18.3 - '@types/prop-types': 15.7.5 - '@types/react-is': 17.0.3 - prop-types: 15.8.1 - react: 18.1.0 - react-is: 17.0.2 - dev: false - - /@mui/x-data-grid@5.12.3(@mui/material@5.8.3)(@mui/system@5.8.3)(react-dom@18.1.0)(react@18.1.0): - resolution: {integrity: sha512-57A2MkRR/uUNC/dECFV0YDJvi1Q+gQgmgw1OHmZ1uSnKh29PcHpswkdapO0LueLpxAy8tfH+fTtnnPDmYgJeUg==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@mui/material': ^5.4.1 - '@mui/system': ^5.4.1 - react: ^17.0.2 || ^18.0.0 - react-dom: ^17.0.2 || ^18.0.0 - dependencies: - '@babel/runtime': 7.18.3 - '@mui/material': 5.8.3(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(@types/react@18.0.12)(react-dom@18.1.0)(react@18.1.0) - '@mui/system': 5.8.3(@emotion/react@11.9.3)(@emotion/styled@11.9.3)(@types/react@18.0.12)(react@18.1.0) - '@mui/utils': 5.8.0(react@18.1.0) - clsx: 1.1.1 - prop-types: 15.8.1 - react: 18.1.0 - react-dom: 18.1.0(react@18.1.0) - reselect: 4.1.6 - dev: false - - /@popperjs/core@2.11.5: - resolution: {integrity: sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==} - dev: false - - /@tsconfig/node10@1.0.9: - resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} - dev: true - - /@tsconfig/node12@1.0.10: - resolution: {integrity: sha512-N+srakvPaYMGkwjNDx3ASx65Zl3QG8dJgVtIB+YMOkucU+zctlv/hdP5250VKdDHSDoW9PFZoCqbqNcAPjCjXA==} - dev: true - - /@tsconfig/node14@1.0.2: - resolution: {integrity: sha512-YwrUA5ysDXHFYfL0Xed9x3sNS4P+aKlCOnnbqUa2E5HdQshHFleCJVrj1PlGTb4GgFUCDyte1v3JWLy2sz8Oqg==} - dev: true - - /@tsconfig/node16@1.0.3: - resolution: {integrity: sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==} - dev: true - - /@types/node@20.2.5: - resolution: {integrity: sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==} - dev: true - - /@types/parse-json@4.0.0: - resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} - dev: false - - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - dev: false - - /@types/react-dom@18.0.5: - resolution: {integrity: sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA==} - dependencies: - '@types/react': 18.0.12 - dev: false - - /@types/react-is@17.0.3: - resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} - dependencies: - '@types/react': 18.0.12 - dev: false - - /@types/react-transition-group@4.4.4: - resolution: {integrity: sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==} - dependencies: - '@types/react': 18.0.12 - dev: false - - /@types/react@18.0.12: - resolution: {integrity: sha512-duF1OTASSBQtcigUvhuiTB1Ya3OvSy+xORCiEf20H0P0lzx+/KeVsA99U5UjLXSbyo1DRJDlLKqTeM1ngosqtg==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.2 - csstype: 3.1.0 - dev: false - - /@types/scheduler@0.16.2: - resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} - dev: false - - /acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} - dev: true - - /acorn@8.7.1: - resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - dev: false - - /arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true - - /babel-plugin-macros@2.8.0: - resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==} - dependencies: - '@babel/runtime': 7.18.3 - cosmiconfig: 6.0.0 - resolve: 1.22.0 - dev: false - - /browserslist@4.21.7: - resolution: {integrity: sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001492 - electron-to-chromium: 1.4.415 - node-releases: 2.0.12 - update-browserslist-db: 1.0.11(browserslist@4.21.7) - dev: false - - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: false - - /caniuse-lite@1.0.30001492: - resolution: {integrity: sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==} - dev: false - - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - dev: false - - /clsx@1.1.1: - resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==} - engines: {node: '>=6'} - dev: false - - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - dev: false - - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: false - - /convert-source-map@1.8.0: - resolution: {integrity: sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==} - dependencies: - safe-buffer: 5.1.2 - dev: false - - /cosmiconfig@6.0.0: - resolution: {integrity: sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==} - engines: {node: '>=8'} - dependencies: - '@types/parse-json': 4.0.0 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - dev: false - - /create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true - - /csstype@3.1.0: - resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==} - dev: false - - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: false - - /diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dev: true - - /dom-helpers@5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dependencies: - '@babel/runtime': 7.18.3 - csstype: 3.1.0 - dev: false - - /electron-to-chromium@1.4.415: - resolution: {integrity: sha512-3meOxxvyUOJVwa7cem6O2/MRPZ+FTzblSPSpG7biZoF9yOVrhCaS2l9C4jjW6YTm8uuEpmApuP0xQJSmUglfdg==} - dev: false - - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - dependencies: - is-arrayish: 0.2.1 - dev: false - - /esbuild-android-64@0.14.43: - resolution: {integrity: sha512-kqFXAS72K6cNrB6RiM7YJ5lNvmWRDSlpi7ZuRZ1hu1S3w0zlwcoCxWAyM23LQUyZSs1PbjHgdbbfYAN8IGh6xg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64@0.14.43: - resolution: {integrity: sha512-bKS2BBFh+7XZY9rpjiHGRNA7LvWYbZWP87pLehggTG7tTaCDvj8qQGOU/OZSjCSKDYbgY7Q+oDw8RlYQ2Jt2BA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64@0.14.43: - resolution: {integrity: sha512-/3PSilx011ttoieRGkSZ0XV8zjBf2C9enV4ScMMbCT4dpx0mFhMOpFnCHkOK0pWGB8LklykFyHrWk2z6DENVUg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64@0.14.43: - resolution: {integrity: sha512-1HyFUKs8DMCBOvw1Qxpr5Vv/ThNcVIFb5xgXWK3pyT40WPvgYIiRTwJCvNs4l8i5qWF8/CK5bQxJVDjQvtv0Yw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64@0.14.43: - resolution: {integrity: sha512-FNWc05TPHYgaXjbPZO5/rJKSBslfG6BeMSs8GhwnqAKP56eEhvmzwnIz1QcC9cRVyO+IKqWNfmHFkCa1WJTULA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64@0.14.43: - resolution: {integrity: sha512-amrYopclz3VohqisOPR6hA3GOWA3LZC1WDLnp21RhNmoERmJ/vLnOpnrG2P/Zao+/erKTCUqmrCIPVtj58DRoA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32@0.14.43: - resolution: {integrity: sha512-KoxoEra+9O3AKVvgDFvDkiuddCds6q71owSQEYwjtqRV7RwbPzKxJa6+uyzUulHcyGVq0g15K0oKG5CFBcvYDw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64@0.14.43: - resolution: {integrity: sha512-EwINwGMyiJMgBby5/SbMqKcUhS5AYAZ2CpEBzSowsJPNBJEdhkCTtEjk757TN/wxgbu3QklqDM6KghY660QCUw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64@0.14.43: - resolution: {integrity: sha512-UlSpjMWllAc70zYbHxWuDS3FJytyuR/gHJYBr8BICcTNb/TSOYVBg6U7b3jZ3mILTrgzwJUHwhEwK18FZDouUQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm@0.14.43: - resolution: {integrity: sha512-e6YzQUoDxxtyamuF12eVzzRC7bbEFSZohJ6igQB9tBqnNmIQY3fI6Cns3z2wxtbZ3f2o6idkD2fQnlvs2902Dg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le@0.14.43: - resolution: {integrity: sha512-f+v8cInPEL1/SDP//CfSYzcDNgE4CY3xgDV81DWm3KAPWzhvxARrKxB1Pstf5mB56yAslJDxu7ryBUPX207EZA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le@0.14.43: - resolution: {integrity: sha512-5wZYMDGAL/K2pqkdIsW+I4IR41kyfHr/QshJcNpUfK3RjB3VQcPWOaZmc+74rm4ZjVirYrtz+jWw0SgxtxRanA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64@0.14.43: - resolution: {integrity: sha512-lYcAOUxp85hC7lSjycJUVSmj4/9oEfSyXjb/ua9bNl8afonaduuqtw7hvKMoKuYnVwOCDw4RSfKpcnIRDWq+Bw==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x@0.14.43: - resolution: {integrity: sha512-27e43ZhHvhFE4nM7HqtUbMRu37I/4eNSUbb8FGZWszV+uLzMIsHDwLoBiJmw7G9N+hrehNPeQ4F5Ujad0DrUKQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64@0.14.43: - resolution: {integrity: sha512-2mH4QF6hHBn5zzAfxEI/2eBC0mspVsZ6UVo821LpAJKMvLJPBk3XJO5xwg7paDqSqpl7p6IRrAenW999AEfJhQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64@0.14.43: - resolution: {integrity: sha512-ZhQpiZjvqCqO8jKdGp9+8k9E/EHSA+zIWOg+grwZasI9RoblqJ1QiZqqi7jfd6ZrrG1UFBNGe4m0NFxCFbMVbg==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64@0.14.43: - resolution: {integrity: sha512-DgxSi9DaHReL9gYuul2rrQCAapgnCJkh3LSHPKsY26zytYppG0HgkgVF80zjIlvEsUbGBP/GHQzBtrezj/Zq1Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32@0.14.43: - resolution: {integrity: sha512-Ih3+2O5oExiqm0mY6YYE5dR0o8+AspccQ3vIAtRodwFvhuyGLjb0Hbmzun/F3Lw19nuhPMu3sW2fqIJ5xBxByw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64@0.14.43: - resolution: {integrity: sha512-8NsuNfI8xwFuJbrCuI+aBqNTYkrWErejFO5aYM+yHqyHuL8mmepLS9EPzAzk8rvfaJrhN0+RvKWAcymViHOKEw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64@0.14.43: - resolution: {integrity: sha512-7ZlD7bo++kVRblJEoG+cepljkfP8bfuTPz5fIXzptwnPaFwGS6ahvfoYzY7WCf5v/1nX2X02HDraVItTgbHnKw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild@0.14.43: - resolution: {integrity: sha512-Uf94+kQmy/5jsFwKWiQB4hfo/RkM9Dh7b79p8yqd1tshULdr25G2szLz631NoH3s2ujnKEKVD16RmOxvCNKRFA==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - esbuild-android-64: 0.14.43 - esbuild-android-arm64: 0.14.43 - esbuild-darwin-64: 0.14.43 - esbuild-darwin-arm64: 0.14.43 - esbuild-freebsd-64: 0.14.43 - esbuild-freebsd-arm64: 0.14.43 - esbuild-linux-32: 0.14.43 - esbuild-linux-64: 0.14.43 - esbuild-linux-arm: 0.14.43 - esbuild-linux-arm64: 0.14.43 - esbuild-linux-mips64le: 0.14.43 - esbuild-linux-ppc64le: 0.14.43 - esbuild-linux-riscv64: 0.14.43 - esbuild-linux-s390x: 0.14.43 - esbuild-netbsd-64: 0.14.43 - esbuild-openbsd-64: 0.14.43 - esbuild-sunos-64: 0.14.43 - esbuild-windows-32: 0.14.43 - esbuild-windows-64: 0.14.43 - esbuild-windows-arm64: 0.14.43 - dev: true - - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - dev: false - - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - dev: false - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: false - - /find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - dev: false - - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: false - - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - dev: false - - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - dev: false - - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - dev: false - - /has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: false - - /history@5.3.0: - resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==} - dependencies: - '@babel/runtime': 7.18.3 - dev: false - - /hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - dependencies: - react-is: 16.13.1 - dev: false - - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: false - - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - dev: false - - /is-core-module@2.9.0: - resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==} - dependencies: - has: 1.0.3 - dev: false - - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: false - - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - dev: false - - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - dev: false - - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - dev: false - - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: false - - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - dependencies: - js-tokens: 4.0.0 - dev: false - - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - dependencies: - yallist: 3.1.1 - dev: false - - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: false - - /node-releases@2.0.12: - resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} - dev: false - - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - dev: false - - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: false - - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - dependencies: - '@babel/code-frame': 7.16.7 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - dev: false - - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: false - - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - dev: false - - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: false - - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - dev: false - - /react-dom@18.1.0(react@18.1.0): - resolution: {integrity: sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==} - peerDependencies: - react: ^18.1.0 - dependencies: - loose-envify: 1.4.0 - react: 18.1.0 - scheduler: 0.22.0 - dev: false - - /react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - dev: false - - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - dev: false - - /react-router-dom@6.3.0(react-dom@18.1.0)(react@18.1.0): - resolution: {integrity: sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - dependencies: - history: 5.3.0 - react: 18.1.0 - react-dom: 18.1.0(react@18.1.0) - react-router: 6.3.0(react@18.1.0) - dev: false - - /react-router@6.3.0(react@18.1.0): - resolution: {integrity: sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==} - peerDependencies: - react: '>=16.8' - dependencies: - history: 5.3.0 - react: 18.1.0 - dev: false - - /react-transition-group@4.4.2(react-dom@18.1.0)(react@18.1.0): - resolution: {integrity: sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - dependencies: - '@babel/runtime': 7.18.3 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.1.0 - react-dom: 18.1.0(react@18.1.0) - dev: false - - /react@18.1.0: - resolution: {integrity: sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - dev: false - - /regenerator-runtime@0.13.9: - resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} - dev: false - - /reselect@4.1.6: - resolution: {integrity: sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ==} - dev: false - - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: false - - /resolve@1.22.0: - resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} - hasBin: true - dependencies: - is-core-module: 2.9.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: false - - /safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - dev: false - - /scheduler@0.22.0: - resolution: {integrity: sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==} - dependencies: - loose-envify: 1.4.0 - dev: false - - /semver@6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - dev: false - - /source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - dev: false - - /stylis@4.0.13: - resolution: {integrity: sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==} - dev: false - - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - dev: false - - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: false - - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - dev: false - - /ts-node@10.8.1(@types/node@20.2.5)(typescript@5.0.4): - resolution: {integrity: sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.10 - '@tsconfig/node14': 1.0.2 - '@tsconfig/node16': 1.0.3 - '@types/node': 20.2.5 - acorn: 8.7.1 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 5.0.4 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - - /typescript@5.0.4: - resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} - engines: {node: '>=12.20'} - hasBin: true - dev: true - - /update-browserslist-db@1.0.11(browserslist@4.21.7): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.7 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: false - - /v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true - - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: false - - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - dev: false - - /yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true diff --git a/src/client/state.tsx b/src/client/state.tsx deleted file mode 100644 index ad9a74c..0000000 --- a/src/client/state.tsx +++ /dev/null @@ -1,94 +0,0 @@ -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[]) => {}, -}); - -type LoginLocalStorage = { - username: string; - permission: string[]; - accessExpired: number; -}; - -let localObj: LoginLocalStorage | null = null; - -export const getInitialValue = async () => { - if (localObj === null) { - 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", - }); - 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 { - localObj = { - accessExpired: 0, - username: "", - permission: r.permission, - }; - } - window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); - return { - username: r.username, - permission: r.permission, - }; -}; -export const doLogout = async () => { - const req = await fetch("/user/logout", { - method: "POST", - }); - try { - const res = await req.json(); - localObj = { - accessExpired: 0, - username: "", - 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" }, - }); - const b = await res.json(); - if (res.status !== 200) { - return b.detail as string; - } - localObj = b; - window.localStorage.setItem("UserLoginContext", JSON.stringify(localObj)); - return b; -}; diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index 583c834..0000000 --- a/src/config.ts +++ /dev/null @@ -1,22 +0,0 @@ -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", - }, - debug: true, - }, - production: { - client: "sqlite3", - connection: { - filename: "./db.sqlite3", - }, - }, - }; -} diff --git a/src/content/comic.ts b/src/content/comic.ts deleted file mode 100644 index 0f0feb0..0000000 --- a/src/content/comic.ts +++ /dev/null @@ -1,66 +0,0 @@ -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[]; -} -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) { - super(path); - this.additional = option; - this.pagenum = 0; - } - 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) { - return; - } - const data = (await readAllFromZip(zip, entry)).toString("utf-8"); - this.desc = JSON.parse(data); - if (this.desc === undefined) { - throw new Error(`JSON.parse is returning undefined. ${this.path} desc.json format error`); - } - } - - async createDocumentBody(): Promise { - await this.initDesc(); - const basebody = await super.createDocumentBody(); - this.desc?.title; - 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; - tags.push(`type:${type}`); - return { - ...basebody, - title: this.desc.title, - additional: { - page: this.pagenum, - }, - tags: tags, - }; - } -} -registerContentReferrer(ComicReferrer); diff --git a/src/content/file.ts b/src/content/file.ts deleted file mode 100644 index e7b85e0..0000000 --- a/src/content/file.ts +++ /dev/null @@ -1,90 +0,0 @@ -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; - 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 { - readonly path: string; - // type = type; - static content_type = type; - protected hash: string | undefined; - protected stat: Stats | undefined; - - 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 ret = { - title: name, - basepath: dir, - additional: {}, - content_type: cons.content_type, - filename: base, - tags: [], - content_hash: await this.getHash(), - modified_at: await this.getMtime(), - } as DocumentBody; - return ret; - } - get type(): string { - return cons.content_type; - } - 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) - // 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; - 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}`); - ContstructorTable[s.content_type] = s; -} -export function createContentFile(type: string, path: string, option?: ContentConstructOption) { - const constructorMethod = ContstructorTable[type]; - 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); -} -export function getContentFileConstructor(type: string): ContentFileConstructor | undefined { - const ret = ContstructorTable[type]; - return ret; -} diff --git a/src/database.ts b/src/database.ts deleted file mode 100644 index 285e726..0000000 --- a/src/database.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { existsSync } from "fs"; -import Knex from "knex"; -import { Knex as KnexConfig } from "./config"; -import { get_setting } from "./SettingConfig"; - -export async function connectDB() { - const env = get_setting().mode; - const config = KnexConfig.config[env]; - if (!config.connection) { - throw new Error("connection options required."); - } - const connection = config.connection; - if (typeof connection === "string") { - throw new Error("unknown connection options"); - } - if (typeof connection === "function") { - throw new Error("connection provider not supported..."); - } - 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 { - console.log("try to connect db"); - await knex.raw("select 1 + 1;"); - console.log("connect success"); - } catch (err) { - if (tries < 3) { - tries++; - console.error(`connection fail ${err} retry...`); - continue; - } else { - throw err; - } - } - break; - } - if (init_need) { - console.log("first execute: initialize database..."); - const migrate = await import("../migrations/initial"); - await migrate.up(knex); - } - return knex; -} diff --git a/src/db/doc.ts b/src/db/doc.ts deleted file mode 100644 index ef48898..0000000 --- a/src/db/doc.ts +++ /dev/null @@ -1,223 +0,0 @@ -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; -}; - -class KnexDocumentAccessor implements DocumentAccessor { - knex: Knex; - tagController: TagAccessor; - constructor(knex: Knex) { - this.knex = knex; - this.tagController = createKnexTagController(knex); - } - 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); - return docs; - } - 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 => { - tagCollected.add(x); - }); - }); - const tagCollectPromiseList = []; - const tagController = createKnexTagController(trx); - for (const it of tagCollected) { - const p = tagController.addTag({ name: it }); - tagCollectPromiseList.push(p); - } - await Promise.all(tagCollectPromiseList); - // add for each contents - const ret = []; - for (const content of content_list) { - const { tags, additional, ...rest } = content; - const id_lst = await trx.insert({ - 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"); - } - ret.push(id); - } - return ret; - }); - } - 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"); - const id = id_lst[0]; - for (const it of tags) { - this.tagController.addTag({ name: it }); - } - if (tags.length > 0) { - await this.knex.insert( - 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 }); - 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; - 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); - } - return { - ...first, - tags: ret_tags, - additional: first.additional !== null ? JSON.parse(first.additional) : {}, - }; - } - async findDeleted(content_type: string) { - const s = await this.knex.select("*") - .where({ content_type: content_type }) - .whereNotNull("update_at") - .from("document"); - return s.map(x => ({ - ...x, - tags: [], - additional: {}, - })); - } - async findList(option?: QueryListOption) { - option = option ?? {}; - const allow_tag = option.allow_tag ?? []; - const eager_loading = option.eager_loading ?? true; - const limit = option.limit ?? 20; - const use_offset = option.use_offset ?? false; - const offset = option.offset ?? 0; - const word = option.word; - const content_type = option.content_type; - const cursor = option.cursor; - - const buildquery = () => { - let query = this.knex.select("document.*"); - if (allow_tag.length > 0) { - query = query.from("doc_tag_relation as tags_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("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 (content_type !== undefined) { - query = query.where("content_type", "=", content_type); - } - if (use_offset) { - query = query.offset(offset); - } else { - if (cursor !== undefined) { - query = query.where("id", "<", cursor); - } - } - query = query.limit(limit); - query = query.orderBy("id", "desc"); - return query; - }; - let query = buildquery(); - // 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) { - 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) { - idmap[id].tags.push(tag_name); - } - } 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"); - return true; - } - return false; - } - 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 }) - .into("doc_tag_relation"); - c.tags.push(tag_name); - return true; - } - 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"); - c.tags.push(tag_name); - return true; - } -} -export const createKnexDocumentAccessor = (knex: Knex): DocumentAccessor => { - return new KnexDocumentAccessor(knex); -}; diff --git a/src/db/tag.ts b/src/db/tag.ts deleted file mode 100644 index dae8909..0000000 --- a/src/db/tag.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { Knex } from "knex"; -import { Tag, TagAccessor, TagCount } from "../model/tag"; -import { DBTagContentRelation } from "./doc"; - -type DBTags = { - name: string; - description?: string; -}; - -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"); - return result; - } - async getAllTagList(onlyname?: boolean) { - onlyname = onlyname ?? false; - 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; - return t[0]; - } - async addTag(tag: Tag) { - if (await this.getTagByName(tag.name) === undefined) { - await this.knex.insert({ - 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"); - 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"); - return true; - } - return false; - } -} -export const createKnexTagController = (knex: Knex): TagAccessor => { - return new KnexTagAccessor(knex); -}; diff --git a/src/db/user.ts b/src/db/user.ts deleted file mode 100644 index 1ff2f15..0000000 --- a/src/db/user.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { Knex } from "knex"; -import { IUser, Password, UserAccessor, UserCreateInput } from "../model/user"; - -type PermissionTable = { - username: string; - name: string; -}; -type DBUser = { - 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) { - this.username = username; - this.password = pw; - this.knex = knex; - } - 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 }); - } - 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)) { - const r = await this.knex.insert({ - username: this.username, - name: name, - }).into("permissions"); - return true; - } - return false; - } - async remove(name: string) { - const r = await this.knex - .from("permissions") - .where({ - 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))) { - return undefined; - } - 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"); - 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 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 { - createUser: createUserKnex, - findUser: findUserKenx, - delUser: delUserKnex, - }; -}; diff --git a/src/diff/content_handler.ts b/src/diff/content_handler.ts deleted file mode 100644 index 11ab44c..0000000 --- a/src/diff/content_handler.ts +++ /dev/null @@ -1,120 +0,0 @@ -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. -export class ContentDiffHandler { - /** content file list waiting to add */ - waiting_list: ContentList; - /** deleted contents */ - tombstone: Map; // hash, contentfile - doc_cntr: DocumentAccessor; - /** content type of handle */ - content_type: string; - constructor(cntr: DocumentAccessor, content_type: string) { - this.waiting_list = new ContentList(); - this.tombstone = new Map(); - this.doc_cntr = cntr; - this.content_type = content_type; - } - async setup() { - const deleted = await this.doc_cntr.findDeleted(this.content_type); - for (const it of deleted) { - this.tombstone.set(it.content_hash, it); - } - } - register(diff: IDiffWatcher) { - 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 (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. - if (dbc.length === 0) { - console.log("its not in waiting_list and db!!!: ", cpath); - return; - } - 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. - const cf = this.waiting_list.getByHash(content_hash); - if (cf) { - // 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); - this.waiting_list.deleteByHash(content_hash); - await this.doc_cntr.update({ - id: dbc[0].id, - deleted_at: null, - filename: newFilename, - basepath: newBasepath, - }); - return; - } - // invalidate db and add it to tombstone. - await this.doc_cntr.update({ - id: dbc[0].id, - deleted_at: Date.now(), - }); - this.tombstone.set(dbc[0].content_hash, dbc[0]); - } - private async OnCreated(cpath: string) { - const basepath = dirname(cpath); - const filename = basename(cpath); - console.log("createContentFile", cpath); - const content = createContentFile(this.content_type, cpath); - const hash = await content.getHash(); - const c = this.tombstone.get(hash); - if (c !== undefined) { - await this.doc_cntr.update({ - id: c.id, - deleted_at: null, - filename: filename, - 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); - const prev_filename = basename(prev_path); - const cur_basepath = dirname(cur_path); - 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) { - await this.waiting_list.delete(c); - const content = createContentFile(this.content_type, cur_path); - await this.waiting_list.set(content); - return; - } - const doc = await this.doc_cntr.findByPath(prev_basepath, prev_filename); - - if (doc.length === 0) { - await this.OnCreated(cur_path); - return; - } - - await this.doc_cntr.update({ - ...doc[0], - basepath: cur_basepath, - filename: cur_filename, - }); - } -} diff --git a/src/diff/content_list.ts b/src/diff/content_list.ts deleted file mode 100644 index 7a69cbd..0000000 --- a/src/diff/content_list.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ContentFile } from "../content/mod"; - -export class ContentList { - /** path map */ - private cl: Map; - /** hash map */ - private hl: Map; - - constructor() { - this.cl = new Map(); - this.hl = new Map(); - } - hasByHash(s: string) { - return this.hl.has(s); - } - hasByPath(p: string) { - return this.cl.has(p); - } - getByHash(s: string) { - return this.hl.get(s); - } - getByPath(p: string) { - return this.cl.get(p); - } - async set(c: ContentFile) { - const path = c.path; - const hash = await c.getHash(); - this.cl.set(path, c); - this.hl.set(hash, c); - } - /** delete content file */ - 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) { - const o = this.getByPath(p); - if (o === undefined) return false; - return await this.delete(o); - } - deleteByHash(s: string) { - const o = this.getByHash(s); - if (o === undefined) return false; - let r = true; - r = this.cl.delete(o.path) && r; - r = this.hl.delete(s) && r; - return r; - } - clear() { - this.cl.clear(); - this.hl.clear(); - } - getAll() { - return [...this.cl.values()]; - } -} diff --git a/src/diff/diff.ts b/src/diff/diff.ts deleted file mode 100644 index c290ee3..0000000 --- a/src/diff/diff.ts +++ /dev/null @@ -1,45 +0,0 @@ -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 }; - doc_cntr: 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); - } - this.watching[content_type].register(watcher); - await watcher.setup(this.doc_cntr); - } - async commit(type: string, path: string) { - const list = this.watching[type].waiting_list; - const c = list.getByPath(path); - if (c === undefined) { - throw new Error("path is not exist"); - } - await list.delete(c); - const body = await c.createDocumentBody(); - const id = await this.doc_cntr.add(body); - return id; - } - 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 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(), - })); - } -} diff --git a/src/diff/router.ts b/src/diff/router.ts deleted file mode 100644 index 63dc3ee..0000000 --- a/src/diff/router.ts +++ /dev/null @@ -1,83 +0,0 @@ -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 }; -} - -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.type = "json"; -}; - -type PostAddedBody = { - 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); - } - return false; -} - -export const postAdded = (diffmgr: DiffManager) => async (ctx: Router.IRouterContext, next: Koa.Next) => { - const reqbody = ctx.request.body; - if (!checkPostAddedBody(reqbody)) { - sendError(400, "format exception"); - return; - } - 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"); - return; - } - 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\""); - return; - } - await diffmgr.commitAll(t); - ctx.body = { - ok: true, - }; - ctx.type = "json"; -}; -/* -export const getNotWatched = (diffmgr : DiffManager)=> (ctx:Router.IRouterContext,next:Koa.Next)=>{ - ctx.body = { - added: diffmgr.added.map(content_file_to_return), - deleted: diffmgr.deleted.map(content_file_to_return), - }; - ctx.type = 'json'; -}*/ - -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)); - return ret; -} diff --git a/src/diff/watcher.ts b/src/diff/watcher.ts deleted file mode 100644 index f64604e..0000000 --- a/src/diff/watcher.ts +++ /dev/null @@ -1,25 +0,0 @@ -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 IDiffWatcher extends event.EventEmitter { - 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)); -} diff --git a/src/diff/watcher/ComicConfig.schema.json b/src/diff/watcher/ComicConfig.schema.json deleted file mode 100644 index 50196de..0000000 --- a/src/diff/watcher/ComicConfig.schema.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$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/common_watcher.ts b/src/diff/watcher/common_watcher.ts deleted file mode 100644 index 808645e..0000000 --- a/src/diff/watcher/common_watcher.ts +++ /dev/null @@ -1,44 +0,0 @@ -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; - -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); - } - private _path: string; - private _watcher: FSWatcher; - - constructor(path: string) { - super(); - this._path = path; - 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)); - } - } - }); - } - async setup(cntr: DocumentAccessor): Promise { - await setupHelp(this, this.path, cntr); - } - public get path() { - return this._path; - } - watchClose() { - this._watcher.close(); - } -} diff --git a/src/diff/watcher/compositer.ts b/src/diff/watcher/compositer.ts deleted file mode 100644 index e621ab8..0000000 --- a/src/diff/watcher/compositer.ts +++ /dev/null @@ -1,23 +0,0 @@ -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); - } - emit(event: U, ...arg: Parameters): boolean { - return super.emit(event, ...arg); - } - constructor(refWatchers: IDiffWatcher[]) { - super(); - this.refWatchers = refWatchers; - for (const refWatcher of this.refWatchers) { - linkWatcher(refWatcher, this); - } - } - async setup(cntr: DocumentAccessor): Promise { - await Promise.all(this.refWatchers.map(x => x.setup(cntr))); - } -} diff --git a/src/diff/watcher/recursive_watcher.ts b/src/diff/watcher/recursive_watcher.ts deleted file mode 100644 index 6afadce..0000000 --- a/src/diff/watcher/recursive_watcher.ts +++ /dev/null @@ -1,61 +0,0 @@ -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 = { - /** @default true */ - watchFile?: boolean; - /** @default false */ - watchDir?: boolean; -}; - -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); - } - readonly path: string; - private watcher: FSWatcher; - - constructor(path: string, option: RecursiveWatcherOption = { - watchDir: false, - watchFile: true, - }) { - super(); - this.path = path; - this.watcher = watch(path, { - persistent: true, - ignoreInitial: true, - depth: 100, - }); - option.watchFile ??= true; - if (option.watchFile) { - this.watcher.on("add", path => { - const cpath = path; - // console.log("add ", cpath); - this.emit("create", cpath); - }).on("unlink", path => { - const cpath = path; - // console.log("unlink ", cpath); - this.emit("delete", cpath); - }); - } - if (option.watchDir) { - this.watcher.on("addDir", path => { - const cpath = path; - this.emit("create", cpath); - }).on("unlinkDir", path => { - const cpath = path; - this.emit("delete", cpath); - }); - } - } - async setup(cntr: DocumentAccessor): Promise { - await setupRecursive(this, this.path, cntr); - } -} diff --git a/src/diff/watcher/util.ts b/src/diff/watcher/util.ts deleted file mode 100644 index 8e51d7f..0000000 --- a/src/diff/watcher/util.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { EventEmitter } from "events"; -import { promises } from "fs"; -import { join } from "path"; -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. - let added = cur.filter(x => !initial_filenames.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); - } - for (const it of deleted) { - const cpath = join(basepath, it); - watcher.emit("delete", cpath); - } -} -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 cur = await readdir(basepath); - setupCommon(watcher, basepath, initial_filenames, cur); -} -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)), - ]); -} diff --git a/src/diff/watcher/watcher_filter.ts b/src/diff/watcher/watcher_filter.ts deleted file mode 100644 index 381b807..0000000 --- a/src/diff/watcher/watcher_filter.ts +++ /dev/null @@ -1,46 +0,0 @@ -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); - } - /** - * emit event - * @param event - * @param arg - * @returns `true` if the event had listeners, `false` otherwise. - */ - 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); - } else { - return super.emit("delete", cur); - } - } else { - if (this.filter(cur)) { - return super.emit("create", cur); - } - } - return false; - } else if (!this.filter(arg[0])) { - return false; - } else return super.emit(event, ...arg); - } - constructor(refWatcher: IDiffWatcher, filter: (filename: string) => boolean) { - super(); - this.refWatcher = refWatcher; - this.filter = filter; - linkWatcher(refWatcher, this); - } - setup(cntr: DocumentAccessor): Promise { - return this.refWatcher.setup(cntr); - } -} diff --git a/src/login.ts b/src/login.ts deleted file mode 100644 index a3a1cbf..0000000 --- a/src/login.ts +++ /dev/null @@ -1,285 +0,0 @@ -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 { createKnexUserController } from "./db/mod"; -import { IUser, UserAccessor } from "./model/mod"; -import { sendError } from "./route/error_handler"; -import { get_setting } from "./SettingConfig"; - -type PayloadInfo = { - username: string; - permission: string[]; -}; - -export type UserState = { - 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; -}; -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"; -}; - -export const accessTokenName = "access_token"; -export const refreshTokenName = "refresh_token"; -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); -}; -export const getAdminRefreshTokenValue = () => { - const { jwt_secretkey } = get_setting(); - return publishRefreshToken(jwt_secretkey, "admin", refreshExpiredTime); -}; -const publishAccessToken = ( - secretKey: string, - username: string, - permission: string[], - expiredtime: number, -) => { - const payload = sign( - { - username: username, - permission: permission, - }, - secretKey, - { expiresIn: expiredtime }, - ); - return payload; -}; -const publishRefreshToken = ( - secretKey: string, - username: string, - expiredtime: number, -) => { - const payload = sign( - { username: username }, - secretKey, - { expiresIn: expiredtime }, - ); - return payload; -}; -function setToken( - 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(); - const secretKey = setting.jwt_secretkey; - const body = ctx.request.body; - // 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.", - ); - } - const username = body["username"]; - const password = body["password"]; - // check type - if (typeof username !== "string" || typeof password !== "string") { - return sendError( - 400, - "invalid form : username or password is not string", - ); - } - // if admin login is forbidden? - if (username === "admin" && setting.forbid_remote_admin_login) { - return sendError(403, "forbidden remote admin login"); - } - const user = await userController.findUser(username); - // username not exist - if (user === undefined) return sendError(401, "not authorized"); - // password not matched - if (!user.password.check_password(password)) { - return sendError(401, "not authorized"); - } - // create token - const userPermission = await user.get_permissions(); - const payload = publishAccessToken( - secretKey, - user.username, - userPermission, - accessExpiredTime, - ); - const payload2 = publishRefreshToken( - 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), - }; - 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(); - }; - 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(); - } - 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 { - 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 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"); - } - const user = await cntr.findUser(username); - if (user === undefined) { - return sendError(403, "not authorized"); - } - if (!user.password.check_password(oldpw)) { - return sendError(403, "not authorized"); - } - user.reset_password(newpw); - 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; -} - -export const getAdmin = async (cntr: UserAccessor) => { - 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"; -}; diff --git a/src/model/doc.ts b/src/model/doc.ts deleted file mode 100644 index 7954de0..0000000 --- a/src/model/doc.ts +++ /dev/null @@ -1,129 +0,0 @@ -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 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; -} - -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[]; - /** - * limit of list - * @default 20 - */ - limit?: number; - /** - * use offset if true, otherwise - * @default false - */ - use_offset?: boolean; - /** - * cursor of documents - */ - cursor?: number; - /** - * offset of documents - */ - offset?: number; - /** - * tag eager loading - * @default true - */ - eager_loading?: boolean; - /** - * content type - */ - content_type?: string; -}; - -export interface DocumentAccessor { - /** - * find list by option - * @returns documents list - */ - findList: (option?: QueryListOption) => Promise; - /** - * @returns document if exist, otherwise undefined - */ - 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; - /** - * find deleted content - */ - findDeleted: (content_type: string) => Promise; - /** - * search by in document - */ - search: (search_word: string) => Promise; - /** - * update document except tag. - */ - update: (c: Partial & { id: number }) => Promise; - /** - * add document - */ - add: (c: DocumentBody) => Promise; - /** - * add document list - */ - addList: (content_list: DocumentBody[]) => Promise; - /** - * delete document - * @returns if it exists, return true. - */ - 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; - /** - * @returns if success, return true - */ - delTag: (c: Document, tag_name: string) => Promise; -} diff --git a/src/model/tag.ts b/src/model/tag.ts deleted file mode 100644 index 74d945c..0000000 --- a/src/model/tag.ts +++ /dev/null @@ -1,18 +0,0 @@ -export interface Tag { - readonly name: string; - description?: string; -} - -export interface TagCount { - tag_name: string; - occurs: number; -} - -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; -} diff --git a/src/model/user.ts b/src/model/user.ts deleted file mode 100644 index eb7c14b..0000000 --- a/src/model/user.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { createHmac, randomBytes } from "crypto"; - -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"); - return { - salt: secret, - 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; - this._hash = hash; - this._salt = salt; - } - 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); - } - get salt() { - return this._salt; - } - get hash() { - return this._hash; - } -} - -export interface UserCreateInput { - username: string; - password: string; -} - -export interface IUser { - readonly username: string; - readonly password: Password; - /** - * return user's permission list. - */ - get_permissions(): Promise; - /** - * add permission - * @param name permission name to add - * @returns if `name` doesn't exist, return true - */ - add(name: string): Promise; - /** - * remove permission - * @param name permission name to remove - * @returns if `name` exist, return true - */ - remove(name: string): Promise; - /** - * reset password. - * @param password password to set - */ - reset_password(password: string): Promise; -} - -export interface UserAccessor { - /** - * create user - * @returns if user exist, return undefined - */ - createUser: (input: UserCreateInput) => Promise; - /** - * find user - */ - findUser: (username: string) => Promise; - /** - * remove user - * @returns if user exist, true - */ - delUser: (username: string) => Promise; -} diff --git a/src/permission/permission.ts b/src/permission/permission.ts deleted file mode 100644 index 2d06225..0000000 --- a/src/permission/permission.ts +++ /dev/null @@ -1,58 +0,0 @@ -import Koa from "koa"; -import { UserState } from "../login"; -import { sendError } from "../route/error_handler"; - -export enum Permission { - // ======== - // not implemented - // admin only - /** remove document */ - // removeContent = 'removeContent', - - /** upload document */ - // uploadContent = 'uploadContent', - - /** modify document except base path, filename, content_hash. but admin can modify all. */ - // modifyContent = 'modifyContent', - - /** add tag into document */ - // addTagContent = 'addTagContent', - /** remove tag from document */ - // removeTagContent = 'removeTagContent', - /** ModifyTagInDoc */ - ModifyTag = "ModifyTag", - - /** find documents with query */ - // findAllContent = 'findAllContent', - /** find one document. */ - // findOneContent = 'findOneContent', - /** view content*/ - // viewContent = 'viewContent', - QueryContent = "QueryContent", - - /** modify description about the one tag. */ - 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."); - } 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(); -}; diff --git a/src/route/all.ts b/src/route/all.ts deleted file mode 100644 index 381226b..0000000 --- a/src/route/all.ts +++ /dev/null @@ -1,57 +0,0 @@ -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); - }; -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/:rest(.*)", async (ctx, next) => { - const cont = ctx.params["content_type"] as string; - return await (all_middleware(cont, ctx.params["rest"]))(ctx, next); - }); - } -} diff --git a/src/route/comic.ts b/src/route/comic.ts deleted file mode 100644 index 875fd02..0000000 --- a/src/route/comic.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Context, DefaultContext, DefaultState, Next } from "koa"; -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. - */ - -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; - } -} - -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]; - } -} - -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); - }); - - 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); - }); - } -} - -export default ComicRouter; diff --git a/src/route/contents.ts b/src/route/contents.ts deleted file mode 100644 index 1f737a1..0000000 --- a/src/route/contents.ts +++ /dev/null @@ -1,167 +0,0 @@ -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."); - } - ctx.body = document; - 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."); - } - ctx.body = document.tags; - 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 - || query_content_type instanceof Array - || query_offset instanceof 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"); - } - 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 = { - limit: limit, - allow_tag: allow_tag, - word: word, - cursor: cursor, - eager_loading: true, - offset: offset, - use_offset: use_offset, - content_type: content_type, - }; - 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"]); - - 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"); - } - 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"; -}; -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"; -}; -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."); - } - 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) => { - 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()); - return ret; -}; - -export default getContentRouter; diff --git a/src/route/context.ts b/src/route/context.ts deleted file mode 100644 index 19e44d9..0000000 --- a/src/route/context.ts +++ /dev/null @@ -1,8 +0,0 @@ -export type ContentLocation = { - path: string; - type: string; - additional: object | undefined; -}; -export interface ContentContext { - location: ContentLocation; -} diff --git a/src/route/error_handler.ts b/src/route/error_handler.ts deleted file mode 100644 index 5d3e84f..0000000 --- a/src/route/error_handler.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Context, Next } from "koa"; - -export interface ErrorFormat { - code: number; - message: string; - detail?: string; -} - -class ClientRequestError implements Error { - name: string; - message: string; - stack?: string | undefined; - code: number; - - 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", -}; - -export const error_handler = async (ctx: Context, next: Next) => { - try { - await next(); - } catch (err) { - if (err instanceof ClientRequestError) { - const body: ErrorFormat = { - code: err.code, - message: code_to_message_table[err.code] ?? "", - detail: err.message, - }; - ctx.status = err.code; - ctx.body = body; - } else { - throw err; - } - } -}; - -export const sendError = (code: number, message?: string) => { - throw new ClientRequestError(code, message ?? ""); -}; - -export default error_handler; diff --git a/src/route/tags.ts b/src/route/tags.ts deleted file mode 100644 index aa86d67..0000000 --- a/src/route/tags.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Context, Next } from "koa"; -import Router, { RouterContext } from "koa-router"; -import { TagAccessor } from "../model/tag"; -import { createPermissionCheckMiddleware as PerCheck, Permission } from "../permission/permission"; -import { sendError } from "./error_handler"; - -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"); - } - ctx.body = c; - ctx.type = "json"; - }); - return router; -} diff --git a/src/route/util.ts b/src/route/util.ts deleted file mode 100644 index 8a64a41..0000000 --- a/src/route/util.ts +++ /dev/null @@ -1,37 +0,0 @@ -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; - else return Number.parseInt(s); -} -export function ParseQueryArray(s: string[] | string | undefined) { - s = s ?? []; - const r = s instanceof Array ? s : [s]; - return r.map(x => decodeURIComponent(x)); -} -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; - - if (s === "true") { - value = true; - } else if (s === "false") { - value = false; - } else if (s === undefined) { - value = undefined; - } else return [false, undefined]; - return [true, value]; -} - -export function since_last_modified(ctx: Context, last_modified: Date): boolean { - const con = ctx.get("If-Modified-Since"); - if (con === "") return false; - const mdate = new Date(con); - if (last_modified > mdate) return false; - ctx.status = 304; - return true; -} diff --git a/src/route/video.ts b/src/route/video.ts deleted file mode 100644 index f52499d..0000000 --- a/src/route/video.ts +++ /dev/null @@ -1,67 +0,0 @@ -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}`); - return; - } - ctx.response.type = ext; - const range_text = ctx.request.get("range"); - 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("Accept-Ranges", "bytes"); - if (range_text === "") { - end = 1024 * 512; - end = Math.min(end, stat.size - 1); - if (start > end) { - ctx.status = 416; - return; - } - ctx.status = 200; - ctx.length = stat.size; - let stream = createReadStream(path); - ctx.body = stream; - } else { - const m = range_text.match(/^bytes=(\d+)-(\d*)/); - 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) { - 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. - } -} - -export class VideoRouter extends Router { - constructor() { - super(); - 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); - }); - } -} - -export default VideoRouter; diff --git a/src/search/indexer.ts b/src/search/indexer.ts deleted file mode 100644 index f3c129b..0000000 --- a/src/search/indexer.ts +++ /dev/null @@ -1,12 +0,0 @@ -export interface PaginationOption { - cursor: number; - limit: number; -} - -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[]; -} diff --git a/src/search/tokenizer.ts b/src/search/tokenizer.ts deleted file mode 100644 index cb7f8ad..0000000 --- a/src/search/tokenizer.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface ITokenizer { - tokenize(s: string): string[]; -} - -export class DefaultTokenizer implements ITokenizer { - tokenize(s: string): string[] { - return s.split(" "); - } -} diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index 23590b0..0000000 --- a/src/server.ts +++ /dev/null @@ -1,237 +0,0 @@ -import Koa from "koa"; -import Router from "koa-router"; - -import { connectDB } from "./database"; -import { createDiffRouter, DiffManager } from "./diff/mod"; -import { get_setting, SettingConfig } from "./SettingConfig"; - -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 { createComicWatcher } from "./diff/watcher/comic_watcher"; -import { DocumentAccessor, TagAccessor, UserAccessor } from "./model/mod"; -import { getTagRounter } from "./route/tags"; - -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; - }) { - 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"); - } - private async setup() { - const setting = get_setting(); - const app = this.app; - - if (setting.cli) { - const userAdmin = await getAdmin(this.userController); - if (await isAdminFirst(userAdmin)) { - const rl = createReadlineInterface({ - input: process.stdin, - output: process.stdout, - }); - const pw = await new Promise((res: (data: string) => void, err) => { - rl.question("put admin password :", (data) => { - res(data); - }); - }); - rl.close(); - userAdmin.reset_password(pw); - } - } - app.use(bodyparser()); - app.use(error_handler); - app.use(createUserMiddleWare(this.userController)); - - let diff_router = createDiffRouter(this.diffManger); - this.diffManger.register("comic", createComicWatcher()); - - console.log("setup router"); - - let router = new Router(); - 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()); - - const content_router = getContentRouter(this.documentController); - 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()); - - 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`); - }); - } - 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; - 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"); - } - }); - }; - 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) => { - const docId = Number.parseInt(ctx.params["id"]); - const doc = await this.documentController.findById(docId, true); - let meta; - if (doc === undefined) { - ctx.status = 404; - meta = NotFoundContent(); - } else { - ctx.status = 200; - 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); - }; - 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 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"); - } - } - - 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"); - } - } - 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"); - } 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"); - } - } - start_server() { - let setting = get_setting(); - // 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"); - } - static async createServer() { - const setting = get_setting(); - let db = await connectDB(); - - const app = new ServerApplication({ - userController: createKnexUserController(db), - documentController: createKnexDocumentAccessor(db), - tagController: createKnexTagController(db), - }); - await app.setup(); - return app; - } -} - -export async function create_server() { - return await ServerApplication.createServer(); -} - -export default { create_server }; diff --git a/src/types/db.d.ts b/src/types/db.d.ts deleted file mode 100644 index 4e2fdda..0000000 --- a/src/types/db.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -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; - }; - } -} diff --git a/src/util/configRW.ts b/src/util/configRW.ts deleted file mode 100644 index f0700f9..0000000 --- a/src/util/configRW.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { existsSync, promises as fs, readFileSync, writeFileSync } from "fs"; -import { validate } from "jsonschema"; - -export class ConfigManager { - path: string; - default_config: T; - 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() }; - return this.config; - } - private emptyToDefault(target: T) { - let occur = false; - for (const key in this.default_config) { - if (key === undefined || key in target) { - continue; - } - target[key] = this.default_config[key]; - occur = true; - } - return occur; - } - 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 result = validate(ret, this.schema); - if (!result.valid) { - throw new Error(result.toString()); - } - return ret; - } - 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); - } -} diff --git a/src/util/type_check.ts b/src/util/type_check.ts deleted file mode 100644 index c45a2b1..0000000 --- a/src/util/type_check.ts +++ /dev/null @@ -1,15 +0,0 @@ -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; - defined = defined.trim(); - if (defined.endsWith("[]")) { - if (!(obj[it] instanceof Array)) { - return false; - } - } else if (defined !== typeof obj[it]) { - return false; - } - } - return true; -} diff --git a/src/util/zipwrap.ts b/src/util/zipwrap.ts deleted file mode 100644 index 2de4529..0000000 --- a/src/util/zipwrap.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ZipEntry } 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 { - return new StreamZip.async({ - file: path, - storeEntries: true, - }); -} -export async function entriesByNaturalOrder(zip: ZipAsync) { - const entries = await zip.entries(); - const ret = orderBy(Object.values(entries), v => v.name); - return ret; -} - -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))); - }); -}