feat: implement entity API and reactive store-like modules in frontend

Also does a bunch of other stuff.
This commit is contained in:
Xymorot 2021-01-30 22:34:36 +01:00
parent 55a5047328
commit 4bb6e5c166
221 changed files with 20203 additions and 6051 deletions

View File

@ -11,3 +11,6 @@ charset = utf-8
indent_style = space indent_style = space
indent_size = 2 indent_size = 2
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.handlebars]
insert_final_newline = false

View File

@ -4,6 +4,7 @@
# except these file types # except these file types
!*.js !*.js
!*.ts !*.ts
!*.svelte
# but ignore stuff from .gitignore # but ignore stuff from .gitignore
node_modules node_modules

View File

@ -1,9 +1,9 @@
{ {
"root": true, "root": true,
"plugins": ["@typescript-eslint", "import"], "plugins": ["@typescript-eslint", "import", "promise", "svelte3"],
"extends": ["eslint:recommended", "prettier", "plugin:import/recommended"], "extends": ["eslint:recommended", "prettier", "plugin:import/recommended", "plugin:promise/recommended"],
"parserOptions": { "parserOptions": {
"ecmaVersion": 2019, "ecmaVersion": 2020,
"sourceType": "module" "sourceType": "module"
}, },
"env": { "env": {
@ -91,16 +91,24 @@
"order": "asc" "order": "asc"
} }
} }
] ],
"promise/valid-params": "off",
"promise/always-return": "off"
}, },
"overrides": [ "overrides": [
{
"files": ["src/**/*.*"],
"rules": {
"no-console": "warn"
}
},
{ {
"files": ["**/*.ts"], "files": ["**/*.ts"],
"extends": [ "extends": [
"plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking", "plugin:@typescript-eslint/recommended-requiring-type-checking",
"prettier/@typescript-eslint",
"plugin:import/typescript", "plugin:import/typescript",
"plugin:import/electron" "plugin:import/electron"
], ],
@ -115,7 +123,6 @@
} }
}, },
"rules": { "rules": {
"no-console": "warn",
"no-magic-numbers": "off", "no-magic-numbers": "off",
"no-shadow": "off", "no-shadow": "off",
@ -183,7 +190,7 @@
{ {
"selector": "interface", "selector": "interface",
"format": ["PascalCase"], "format": ["PascalCase"],
"suffix": ["Interface"] "suffix": ["Interface", "Function"]
} }
], ],
"@typescript-eslint/explicit-member-accessibility": "warn", "@typescript-eslint/explicit-member-accessibility": "warn",
@ -198,7 +205,10 @@
"allowNullish": false "allowNullish": false
} }
], ],
"@typescript-eslint/no-shadow": ["warn"] "@typescript-eslint/no-shadow": ["warn"],
"@typescript-eslint/no-extra-semi": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/consistent-type-assertions": ["warn"]
} }
}, },
{ {
@ -207,6 +217,10 @@
"project": "./tsconfig.renderer.json" "project": "./tsconfig.renderer.json"
} }
}, },
{
"files": ["**/*.svelte"],
"processor": "svelte3/svelte3"
},
{ {
"files": ["src/shared/types/**/*"], "files": ["src/shared/types/**/*"],
"rules": { "rules": {

1
.husky/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
_

4
.husky/pre-commit Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run precommit

4
.husky/pre-push Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run prepush

View File

@ -2,7 +2,6 @@
*.* *.*
# except these file types # except these file types
!*.html
!*.handlebars !*.handlebars
!*.json !*.json
!*.yml !*.yml

View File

@ -1,4 +1,4 @@
trailingComma: 'es5' trailingComma: all
tabWidth: 2 tabWidth: 2
semi: true semi: true
singleQuote: true singleQuote: true
@ -6,13 +6,10 @@ quoteProps: consistent
arrowParens: always arrowParens: always
printWidth: 120 printWidth: 120
endOfLine: lf endOfLine: lf
svelteStrictMode: true
overrides: overrides:
- files: '*.svelte'
options:
parser: html
- files: '*.handlebars'
options:
parser: html
- files: '*.drawio' - files: '*.drawio'
options: options:
parser: html parser: xml

View File

@ -11,6 +11,8 @@ Nothing in this project is set in stone (even this rule). I might not know what
1. [Formatting and Linters](CONTRIBUTING.md#formatting-and-linters) 1. [Formatting and Linters](CONTRIBUTING.md#formatting-and-linters)
1. [Testing](CONTRIBUTING.md#testing) 1. [Testing](CONTRIBUTING.md#testing)
1. [Updating](CONTRIBUTING.md#updating-dependencies) 1. [Updating](CONTRIBUTING.md#updating-dependencies)
1. [Frontend](CONTRIBUTING.md#frontend)
1. [Translation](CONTRIBUTING.md#translation)
1. [Design](CONTRIBUTING.md#design) 1. [Design](CONTRIBUTING.md#design)
## Currently Most Wanted ## Currently Most Wanted
@ -45,6 +47,10 @@ The [ideas folder](workspace/ideas) is a collection of possible features for the
I could use another developer (experienced or not) to take a look or two at this code base and voice their opinion. I could use another developer (experienced or not) to take a look or two at this code base and voice their opinion.
#### LF: TypeScript Magician
Do a search over the whole code-base for `@ts-ignore` or ` as [A-Z]` (regex for type assertions) and try to fix my typings. Good luck!
## Communication ## Communication
Every contribution is valuable. Open a ticket [here](https://git.fuwafuwa.moe/Xymorot/RenaiApp/issues) or write me an [email](mailto:xymorot@mailbox.org). Every contribution is valuable. Open a ticket [here](https://git.fuwafuwa.moe/Xymorot/RenaiApp/issues) or write me an [email](mailto:xymorot@mailbox.org).
@ -141,7 +147,7 @@ The point of these libraries is to make uniform code possible over various edito
The testing framework of choice is [Mocha](https://mochajs.org/). Call `npm run test` to run all tests. Tests are written in typescript and need to be transpiled before testing. The testing framework of choice is [Mocha](https://mochajs.org/). Call `npm run test` to run all tests. Tests are written in typescript and need to be transpiled before testing.
- assertion is (mainly) done by [Chai](https://www.chaijs.com/)should not be compromised - assertion is (mainly) done by [Chai](https://www.chaijs.com/)
- spies, stubs and mocks are provided by [Sinon.JS](https://sinonjs.org/) - spies, stubs and mocks are provided by [Sinon.JS](https://sinonjs.org/)
- HTTP server mocking is done by [nock](https://github.com/nock/nock) - HTTP server mocking is done by [nock](https://github.com/nock/nock)
- property based testing is made possible by [fast-check](https://github.com/dubzzz/fast-check) - property based testing is made possible by [fast-check](https://github.com/dubzzz/fast-check)
@ -182,6 +188,15 @@ This also means potentially updating:
- the `@types/node` package - the `@types/node` package
- the electron version in the `target` field of the [webpack config](scripts/webpack.config.js) - the electron version in the `target` field of the [webpack config](scripts/webpack.config.js)
- the `compilerOptions.target` fields of the [main](tsconfig.json) and [renderer](tsconfig.renderer.json) typescript configuration files - the `compilerOptions.target` fields of the [main](tsconfig.json) and [renderer](tsconfig.renderer.json) typescript configuration files
- the `parserOptions.ecmaVersion` field in the [ESLint configuration](.eslintrc.json)
Also might be worth checking out https://github.com/electron/electron/issues/21457 if the project can be converted to transpile to ES Modules.
### Frontend
#### Translation
With the help of [intl-messageformat](https://www.npmjs.com/package/intl-messageformat) frontend message can be defined in the [ICU Message Format](https://unicode-org.github.io/icu/userguide/format_parse/messages/). The corresponding code can be found [here](src/shared/services/translation/t.ts). I don't think a language other than English will be necessary, but the formatting features are nice anyway.
## Design ## Design

View File

@ -1,5 +0,0 @@
declare module '*.svelte' {
import { SvelteComponentTyped } from 'svelte';
// noinspection JSUnusedGlobalSymbols -- because it is used
export class Component extends SvelteComponentTyped {}
}

19913
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,9 +11,11 @@
}, },
"main": "src/main.js", "main": "src/main.js",
"engines": { "engines": {
"node": "12" "node": "14",
"npm": "7"
}, },
"scripts": { "scripts": {
"prepare": "husky install",
"postinstall": "npm run rebuild", "postinstall": "npm run rebuild",
"postupdate": "npm run rebuild", "postupdate": "npm run rebuild",
"start": "electron . --enable-logging --env=dev", "start": "electron . --enable-logging --env=dev",
@ -45,69 +47,68 @@
"prepush": "npm run build && npm run coverage" "prepush": "npm run build && npm run coverage"
}, },
"dependencies": { "dependencies": {
"better-sqlite3": "^7.1.2", "better-sqlite3": "^7.4.0",
"electron-squirrel-startup": "^1.0.0", "electron-squirrel-startup": "^1.0.0",
"fs-extra": "^9.1.0", "fs-extra": "^10.0.0",
"inversify": "^5.0.1", "intl-messageformat": "^9.6.14",
"inversify": "^5.1.1",
"minimist": "^1.2.5", "minimist": "^1.2.5",
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"typeorm": "^0.2.30", "typeorm": "^0.2.32",
"uuid": "^8.3.2" "uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.54", "@electron-forge/cli": "^6.0.0-beta.55",
"@electron-forge/maker-squirrel": "^6.0.0-beta.54", "@electron-forge/maker-squirrel": "^6.0.0-beta.55",
"@prettier/plugin-xml": "^0.13.1",
"@types/better-sqlite3": "^5.4.1", "@types/better-sqlite3": "^5.4.1",
"@types/chai": "^4.2.14", "@types/chai": "^4.2.18",
"@types/chai-fs": "^2.0.2", "@types/chai-fs": "^2.0.2",
"@types/deep-equal-in-any-order": "^1.0.1", "@types/deep-equal-in-any-order": "^1.0.1",
"@types/fs-extra": "^9.0.6", "@types/fs-extra": "^9.0.11",
"@types/glob": "^7.1.3", "@types/glob": "^7.1.3",
"@types/minimist": "^1.2.1", "@types/minimist": "^1.2.1",
"@types/mocha": "^8.2.0", "@types/mocha": "^8.2.2",
"@types/node": "^12.19.15", "@types/node": "^14.17.0",
"@types/sinon": "^9.0.10", "@types/sinon": "^10.0.0",
"@types/uuid": "^8.3.0", "@types/uuid": "^8.3.0",
"@types/webpack": "^4.41.26", "@types/webpack": "^5.28.0",
"@typescript-eslint/eslint-plugin": "^4.14.0", "@typescript-eslint/eslint-plugin": "^4.24.0",
"@typescript-eslint/parser": "^4.14.0", "@typescript-eslint/parser": "^4.24.0",
"chai": "^4.2.0", "chai": "^4.3.4",
"chai-fs": "^2.0.0", "chai-fs": "^2.0.0",
"chokidar": "^3.5.1", "chokidar": "^3.5.1",
"concurrently": "^5.3.0", "concurrently": "^6.1.0",
"deep-equal-in-any-order": "^1.0.28", "deep-equal-in-any-order": "^1.1.4",
"electron": "^10.3.0", "electron": "^13.0.1",
"electron-mocha": "^10.0.0", "electron-mocha": "^10.0.0",
"electron-rebuild": "^2.3.4", "electron-rebuild": "^2.3.5",
"eslint": "^7.18.0", "eslint": "^7.26.0",
"eslint-config-prettier": "^7.2.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.22.1", "eslint-plugin-import": "^2.23.2",
"fast-check": "^2.12.0", "eslint-plugin-promise": "^5.1.0",
"glob": "^7.1.6", "eslint-plugin-svelte3": "^3.2.0",
"handlebars": "^4.7.6", "fast-check": "^2.14.0",
"husky": "^4.3.8", "glob": "^7.1.7",
"lodash": "^4.17.20", "handlebars": "^4.7.7",
"mocha": "^8.2.1", "husky": "^6.0.0",
"nock": "^13.0.6", "lodash": "^4.17.21",
"mocha": "^8.4.0",
"nock": "^13.0.11",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"prettier": "^2.2.1", "prettier": "^2.3.0",
"sinon": "^9.2.4", "prettier-plugin-svelte": "^2.3.0",
"svelte": "^3.31.2", "sinon": "^11.1.1",
"svelte-loader": "^3.0.0", "svelte": "^3.38.2",
"ts-loader": "^8.0.14", "svelte-loader": "^3.1.1",
"typescript": "^4.1.3", "ts-loader": "^9.2.0",
"webpack": "^5.17.0", "typescript": "^4.2.4",
"webpack-cli": "^4.4.0" "webpack": "^5.37.1",
"webpack-cli": "^4.7.0"
}, },
"repository": "https://git.fuwafuwa.moe/Xymorot/RenaiApp", "repository": "https://git.fuwafuwa.moe/Xymorot/RenaiApp",
"bugs": "https://git.fuwafuwa.moe/Xymorot/RenaiApp/issues", "bugs": "https://git.fuwafuwa.moe/Xymorot/RenaiApp/issues",
"config": { "config": {
"forge": "scripts/forge.config.js" "forge": "scripts/forge.config.js"
},
"husky": {
"hooks": {
"pre-commit": "npm run precommit",
"pre-push": "npm run prepush"
}
} }
} }

View File

@ -39,7 +39,7 @@ const name = packageJson.productName;
if (name !== 'Renai') { if (name !== 'Renai') {
throw new TypeError( throw new TypeError(
`The product name "${name}" in package.json is not "Renai"! Change it before building but do not commit it.` `The product name "${name}" in package.json is not "Renai"! Change it before building but do not commit it.`,
); );
} }

View File

@ -3,7 +3,7 @@ const path = require('path');
module.exports = { module.exports = {
mode: 'production', mode: 'production',
entry: path.resolve(__dirname, '../src/renderer.ts'), entry: path.resolve(__dirname, '../src/renderer.ts'),
target: 'electron10-renderer', target: 'electron13-renderer',
output: { output: {
path: path.resolve(__dirname, '../frontend'), path: path.resolve(__dirname, '../frontend'),
filename: 'bundle.js', filename: 'bundle.js',
@ -21,14 +21,9 @@ module.exports = {
configFile: path.resolve('tsconfig.renderer.json'), configFile: path.resolve('tsconfig.renderer.json'),
}, },
}, },
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
},
], ],
}, },
resolve: { resolve: {
extensions: ['.ts', '.js', '.mjs'], extensions: ['.ts', '.js'],
}, },
}; };

View File

@ -1,23 +1,22 @@
/* eslint-disable import/order -- this is the entry point for the application and some things have to happen before others */ /* eslint-disable import/order -- this is the entry point for the application and some things have to happen before others */
import { container } from './main/core/container';
import './main/core/install';
import { app } from 'electron'; import { app } from 'electron';
import { container, Service } from './main/core/container';
import './main/core/controller';
import { isDev } from './main/core/env'; import { isDev } from './main/core/env';
import type { AppWindowInterface } from './main/modules/app-window/app-window-interface'; import './main/core/install';
/** /**
* have a read: https://github.com/nodejs/node/issues/20392, over 100 comments as of 2020-07-26 * have a read: https://github.com/nodejs/node/issues/20392, over 100 comments as of 2020-07-26
* https://nodejs.org/api/process.html#process_event_unhandledrejection * https://nodejs.org/api/process.html#process_event_unhandledrejection
*/ */
process.on('unhandledRejection', (reason) => { process.on('unhandledRejection', (reason) => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
void logger.fatal(`Unhandled Rejection, see ${logger.getExceptionsLogFile()}`); void logger.fatal(`Unhandled Rejection, see ${logger.getExceptionsLogFile()}`);
throw reason; throw reason;
}); });
process.on('uncaughtException', (error) => { process.on('uncaughtException', (error) => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
void logger.exception(error); void logger.exception(error);
if (isDev()) { if (isDev()) {
// eslint-disable-next-line no-console -- only for development purposes // eslint-disable-next-line no-console -- only for development purposes
@ -26,7 +25,7 @@ process.on('uncaughtException', (error) => {
}); });
async function createWindow(): Promise<void> { async function createWindow(): Promise<void> {
const appWindowMain: AppWindowInterface = container.get('app-window-main'); const appWindowMain = container.get(Service.APP_WINDOW_MAIN);
await appWindowMain.open(); await appWindowMain.open();
appWindowMain.window?.on('closed', () => { appWindowMain.window?.on('closed', () => {
@ -49,7 +48,7 @@ app.on('window-all-closed', () => {
app.on('activate', () => { app.on('activate', () => {
// On OS X it"s common to re-create a window in the app when the // On OS X it"s common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open. // dock icon is clicked and there are no other windows open.
const appWindowMain: AppWindowInterface = container.get('app-window-main'); const appWindowMain = container.get(Service.APP_WINDOW_MAIN);
if (appWindowMain.isClosed()) { if (appWindowMain.isClosed()) {
void createWindow(); void createWindow();
} }

View File

@ -1,40 +1,185 @@
import 'reflect-metadata'; import 'reflect-metadata';
import { Container, interfaces } from 'inversify'; import { Container, interfaces } from 'inversify';
import type { AppWindowInterface } from '../modules/app-window/app-window-interface';
import { MainAppWindow } from '../modules/app-window/main-app-window'; import { MainAppWindow } from '../modules/app-window/main-app-window';
import { Dialog } from '../modules/dialog/dialog'; import { Dialog } from '../modules/dialog/dialog';
import { I18nTranslator } from '../modules/i18n/i18n-translator'; import type { DialogInterface } from '../modules/dialog/dialog-interface';
import { Logger } from '../modules/logger/logger'; import { Logger } from '../modules/logger/logger';
import { NhentaiApi } from '../modules/nhentai/nhentai-api'; import { NhentaiApi } from '../modules/nhentai/nhentai-api';
import '../modules/nhentai/nhentai-ipc-controller';
import { NhentaiAppWindow } from '../modules/nhentai/nhentai-app-window'; import { NhentaiAppWindow } from '../modules/nhentai/nhentai-app-window';
import type { NhentaiAppWindowInterface } from '../modules/nhentai/nhentai-app-window-interface';
import { NhentaiSourceGetter } from '../modules/nhentai/nhentai-source-getter'; import { NhentaiSourceGetter } from '../modules/nhentai/nhentai-source-getter';
import { AuthorNameSerializer } from '../modules/serialization/serializers/author-name-serializer';
import { AuthorRoleNameSerializer } from '../modules/serialization/serializers/author-role-name-serializer';
import { AuthorRoleSerializer } from '../modules/serialization/serializers/author-role-serializer';
import { AuthorSerializer } from '../modules/serialization/serializers/author-serializer';
import { CharacterTagSerializer } from '../modules/serialization/serializers/character-tag-serializer';
import { CollectionNameSerializer } from '../modules/serialization/serializers/collection-name-serializer';
import { CollectionPartSerializer } from '../modules/serialization/serializers/collection-part-serializer';
import { CollectionSerializer } from '../modules/serialization/serializers/collection-serializer';
import { CopySerializer } from '../modules/serialization/serializers/copy-serializer';
import { InteractionTagSerializer } from '../modules/serialization/serializers/interaction-tag-serializer';
import { LanguageSerializer } from '../modules/serialization/serializers/language-serializer';
import { SiteNameSerializer } from '../modules/serialization/serializers/site-name-serializer';
import { SiteSerializer } from '../modules/serialization/serializers/site-serializer';
import { SourceSerializer } from '../modules/serialization/serializers/source-serializer';
import { TagNameSerializer } from '../modules/serialization/serializers/tag-name-serializer';
import { TagSerializer } from '../modules/serialization/serializers/tag-serializer';
import { TransformationSerializer } from '../modules/serialization/serializers/transformation-serializer';
import { TransformationTypeNameSerializer } from '../modules/serialization/serializers/transformation-type-name-serializer';
import { TransformationTypeSerializer } from '../modules/serialization/serializers/transformation-type-serializer';
import { WorkAuthorSerializer } from '../modules/serialization/serializers/work-author-serializer';
import { WorkCharacterNameSerializer } from '../modules/serialization/serializers/work-character-name-serializer';
import { WorkCharacterSerializer } from '../modules/serialization/serializers/work-character-serializer';
import { WorkNameSerializer } from '../modules/serialization/serializers/work-name-serializer';
import { WorkSerializer } from '../modules/serialization/serializers/work-serializer';
import { WorkTagSerializer } from '../modules/serialization/serializers/work-tag-serializer';
import { WorldCharacterNameSerializer } from '../modules/serialization/serializers/world-character-name-serializer';
import { WorldCharacterSerializer } from '../modules/serialization/serializers/world-character-serializer';
import { WorldNameSerializer } from '../modules/serialization/serializers/world-name-serializer';
import { WorldSerializer } from '../modules/serialization/serializers/world-serializer';
import type { SourceGetterInterface } from '../modules/source/source-getter-interface';
import { Store } from '../modules/store/store'; import { Store } from '../modules/store/store';
import '../modules/entity-api/entity-api-ipc-controller';
import BindingToSyntax = interfaces.BindingToSyntax; import BindingToSyntax = interfaces.BindingToSyntax;
export const enum Service {
LOGGER = 'LOGGER',
AUTHOR_SERIALIZER = 'AUTHOR_SERIALIZER',
AUTHOR_NAME_SERIALIZER = 'AUTHOR_NAME_SERIALIZER',
AUTHOR_ROLE_SERIALIZER = 'AUTHOR_ROLE_SERIALIZER',
AUTHOR_ROLE_NAME_SERIALIZER = 'AUTHOR_ROLE_NAME_SERIALIZER',
CHARACTER_TAG_SERIALIZER = 'CHARACTER_TAG_SERIALIZER',
COLLECTION_SERIALIZER = 'COLLECTION_SERIALIZER',
COLLECTION_NAME_SERIALIZER = 'COLLECTION_NAME_SERIALIZER',
COLLECTION_PART_SERIALIZER = 'COLLECTION_PART_SERIALIZER',
COPY_SERIALIZER = 'COPY_SERIALIZER',
INTERACTION_TAG_SERIALIZER = 'INTERACTION_TAG_SERIALIZER',
LANGUAGE_SERIALIZER = 'LANGUAGE_SERIALIZER',
SITE_SERIALIZER = 'SITE_SERIALIZER',
SITE_NAME_SERIALIZER = 'SITE_NAME_SERIALIZER',
SOURCE_SERIALIZER = 'SOURCE_SERIALIZER',
TAG_SERIALIZER = 'TAG_SERIALIZER',
TAG_NAME_SERIALIZER = 'TAG_NAME_SERIALIZER',
TRANSFORMATION_SERIALIZER = 'TRANSFORMATION_SERIALIZER',
TRANSFORMATION_TYPE_SERIALIZER = 'TRANSFORMATION_TYPE_SERIALIZER',
TRANSFORMATION_TYPE_NAME_SERIALIZER = 'TRANSFORMATION_TYPE_NAME_SERIALIZER',
WORK_AUTHOR_SERIALIZER = 'WORK_AUTHOR_SERIALIZER',
WORK_CHARACTER_SERIALIZER = 'WORK_CHARACTER_SERIALIZER',
WORK_CHARACTER_NAME_SERIALIZER = 'WORK_CHARACTER_NAME_SERIALIZER',
WORK_SERIALIZER = 'WORK_SERIALIZER',
WORK_NAME_SERIALIZER = 'WORK_NAME_SERIALIZER',
WORK_TAG_SERIALIZER = 'WORK_TAG_SERIALIZER',
WORLD_SERIALIZER = 'WORLD_SERIALIZER',
WORLD_NAME_SERIALIZER = 'WORLD_NAME_SERIALIZER',
WORLD_CHARACTER_SERIALIZER = 'WORLD_CHARACTER_SERIALIZER',
WORLD_CHARACTER_NAME_SERIALIZER = 'WORLD_CHARACTER_NAME_SERIALIZER',
DIALOG = 'DIALOG',
STORE = 'STORE',
NHENTAI_APP_WINDOW = 'NHENTAI_APP_WINDOW',
NHENTAI_API = 'NHENTAI_API',
NHENTAI_SOURCE_GETTER = 'NHENTAI_SOURCE_GETTER',
APP_WINDOW_MAIN = 'APP_WINDOW_MAIN',
}
type ServiceType = {
[Service.LOGGER]: LoggerInterface;
[Service.AUTHOR_SERIALIZER]: AuthorSerializer;
[Service.AUTHOR_NAME_SERIALIZER]: AuthorNameSerializer;
[Service.AUTHOR_ROLE_SERIALIZER]: AuthorRoleSerializer;
[Service.AUTHOR_ROLE_NAME_SERIALIZER]: AuthorRoleNameSerializer;
[Service.CHARACTER_TAG_SERIALIZER]: CharacterTagSerializer;
[Service.COLLECTION_SERIALIZER]: CollectionSerializer;
[Service.COLLECTION_NAME_SERIALIZER]: CollectionNameSerializer;
[Service.COLLECTION_PART_SERIALIZER]: CollectionPartSerializer;
[Service.COPY_SERIALIZER]: CopySerializer;
[Service.INTERACTION_TAG_SERIALIZER]: InteractionTagSerializer;
[Service.LANGUAGE_SERIALIZER]: LanguageSerializer;
[Service.SITE_SERIALIZER]: SiteSerializer;
[Service.SITE_NAME_SERIALIZER]: SiteNameSerializer;
[Service.SOURCE_SERIALIZER]: SourceSerializer;
[Service.TAG_SERIALIZER]: TagSerializer;
[Service.TAG_NAME_SERIALIZER]: TagNameSerializer;
[Service.TRANSFORMATION_SERIALIZER]: TransformationSerializer;
[Service.TRANSFORMATION_TYPE_SERIALIZER]: TransformationTypeSerializer;
[Service.TRANSFORMATION_TYPE_NAME_SERIALIZER]: TransformationTypeNameSerializer;
[Service.WORK_AUTHOR_SERIALIZER]: WorkAuthorSerializer;
[Service.WORK_CHARACTER_SERIALIZER]: WorkCharacterSerializer;
[Service.WORK_CHARACTER_NAME_SERIALIZER]: WorkCharacterNameSerializer;
[Service.WORK_SERIALIZER]: WorkSerializer;
[Service.WORK_NAME_SERIALIZER]: WorkNameSerializer;
[Service.WORK_TAG_SERIALIZER]: WorkTagSerializer;
[Service.WORLD_SERIALIZER]: WorldSerializer;
[Service.WORLD_NAME_SERIALIZER]: WorldNameSerializer;
[Service.WORLD_CHARACTER_SERIALIZER]: WorldCharacterSerializer;
[Service.WORLD_CHARACTER_NAME_SERIALIZER]: WorldCharacterNameSerializer;
[Service.DIALOG]: DialogInterface;
[Service.STORE]: StoreInterface;
[Service.NHENTAI_APP_WINDOW]: NhentaiAppWindowInterface;
[Service.NHENTAI_API]: NhentaiApiInterface;
[Service.NHENTAI_SOURCE_GETTER]: SourceGetterInterface;
[Service.APP_WINDOW_MAIN]: AppWindowInterface;
};
export const container = { export const container = {
original: new Container({ defaultScope: 'Singleton', skipBaseClassChecks: true }), original: new Container({ defaultScope: 'Singleton', skipBaseClassChecks: true }),
bind<T>(key: string): BindingToSyntax<T> { bind<S extends Service>(key: S): BindingToSyntax<ServiceType[S]> {
return this.original.bind<T>(Symbol.for(key)); return this.original.bind<ServiceType[S]>(Symbol.for(key));
}, },
unbind(key: string): void { unbind(key: Service): void {
return this.original.unbind(Symbol.for(key)); return this.original.unbind(Symbol.for(key));
}, },
get<T>(key: string): T { get<S extends Service>(key: S): ServiceType[S] {
return this.original.get<T>(Symbol.for(key)); return this.original.get<ServiceType[S]>(Symbol.for(key));
}, },
}; };
container.bind('logger').to(Logger); container.bind(Service.LOGGER).to(Logger);
container.bind('i18n-translator').to(I18nTranslator); container.bind(Service.AUTHOR_SERIALIZER).to(AuthorSerializer);
container.bind(Service.AUTHOR_NAME_SERIALIZER).to(AuthorNameSerializer);
container.bind(Service.AUTHOR_ROLE_SERIALIZER).to(AuthorRoleSerializer);
container.bind(Service.AUTHOR_ROLE_NAME_SERIALIZER).to(AuthorRoleNameSerializer);
container.bind(Service.CHARACTER_TAG_SERIALIZER).to(CharacterTagSerializer);
container.bind(Service.COLLECTION_SERIALIZER).to(CollectionSerializer);
container.bind(Service.COLLECTION_NAME_SERIALIZER).to(CollectionNameSerializer);
container.bind(Service.COLLECTION_PART_SERIALIZER).to(CollectionPartSerializer);
container.bind(Service.COPY_SERIALIZER).to(CopySerializer);
container.bind(Service.INTERACTION_TAG_SERIALIZER).to(InteractionTagSerializer);
container.bind(Service.LANGUAGE_SERIALIZER).to(LanguageSerializer);
container.bind(Service.SITE_SERIALIZER).to(SiteSerializer);
container.bind(Service.SITE_NAME_SERIALIZER).to(SiteNameSerializer);
container.bind(Service.SOURCE_SERIALIZER).to(SourceSerializer);
container.bind(Service.TAG_SERIALIZER).to(TagSerializer);
container.bind(Service.TAG_NAME_SERIALIZER).to(TagNameSerializer);
container.bind(Service.TRANSFORMATION_SERIALIZER).to(TransformationSerializer);
container.bind(Service.TRANSFORMATION_TYPE_SERIALIZER).to(TransformationTypeSerializer);
container.bind(Service.TRANSFORMATION_TYPE_NAME_SERIALIZER).to(TransformationTypeNameSerializer);
container.bind(Service.WORK_AUTHOR_SERIALIZER).to(WorkAuthorSerializer);
container.bind(Service.WORK_CHARACTER_SERIALIZER).to(WorkCharacterSerializer);
container.bind(Service.WORK_CHARACTER_NAME_SERIALIZER).to(WorkCharacterNameSerializer);
container.bind(Service.WORK_SERIALIZER).to(WorkSerializer);
container.bind(Service.WORK_NAME_SERIALIZER).to(WorkNameSerializer);
container.bind(Service.WORK_TAG_SERIALIZER).to(WorkTagSerializer);
container.bind(Service.WORLD_SERIALIZER).to(WorldSerializer);
container.bind(Service.WORLD_NAME_SERIALIZER).to(WorldNameSerializer);
container.bind(Service.WORLD_CHARACTER_SERIALIZER).to(WorldCharacterSerializer);
container.bind(Service.WORLD_CHARACTER_NAME_SERIALIZER).to(WorldCharacterNameSerializer);
container.bind('dialog').to(Dialog); container.bind(Service.DIALOG).to(Dialog);
container.bind('store').to(Store); container.bind(Service.STORE).to(Store);
container.bind('nhentai-app-window').to(NhentaiAppWindow); container.bind(Service.NHENTAI_APP_WINDOW).to(NhentaiAppWindow);
container.bind('nhentai-api').to(NhentaiApi); container.bind(Service.NHENTAI_API).to(NhentaiApi);
container.bind('nhentai-source-getter').to(NhentaiSourceGetter); container.bind(Service.NHENTAI_SOURCE_GETTER).to(NhentaiSourceGetter);
container.bind('app-window-main').to(MainAppWindow); container.bind(Service.APP_WINDOW_MAIN).to(MainAppWindow);

View File

@ -0,0 +1,2 @@
import '../modules/nhentai/nhentai-ipc-controller';
import '../modules/entity-api/entity-api-ipc-controller';

View File

@ -1,5 +1,5 @@
import path from 'path'; import path from 'path';
import { Connection, createConnection as ormCreateConnection } from 'typeorm'; import { Connection, createConnection as ormCreateConnection, EntityManager, EntityTarget, Repository } from 'typeorm';
import type { BetterSqlite3ConnectionOptions } from 'typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions'; import type { BetterSqlite3ConnectionOptions } from 'typeorm/driver/better-sqlite3/BetterSqlite3ConnectionOptions';
import { appPath } from './app-path'; import { appPath } from './app-path';
@ -50,5 +50,16 @@ const connections = {
}; };
export function getConnection(database: Database): Promise<Connection> { export function getConnection(database: Database): Promise<Connection> {
return Promise.resolve(connections[database]); return connections[database];
}
export function getManager(database: Database): Promise<EntityManager> {
return getConnection(database).then((c) => c.manager);
}
export function getRepository<T>(
target: EntityTarget<T>,
database: Database = Database.LIBRARY,
): Promise<Repository<T>> {
return getManager(database).then((m) => m.getRepository(target));
} }

View File

@ -1,5 +1,6 @@
import { inject as inversifyInject } from 'inversify'; import { inject as inversifyInject } from 'inversify';
import type { Service } from './container';
export function inject(key: string): ReturnType<typeof inversifyInject> { export function inject(key: Service): ReturnType<typeof inversifyInject> {
return inversifyInject(Symbol.for(key)); return inversifyInject(Symbol.for(key));
} }

View File

@ -9,6 +9,6 @@ export const maxValue = Number.MAX_SAFE_INTEGER;
export function PercentCheck(column: string): ClassDecorator & PropertyDecorator { export function PercentCheck(column: string): ClassDecorator & PropertyDecorator {
return Check( return Check(
`${column} needs to be between ${minValue} and ${maxValue}`, `${column} needs to be between ${minValue} and ${maxValue}`,
`${column} >= ${minValue} AND ${column} <= ${maxValue}` `${column} >= ${minValue} AND ${column} <= ${maxValue}`,
); );
} }

View File

@ -16,6 +16,6 @@ export class Author implements AuthorEntityInterface {
@OneToMany(() => AuthorName, (authorName: AuthorNameEntityInterface) => authorName.entity) @OneToMany(() => AuthorName, (authorName: AuthorNameEntityInterface) => authorName.entity)
public names!: Promise<AuthorNameEntityInterface[]>; public names!: Promise<AuthorNameEntityInterface[]>;
@OneToMany(() => WorkAuthor, (workAuthor: WorkAuthorEntityInterface) => workAuthor.author, {}) @OneToMany(() => WorkAuthor, (workAuthor: WorkAuthorEntityInterface) => workAuthor.author)
public workAuthors!: Promise<WorkAuthorEntityInterface[]>; public workAuthors!: Promise<WorkAuthorEntityInterface[]>;
} }

View File

@ -4,8 +4,12 @@ import { Work } from './work';
@Entity() @Entity()
export class Language implements LanguageEntityInterface { export class Language implements LanguageEntityInterface {
@PrimaryColumn() @PrimaryColumn()
public code!: string; public readonly code!: LangCode;
@ManyToMany(() => Work, (work: WorkEntityInterface) => work.languages) @ManyToMany(() => Work, (work: WorkEntityInterface) => work.languages)
public works!: Promise<WorkEntityInterface[]>; public works!: Promise<WorkEntityInterface[]>;
public get id(): LangCode {
return this.code;
}
} }

View File

@ -13,7 +13,7 @@ export class TransformationTypeName implements TransformationTypeNameEntityInter
nullable: false, nullable: false,
onDelete: 'CASCADE', onDelete: 'CASCADE',
onUpdate: 'CASCADE', onUpdate: 'CASCADE',
} },
) )
public entity!: Promise<TransformationTypeEntityInterface>; public entity!: Promise<TransformationTypeEntityInterface>;

View File

@ -16,7 +16,6 @@ export class TransformationType implements TransformationTypeEntityInterface {
@OneToMany( @OneToMany(
() => TransformationTypeName, () => TransformationTypeName,
(transformationTypeName: TransformationTypeNameEntityInterface) => transformationTypeName.entity, (transformationTypeName: TransformationTypeNameEntityInterface) => transformationTypeName.entity,
{}
) )
public names!: Promise<TransformationTypeNameEntityInterface[]>; public names!: Promise<TransformationTypeNameEntityInterface[]>;

View File

@ -21,7 +21,7 @@ export class Transformation implements TransformationEntityInterface {
nullable: false, nullable: false,
onDelete: 'RESTRICT', onDelete: 'RESTRICT',
onUpdate: 'CASCADE', onUpdate: 'CASCADE',
} },
) )
public type!: Promise<TransformationTypeEntityInterface>; public type!: Promise<TransformationTypeEntityInterface>;

View File

@ -25,7 +25,7 @@ export class Work implements WorkEntityInterface {
@OneToMany(() => WorkName, (workName: WorkNameEntityInterface) => workName.entity) @OneToMany(() => WorkName, (workName: WorkNameEntityInterface) => workName.entity)
public names!: Promise<WorkNameEntityInterface[]>; public names!: Promise<WorkNameEntityInterface[]>;
@OneToMany(() => Copy, (copy: CopyEntityInterface) => copy.original, {}) @OneToMany(() => Copy, (copy: CopyEntityInterface) => copy.original)
public copies!: Promise<CopyEntityInterface[]>; public copies!: Promise<CopyEntityInterface[]>;
@OneToMany(() => Transformation, (transformation: TransformationEntityInterface) => transformation.byWork) @OneToMany(() => Transformation, (transformation: TransformationEntityInterface) => transformation.byWork)

View File

@ -16,7 +16,7 @@ export class WorldCharacter implements WorldCharacterEntityInterface {
@OneToMany( @OneToMany(
() => WorldCharacterName, () => WorldCharacterName,
(worldCharacterName: WorldCharacterNameEntityInterface) => worldCharacterName.entity (worldCharacterName: WorldCharacterNameEntityInterface) => worldCharacterName.entity,
) )
public names!: Promise<WorldCharacterNameEntityInterface[]>; public names!: Promise<WorldCharacterNameEntityInterface[]>;

File diff suppressed because it is too large Load Diff

View File

@ -65,7 +65,7 @@ export class addLanguages1611508644004 implements MigrationInterface {
await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HIRI_MOTU}')`); await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HIRI_MOTU}')`);
await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HUNGARIAN}')`); await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HUNGARIAN}')`);
await queryRunner.query( await queryRunner.query(
`INSERT INTO language VALUES('${LangCode.INTERLINGUA_INTERNATIONAL_AUXILIARY_LANGUAGE_ASSOCIATION}')` `INSERT INTO language VALUES('${LangCode.INTERLINGUA_INTERNATIONAL_AUXILIARY_LANGUAGE_ASSOCIATION}')`,
); );
await queryRunner.query(`INSERT INTO language VALUES('${LangCode.INDONESIAN}')`); await queryRunner.query(`INSERT INTO language VALUES('${LangCode.INDONESIAN}')`);
await queryRunner.query(`INSERT INTO language VALUES('${LangCode.INTERLINGUE_OCCIDENTAL}')`); await queryRunner.query(`INSERT INTO language VALUES('${LangCode.INTERLINGUE_OCCIDENTAL}')`);
@ -124,7 +124,7 @@ export class addLanguages1611508644004 implements MigrationInterface {
await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OCCITAN}')`); await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OCCITAN}')`);
await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OJIBWA}')`); await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OJIBWA}')`);
await queryRunner.query( await queryRunner.query(
`INSERT INTO language VALUES('${LangCode.CHURCH_SLAVIC_OLD_SLAVONIC_CHURCH_SLAVONIC_OLD_BULGARIAN_OLD_CHURCH_SLAVONIC}')` `INSERT INTO language VALUES('${LangCode.CHURCH_SLAVIC_OLD_SLAVONIC_CHURCH_SLAVONIC_OLD_BULGARIAN_OLD_CHURCH_SLAVONIC}')`,
); );
await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OROMO}')`); await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OROMO}')`);
await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ORIYA}')`); await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ORIYA}')`);

View File

@ -6,11 +6,11 @@ export class initialMigration1587511036078 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query( await queryRunner.query(
`CREATE TABLE "store_value" ("key" varchar PRIMARY KEY NOT NULL, "value" text NOT NULL)`, `CREATE TABLE "store_value" ("key" varchar PRIMARY KEY NOT NULL, "value" text NOT NULL)`,
undefined undefined,
); );
await queryRunner.query( await queryRunner.query(
`CREATE TABLE "query-result-cache" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar, "time" bigint NOT NULL, "duration" integer NOT NULL, "query" text NOT NULL, "result" text NOT NULL)`, `CREATE TABLE "query-result-cache" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "identifier" varchar, "time" bigint NOT NULL, "duration" integer NOT NULL, "query" text NOT NULL, "result" text NOT NULL)`,
undefined undefined,
); );
} }

View File

@ -142,7 +142,7 @@ export abstract class AppWindow implements AppWindowInterface {
}) })
.catch(() => { .catch(() => {
void this.logger.warning( void this.logger.warning(
`Could not get the of the presumed HTMLTimeElement with the selector '${selector}'.` `Could not get the of the presumed HTMLTimeElement with the selector '${selector}'.`,
); );
resolve(undefined); resolve(undefined);
}); });

View File

@ -8,7 +8,7 @@ export abstract class FileAppWindow extends AppWindow {
logger: LoggerInterface, logger: LoggerInterface,
uri: string, uri: string,
options: BrowserWindowConstructorOptions = {}, options: BrowserWindowConstructorOptions = {},
loadOptions: LoadFileOptions = {} loadOptions: LoadFileOptions = {},
) { ) {
super(logger, uri, options); super(logger, uri, options);
this.loadOptions = loadOptions; this.loadOptions = loadOptions;

View File

@ -1,13 +1,15 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { Service } from '../../core/container';
import { inject } from '../../core/inject'; import { inject } from '../../core/inject';
import { FileAppWindow } from './file-app-window'; import { FileAppWindow } from './file-app-window';
@injectable() @injectable()
export class MainAppWindow extends FileAppWindow { export class MainAppWindow extends FileAppWindow {
public constructor(@inject('logger') logger: LoggerInterface) { public constructor(@inject(Service.LOGGER) logger: LoggerInterface) {
super(logger, 'frontend/index.html', { super(logger, 'frontend/index.html', {
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false,
}, },
}); });
} }

View File

@ -14,7 +14,7 @@ export abstract class SiteAppWindow extends UrlAppWindow implements SiteAppWindo
logger: LoggerInterface, logger: LoggerInterface,
uri: string, uri: string,
options: BrowserWindowConstructorOptions = {}, options: BrowserWindowConstructorOptions = {},
loadOptions: LoadURLOptions = {} loadOptions: LoadURLOptions = {},
) { ) {
super(logger, uri, options, loadOptions); super(logger, uri, options, loadOptions);
this.windowLock = new SimpleMutex(); this.windowLock = new SimpleMutex();

View File

@ -16,6 +16,6 @@ interface UrlAppWindowInterface extends AppWindowInterface {
loadUrlSafe( loadUrlSafe(
url: string, url: string,
readyCheck?: (webContents: WebContents) => Promise<boolean>, readyCheck?: (webContents: WebContents) => Promise<boolean>,
options?: LoadURLOptions options?: LoadURLOptions,
): Promise<void>; ): Promise<void>;
} }

View File

@ -35,7 +35,7 @@ export abstract class UrlAppWindow extends AppWindow implements UrlAppWindowInte
logger: LoggerInterface, logger: LoggerInterface,
uri: string, uri: string,
options: BrowserWindowConstructorOptions = {}, options: BrowserWindowConstructorOptions = {},
loadOptions: LoadURLOptions = {} loadOptions: LoadURLOptions = {},
) { ) {
super(logger, uri, { super(logger, uri, {
...options, ...options,
@ -78,36 +78,38 @@ export abstract class UrlAppWindow extends AppWindow implements UrlAppWindowInte
public async loadUrlSafe( public async loadUrlSafe(
url: string, url: string,
readyCheck?: (webContents: WebContents) => Promise<boolean>, readyCheck?: (webContents: WebContents) => Promise<boolean>,
options?: LoadURLOptions options?: LoadURLOptions,
): Promise<void> { ): Promise<void> {
return this.loadWait.then(async () => { await this.loadWait;
let failedLoad = true; let failedLoad = true;
while (failedLoad) { while (failedLoad) {
await this.loadUrl(url, options).then((httpResponseCode) => { const httpResponseCode = await this.loadUrl(url, options);
failedLoad = HttpCode.BAD_REQUEST <= httpResponseCode; failedLoad = HttpCode.BAD_REQUEST <= httpResponseCode;
if (HttpCode.TOO_MANY_REQUESTS === httpResponseCode) { switch (httpResponseCode) {
// go slower case HttpCode.NOT_FOUND:
this.loadWaitTime += this.loadWaitTimeStep; throw new Error(`Loading ${url} gave the unrecoverable HTTP status code ${httpResponseCode}.`);
// but go faster again after a time case HttpCode.TOO_MANY_REQUESTS:
clearTimeout(this.loadWaitTimeStepResetTimeout); // go slower
this.loadWaitTimeStepResetTimeout = setTimeout(() => { this.loadWaitTime += this.loadWaitTimeStep;
this.loadWaitTime = 0; // but go faster again after a time
}, this.loadWaitTimeResetTimeoutTime); clearTimeout(this.loadWaitTimeStepResetTimeout);
} this.loadWaitTimeStepResetTimeout = setTimeout(() => {
}); this.loadWaitTime = 0;
if (failedLoad) { }, this.loadWaitTimeResetTimeoutTime);
await promisify(setTimeout)(this.waitInterval); break;
}
} }
this.loadWait = promisify(setTimeout)(this.loadWaitTime); if (failedLoad) {
if (readyCheck) { await promisify(setTimeout)(this.waitInterval);
let isReady = await readyCheck(this.getWindow().webContents);
do {
await promisify(setTimeout)(Milliseconds.TEN);
isReady = await readyCheck(this.getWindow().webContents);
} while (!isReady);
} }
}); }
this.loadWait = promisify(setTimeout)(this.loadWaitTime);
if (readyCheck) {
let isReady = await readyCheck(this.getWindow().webContents);
do {
await promisify(setTimeout)(Milliseconds.TEN);
isReady = await readyCheck(this.getWindow().webContents);
} while (!isReady);
}
} }
/** /**

View File

@ -9,7 +9,7 @@ export abstract class CloudflareSiteAppWindow extends SiteAppWindow {
const onDidNavigate: (event: Event, url: string, httpResponseCode: number) => void = async ( const onDidNavigate: (event: Event, url: string, httpResponseCode: number) => void = async (
event, event,
navigationUrl, navigationUrl,
httpResponseCode httpResponseCode,
) => { ) => {
if (!(await isCloudFlareSite(this.getWindow().webContents))) { if (!(await isCloudFlareSite(this.getWindow().webContents))) {
this.getWindow().webContents.removeListener('did-navigate', onDidNavigate); this.getWindow().webContents.removeListener('did-navigate', onDidNavigate);

View File

@ -15,12 +15,12 @@ export const cloudflareSiteCsp: ContentSecurityPolicy = {
export function humanInteractionRequired(webContents: WebContents): Promise<boolean> { export function humanInteractionRequired(webContents: WebContents): Promise<boolean> {
return webContents.executeJavaScript( return webContents.executeJavaScript(
"[...document.querySelectorAll('iframe')].map(iframe => (new URL(iframe.src)).hostname.match(/hcaptcha/)).some(e => e)" "[...document.querySelectorAll('iframe')].map(iframe => (new URL(iframe.src)).hostname.match(/hcaptcha/)).some(e => e)",
) as Promise<boolean>; ) as Promise<boolean>;
} }
export function isCloudFlareSite(webContents: WebContents): Promise<boolean> { export function isCloudFlareSite(webContents: WebContents): Promise<boolean> {
return webContents.executeJavaScript( return webContents.executeJavaScript(
"!!document.querySelector('.cf-browser-verification, #cf-content, #cf-wrapper') || !!window._cf_translation" "!!document.querySelector('.cf-browser-verification, #cf-content, #cf-wrapper') || !!window._cf_translation",
) as Promise<boolean>; ) as Promise<boolean>;
} }

View File

@ -1,6 +1,6 @@
export function dateObjectToString(date: Date): string { export function dateObjectToString(date: Date): string {
return `${date.getUTCFullYear()}-${`${date.getUTCMonth()}`.padStart(2, '0')}-${`${date.getUTCDate()}`.padStart( return `${date.getUTCFullYear()}-${`${date.getUTCMonth()}`.padStart(2, '0')}-${`${date.getUTCDate()}`.padStart(
2, 2,
'0' '0',
)}`; )}`;
} }

View File

@ -1,20 +1,14 @@
import { dialog, OpenDialogOptions } from 'electron'; import { dialog, OpenDialogOptions } from 'electron';
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { inject } from '../../core/inject'; import { t } from '../../../shared/services/translation/t';
import type { DialogInterface } from './dialog-interface'; import type { DialogInterface } from './dialog-interface';
@injectable() @injectable()
export class Dialog implements DialogInterface { export class Dialog implements DialogInterface {
private readonly translator: I18nTranslatorInterface;
public constructor(@inject('i18n-translator') translator: I18nTranslatorInterface) {
this.translator = translator;
}
public selectFolder(options: OpenDialogOptions): ReturnType<typeof dialog.showOpenDialog> { public selectFolder(options: OpenDialogOptions): ReturnType<typeof dialog.showOpenDialog> {
return dialog.showOpenDialog({ return dialog.showOpenDialog({
...{ ...{
title: this.translator.t('select a folder'), title: t('imperatives.dialog.select_folder'),
}, },
...options, ...options,
...{ ...{

View File

@ -0,0 +1,265 @@
import type { EntityTarget } from 'typeorm';
import { container, Service } from '../../core/container';
import { Database, getManager } from '../../core/database';
import { Author } from '../../entities/library/author';
import { AuthorName } from '../../entities/library/author-name';
import { AuthorRole } from '../../entities/library/author-role';
import { AuthorRoleName } from '../../entities/library/author-role-name';
import { CharacterTag } from '../../entities/library/character-tag';
import { Collection } from '../../entities/library/collection';
import { CollectionName } from '../../entities/library/collection-name';
import { CollectionPart } from '../../entities/library/collection-part';
import { Copy } from '../../entities/library/copy';
import { InteractionTag } from '../../entities/library/interaction-tag';
import { Site } from '../../entities/library/site';
import { SiteName } from '../../entities/library/site-name';
import { Source } from '../../entities/library/source';
import { Tag } from '../../entities/library/tag';
import { TagName } from '../../entities/library/tag-name';
import { Transformation } from '../../entities/library/transformation';
import { TransformationType } from '../../entities/library/transformation-type';
import { TransformationTypeName } from '../../entities/library/transformation-type-name';
import { Work } from '../../entities/library/work';
import { WorkAuthor } from '../../entities/library/work-author';
import { WorkCharacter } from '../../entities/library/work-character';
import { WorkCharacterName } from '../../entities/library/work-character-name';
import { WorkName } from '../../entities/library/work-name';
import { WorkTag } from '../../entities/library/work-tag';
import { World } from '../../entities/library/world';
import { WorldCharacter } from '../../entities/library/world-character';
import { WorldCharacterName } from '../../entities/library/world-character-name';
import { WorldName } from '../../entities/library/world-name';
import { ipcServer } from '../ipc/ipc-server';
import type { Serializer } from '../serialization/serializer';
type CreateSerializedType = {
[IpcChannel.ENTITY_CREATE_AUTHOR]: AuthorSerializedInterface;
[IpcChannel.ENTITY_CREATE_AUTHOR_NAME]: AuthorNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_AUTHOR_ROLE]: AuthorRoleSerializedInterface;
[IpcChannel.ENTITY_CREATE_AUTHOR_ROLE_NAME]: AuthorRoleNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_CHARACTER_TAG]: CharacterTagSerializedInterface;
[IpcChannel.ENTITY_CREATE_COLLECTION]: CollectionSerializedInterface;
[IpcChannel.ENTITY_CREATE_COLLECTION_NAME]: CollectionNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_COLLECTION_PART]: CollectionPartSerializedInterface;
[IpcChannel.ENTITY_CREATE_COPY]: CopySerializedInterface;
[IpcChannel.ENTITY_CREATE_INTERACTION_TAG]: InteractionTagSerializedInterface;
[IpcChannel.ENTITY_CREATE_SITE]: SiteSerializedInterface;
[IpcChannel.ENTITY_CREATE_SITE_NAME]: SiteNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_SOURCE]: SourceSerializedInterface;
[IpcChannel.ENTITY_CREATE_TAG]: TagSerializedInterface;
[IpcChannel.ENTITY_CREATE_TAG_NAME]: TagNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_TRANSFORMATION]: TransformationSerializedInterface;
[IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE]: TransformationTypeSerializedInterface;
[IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE_NAME]: TransformationTypeNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORK_AUTHOR]: WorkAuthorSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORK_CHARACTER]: WorkCharacterSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORK_CHARACTER_NAME]: WorkCharacterNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORK]: WorkSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORK_NAME]: WorkNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORK_TAG]: WorkTagSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORLD]: WorldSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORLD_NAME]: WorldNameSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORLD_CHARACTER]: WorldCharacterSerializedInterface;
[IpcChannel.ENTITY_CREATE_WORLD_CHARACTER_NAME]: WorldCharacterNameSerializedInterface;
};
type CreateEntityType = {
[IpcChannel.ENTITY_CREATE_AUTHOR]: Author;
[IpcChannel.ENTITY_CREATE_AUTHOR_NAME]: AuthorName;
[IpcChannel.ENTITY_CREATE_AUTHOR_ROLE]: AuthorRole;
[IpcChannel.ENTITY_CREATE_AUTHOR_ROLE_NAME]: AuthorRoleName;
[IpcChannel.ENTITY_CREATE_CHARACTER_TAG]: CharacterTag;
[IpcChannel.ENTITY_CREATE_COLLECTION]: Collection;
[IpcChannel.ENTITY_CREATE_COLLECTION_NAME]: CollectionName;
[IpcChannel.ENTITY_CREATE_COLLECTION_PART]: CollectionPart;
[IpcChannel.ENTITY_CREATE_COPY]: Copy;
[IpcChannel.ENTITY_CREATE_INTERACTION_TAG]: InteractionTag;
[IpcChannel.ENTITY_CREATE_SITE]: Site;
[IpcChannel.ENTITY_CREATE_SITE_NAME]: SiteName;
[IpcChannel.ENTITY_CREATE_SOURCE]: Source;
[IpcChannel.ENTITY_CREATE_TAG]: Tag;
[IpcChannel.ENTITY_CREATE_TAG_NAME]: TagName;
[IpcChannel.ENTITY_CREATE_TRANSFORMATION]: Transformation;
[IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE]: TransformationType;
[IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE_NAME]: TransformationTypeName;
[IpcChannel.ENTITY_CREATE_WORK_AUTHOR]: WorkAuthor;
[IpcChannel.ENTITY_CREATE_WORK_CHARACTER]: WorkCharacter;
[IpcChannel.ENTITY_CREATE_WORK_CHARACTER_NAME]: WorkCharacterName;
[IpcChannel.ENTITY_CREATE_WORK]: Work;
[IpcChannel.ENTITY_CREATE_WORK_NAME]: WorkName;
[IpcChannel.ENTITY_CREATE_WORK_TAG]: WorkTag;
[IpcChannel.ENTITY_CREATE_WORLD]: World;
[IpcChannel.ENTITY_CREATE_WORLD_NAME]: WorldName;
[IpcChannel.ENTITY_CREATE_WORLD_CHARACTER]: WorldCharacter;
[IpcChannel.ENTITY_CREATE_WORLD_CHARACTER_NAME]: WorldCharacterName;
};
async function create<T extends keyof CreateSerializedType>(
partial: Partial<CreateSerializedType[T]>,
entityTarget: EntityTarget<CreateEntityType[T]>,
serializer: Serializer<CreateEntityType[T], CreateSerializedType[T]>,
): Promise<CreateSerializedType[T]> {
const manager = await getManager(Database.LIBRARY);
const entity = manager.create(entityTarget, serializer.deserialize(partial) as DeepPartial<CreateEntityType[T]>);
return serializer.serialize(await manager.save(entity));
}
ipcServer.answer(IpcChannel.ENTITY_CREATE_AUTHOR, (partial) =>
create<IpcChannel.ENTITY_CREATE_AUTHOR>(partial, Author, container.get(Service.AUTHOR_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_AUTHOR_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_AUTHOR_NAME>(partial, AuthorName, container.get(Service.AUTHOR_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_AUTHOR_ROLE, (partial) =>
create<IpcChannel.ENTITY_CREATE_AUTHOR_ROLE>(partial, AuthorRole, container.get(Service.AUTHOR_ROLE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_AUTHOR_ROLE_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_AUTHOR_ROLE_NAME>(
partial,
AuthorRoleName,
container.get(Service.AUTHOR_ROLE_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_CHARACTER_TAG, (partial) =>
create<IpcChannel.ENTITY_CREATE_CHARACTER_TAG>(
partial,
CharacterTag,
container.get(Service.CHARACTER_TAG_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_COLLECTION, (partial) =>
create<IpcChannel.ENTITY_CREATE_COLLECTION>(partial, Collection, container.get(Service.COLLECTION_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_COLLECTION_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_COLLECTION_NAME>(
partial,
CollectionName,
container.get(Service.COLLECTION_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_COLLECTION_PART, (partial) =>
create<IpcChannel.ENTITY_CREATE_COLLECTION_PART>(
partial,
CollectionPart,
container.get(Service.COLLECTION_PART_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_COPY, (partial) =>
create<IpcChannel.ENTITY_CREATE_COPY>(partial, Copy, container.get(Service.COPY_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_INTERACTION_TAG, (partial) =>
create<IpcChannel.ENTITY_CREATE_INTERACTION_TAG>(
partial,
InteractionTag,
container.get(Service.INTERACTION_TAG_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_SITE, (partial) =>
create<IpcChannel.ENTITY_CREATE_SITE>(partial, Site, container.get(Service.SITE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_SITE_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_SITE_NAME>(partial, SiteName, container.get(Service.SITE_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_SOURCE, (partial) =>
create<IpcChannel.ENTITY_CREATE_SOURCE>(partial, Source, container.get(Service.SOURCE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_TAG, (partial) =>
create<IpcChannel.ENTITY_CREATE_TAG>(partial, Tag, container.get(Service.TAG_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_TAG_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_TAG_NAME>(partial, TagName, container.get(Service.TAG_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_TRANSFORMATION, (partial) =>
create<IpcChannel.ENTITY_CREATE_TRANSFORMATION>(
partial,
Transformation,
container.get(Service.TRANSFORMATION_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE, (partial) =>
create<IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE>(
partial,
TransformationType,
container.get(Service.TRANSFORMATION_TYPE_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE_NAME>(
partial,
TransformationTypeName,
container.get(Service.TRANSFORMATION_TYPE_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORK_AUTHOR, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORK_AUTHOR>(partial, WorkAuthor, container.get(Service.WORK_AUTHOR_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORK_CHARACTER, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORK_CHARACTER>(
partial,
WorkCharacter,
container.get(Service.WORK_CHARACTER_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORK_CHARACTER_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORK_CHARACTER_NAME>(
partial,
WorkCharacterName,
container.get(Service.WORK_CHARACTER_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORK, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORK>(partial, Work, container.get(Service.WORK_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORK_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORK_NAME>(partial, WorkName, container.get(Service.WORK_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORK_TAG, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORK_TAG>(partial, WorkTag, container.get(Service.WORK_TAG_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORLD, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORLD>(partial, World, container.get(Service.WORLD_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORLD_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORLD_NAME>(partial, WorldName, container.get(Service.WORLD_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORLD_CHARACTER, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORLD_CHARACTER>(
partial,
WorldCharacter,
container.get(Service.WORLD_CHARACTER_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_CREATE_WORLD_CHARACTER_NAME, (partial) =>
create<IpcChannel.ENTITY_CREATE_WORLD_CHARACTER_NAME>(
partial,
WorldCharacterName,
container.get(Service.WORLD_CHARACTER_NAME_SERIALIZER),
),
);

View File

@ -0,0 +1,92 @@
import type { EntityTarget } from 'typeorm';
import { getRepository } from '../../core/database';
import { Author } from '../../entities/library/author';
import { AuthorName } from '../../entities/library/author-name';
import { AuthorRole } from '../../entities/library/author-role';
import { AuthorRoleName } from '../../entities/library/author-role-name';
import { CharacterTag } from '../../entities/library/character-tag';
import { Collection } from '../../entities/library/collection';
import { CollectionName } from '../../entities/library/collection-name';
import { CollectionPart } from '../../entities/library/collection-part';
import { Copy } from '../../entities/library/copy';
import { InteractionTag } from '../../entities/library/interaction-tag';
import { Site } from '../../entities/library/site';
import { SiteName } from '../../entities/library/site-name';
import { Source } from '../../entities/library/source';
import { Tag } from '../../entities/library/tag';
import { TagName } from '../../entities/library/tag-name';
import { Transformation } from '../../entities/library/transformation';
import { TransformationType } from '../../entities/library/transformation-type';
import { TransformationTypeName } from '../../entities/library/transformation-type-name';
import { Work } from '../../entities/library/work';
import { WorkAuthor } from '../../entities/library/work-author';
import { WorkCharacter } from '../../entities/library/work-character';
import { WorkCharacterName } from '../../entities/library/work-character-name';
import { WorkName } from '../../entities/library/work-name';
import { WorkTag } from '../../entities/library/work-tag';
import { World } from '../../entities/library/world';
import { WorldCharacter } from '../../entities/library/world-character';
import { WorldCharacterName } from '../../entities/library/world-character-name';
import { WorldName } from '../../entities/library/world-name';
import { ipcServer } from '../ipc/ipc-server';
async function del(id: number, entityTarget: EntityTarget<unknown>): Promise<void> {
const repository = await getRepository(entityTarget);
await repository.delete(id);
}
ipcServer.answer(IpcChannel.ENTITY_DELETE_AUTHOR, async (id) => del(id, Author));
ipcServer.answer(IpcChannel.ENTITY_DELETE_AUTHOR_NAME, async (id) => del(id, AuthorName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_AUTHOR_ROLE, async (id) => del(id, AuthorRole));
ipcServer.answer(IpcChannel.ENTITY_DELETE_AUTHOR_ROLE_NAME, async (id) => del(id, AuthorRoleName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_CHARACTER_TAG, async (id) => del(id, CharacterTag));
ipcServer.answer(IpcChannel.ENTITY_DELETE_COLLECTION, async (id) => del(id, Collection));
ipcServer.answer(IpcChannel.ENTITY_DELETE_COLLECTION_NAME, async (id) => del(id, CollectionName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_COLLECTION_PART, async (id) => del(id, CollectionPart));
ipcServer.answer(IpcChannel.ENTITY_DELETE_COPY, async (id) => del(id, Copy));
ipcServer.answer(IpcChannel.ENTITY_DELETE_INTERACTION_TAG, async (id) => del(id, InteractionTag));
ipcServer.answer(IpcChannel.ENTITY_DELETE_SITE, async (id) => del(id, Site));
ipcServer.answer(IpcChannel.ENTITY_DELETE_SITE_NAME, async (id) => del(id, SiteName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_SOURCE, async (id) => del(id, Source));
ipcServer.answer(IpcChannel.ENTITY_DELETE_TAG, async (id) => del(id, Tag));
ipcServer.answer(IpcChannel.ENTITY_DELETE_TAG_NAME, async (id) => del(id, TagName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_TRANSFORMATION, async (id) => del(id, Transformation));
ipcServer.answer(IpcChannel.ENTITY_DELETE_TRANSFORMATION_TYPE, async (id) => del(id, TransformationType));
ipcServer.answer(IpcChannel.ENTITY_DELETE_TRANSFORMATION_TYPE_NAME, async (id) => del(id, TransformationTypeName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORK_AUTHOR, async (id) => del(id, WorkAuthor));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORK_CHARACTER, async (id) => del(id, WorkCharacter));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORK_CHARACTER_NAME, async (id) => del(id, WorkCharacterName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORK, async (id) => del(id, Work));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORK_NAME, async (id) => del(id, WorkName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORK_TAG, async (id) => del(id, WorkTag));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORLD, async (id) => del(id, World));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORLD_NAME, async (id) => del(id, WorldName));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORLD_CHARACTER, async (id) => del(id, WorldCharacter));
ipcServer.answer(IpcChannel.ENTITY_DELETE_WORLD_CHARACTER_NAME, async (id) => del(id, WorldCharacterName));

View File

@ -1,20 +1,4 @@
import { workSerializer } from '../../../shared/services/serialization/serializers/work'; import './create-controller';
import { Database, getConnection } from '../../core/database'; import './read-controller';
import { Work } from '../../entities/library/work'; import './update-controller';
import { answer } from '../ipc/annotations/answer'; import './delete-controller';
export class EntityApiIpcController implements IpcController {
private constructor() {}
@answer(IpcChannel.ENTITY_GET_WORK)
public async getWork({ id }: { id: number }): Promise<WorkSerializedInterface> {
const connection = await getConnection(Database.LIBRARY);
const work = await connection.manager.getRepository(Work).findOneOrFail(id);
return workSerializer.serialize(work);
}
public get(): EntityApiIpcController {
return new EntityApiIpcController();
}
}

View File

@ -0,0 +1,274 @@
import type { EntityTarget } from 'typeorm';
import { container, Service } from '../../core/container';
import { getRepository } from '../../core/database';
import { Author } from '../../entities/library/author';
import { AuthorName } from '../../entities/library/author-name';
import { AuthorRole } from '../../entities/library/author-role';
import { AuthorRoleName } from '../../entities/library/author-role-name';
import { CharacterTag } from '../../entities/library/character-tag';
import { Collection } from '../../entities/library/collection';
import { CollectionName } from '../../entities/library/collection-name';
import { CollectionPart } from '../../entities/library/collection-part';
import { Copy } from '../../entities/library/copy';
import { InteractionTag } from '../../entities/library/interaction-tag';
import { Language } from '../../entities/library/language';
import { Site } from '../../entities/library/site';
import { SiteName } from '../../entities/library/site-name';
import { Source } from '../../entities/library/source';
import { Tag } from '../../entities/library/tag';
import { TagName } from '../../entities/library/tag-name';
import { Transformation } from '../../entities/library/transformation';
import { TransformationType } from '../../entities/library/transformation-type';
import { TransformationTypeName } from '../../entities/library/transformation-type-name';
import { Work } from '../../entities/library/work';
import { WorkAuthor } from '../../entities/library/work-author';
import { WorkCharacter } from '../../entities/library/work-character';
import { WorkCharacterName } from '../../entities/library/work-character-name';
import { WorkName } from '../../entities/library/work-name';
import { WorkTag } from '../../entities/library/work-tag';
import { World } from '../../entities/library/world';
import { WorldCharacter } from '../../entities/library/world-character';
import { WorldCharacterName } from '../../entities/library/world-character-name';
import { WorldName } from '../../entities/library/world-name';
import { ipcServer } from '../ipc/ipc-server';
import type { Serializer } from '../serialization/serializer';
type ReadSerializedType = {
[IpcChannel.ENTITY_READ_AUTHOR]: AuthorSerializedInterface;
[IpcChannel.ENTITY_READ_AUTHOR_NAME]: AuthorNameSerializedInterface;
[IpcChannel.ENTITY_READ_AUTHOR_ROLE]: AuthorRoleSerializedInterface;
[IpcChannel.ENTITY_READ_AUTHOR_ROLE_NAME]: AuthorRoleNameSerializedInterface;
[IpcChannel.ENTITY_READ_CHARACTER_TAG]: CharacterTagSerializedInterface;
[IpcChannel.ENTITY_READ_COLLECTION]: CollectionSerializedInterface;
[IpcChannel.ENTITY_READ_COLLECTION_NAME]: CollectionNameSerializedInterface;
[IpcChannel.ENTITY_READ_COLLECTION_PART]: CollectionPartSerializedInterface;
[IpcChannel.ENTITY_READ_COPY]: CopySerializedInterface;
[IpcChannel.ENTITY_READ_INTERACTION_TAG]: InteractionTagSerializedInterface;
[IpcChannel.ENTITY_READ_LANGUAGE]: LanguageSerializedInterface;
[IpcChannel.ENTITY_READ_SITE]: SiteSerializedInterface;
[IpcChannel.ENTITY_READ_SITE_NAME]: SiteNameSerializedInterface;
[IpcChannel.ENTITY_READ_SOURCE]: SourceSerializedInterface;
[IpcChannel.ENTITY_READ_TAG]: TagSerializedInterface;
[IpcChannel.ENTITY_READ_TAG_NAME]: TagNameSerializedInterface;
[IpcChannel.ENTITY_READ_TRANSFORMATION]: TransformationSerializedInterface;
[IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE]: TransformationTypeSerializedInterface;
[IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE_NAME]: TransformationTypeNameSerializedInterface;
[IpcChannel.ENTITY_READ_WORK_AUTHOR]: WorkAuthorSerializedInterface;
[IpcChannel.ENTITY_READ_WORK_CHARACTER]: WorkCharacterSerializedInterface;
[IpcChannel.ENTITY_READ_WORK_CHARACTER_NAME]: WorkCharacterNameSerializedInterface;
[IpcChannel.ENTITY_READ_WORK]: WorkSerializedInterface;
[IpcChannel.ENTITY_READ_WORK_NAME]: WorkNameSerializedInterface;
[IpcChannel.ENTITY_READ_WORK_TAG]: WorkTagSerializedInterface;
[IpcChannel.ENTITY_READ_WORLD]: WorldSerializedInterface;
[IpcChannel.ENTITY_READ_WORLD_NAME]: WorldNameSerializedInterface;
[IpcChannel.ENTITY_READ_WORLD_CHARACTER]: WorldCharacterSerializedInterface;
[IpcChannel.ENTITY_READ_WORLD_CHARACTER_NAME]: WorldCharacterNameSerializedInterface;
};
type ReadIdType = {
[IpcChannel.ENTITY_READ_AUTHOR]: number;
[IpcChannel.ENTITY_READ_AUTHOR_NAME]: number;
[IpcChannel.ENTITY_READ_AUTHOR_ROLE]: number;
[IpcChannel.ENTITY_READ_AUTHOR_ROLE_NAME]: number;
[IpcChannel.ENTITY_READ_CHARACTER_TAG]: number;
[IpcChannel.ENTITY_READ_COLLECTION]: number;
[IpcChannel.ENTITY_READ_COLLECTION_NAME]: number;
[IpcChannel.ENTITY_READ_COLLECTION_PART]: number;
[IpcChannel.ENTITY_READ_COPY]: number;
[IpcChannel.ENTITY_READ_INTERACTION_TAG]: number;
[IpcChannel.ENTITY_READ_LANGUAGE]: string;
[IpcChannel.ENTITY_READ_SITE]: number;
[IpcChannel.ENTITY_READ_SITE_NAME]: number;
[IpcChannel.ENTITY_READ_SOURCE]: number;
[IpcChannel.ENTITY_READ_TAG]: number;
[IpcChannel.ENTITY_READ_TAG_NAME]: number;
[IpcChannel.ENTITY_READ_TRANSFORMATION]: number;
[IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE]: number;
[IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE_NAME]: number;
[IpcChannel.ENTITY_READ_WORK_AUTHOR]: number;
[IpcChannel.ENTITY_READ_WORK_CHARACTER]: number;
[IpcChannel.ENTITY_READ_WORK_CHARACTER_NAME]: number;
[IpcChannel.ENTITY_READ_WORK]: number;
[IpcChannel.ENTITY_READ_WORK_NAME]: number;
[IpcChannel.ENTITY_READ_WORK_TAG]: number;
[IpcChannel.ENTITY_READ_WORLD]: number;
[IpcChannel.ENTITY_READ_WORLD_NAME]: number;
[IpcChannel.ENTITY_READ_WORLD_CHARACTER]: number;
[IpcChannel.ENTITY_READ_WORLD_CHARACTER_NAME]: number;
};
type ReadEntityType = {
[IpcChannel.ENTITY_READ_AUTHOR]: Author;
[IpcChannel.ENTITY_READ_AUTHOR_NAME]: AuthorName;
[IpcChannel.ENTITY_READ_AUTHOR_ROLE]: AuthorRole;
[IpcChannel.ENTITY_READ_AUTHOR_ROLE_NAME]: AuthorRoleName;
[IpcChannel.ENTITY_READ_CHARACTER_TAG]: CharacterTag;
[IpcChannel.ENTITY_READ_COLLECTION]: Collection;
[IpcChannel.ENTITY_READ_COLLECTION_NAME]: CollectionName;
[IpcChannel.ENTITY_READ_COLLECTION_PART]: CollectionPart;
[IpcChannel.ENTITY_READ_COPY]: Copy;
[IpcChannel.ENTITY_READ_INTERACTION_TAG]: InteractionTag;
[IpcChannel.ENTITY_READ_LANGUAGE]: Language;
[IpcChannel.ENTITY_READ_SITE]: Site;
[IpcChannel.ENTITY_READ_SITE_NAME]: SiteName;
[IpcChannel.ENTITY_READ_SOURCE]: Source;
[IpcChannel.ENTITY_READ_TAG]: Tag;
[IpcChannel.ENTITY_READ_TAG_NAME]: TagName;
[IpcChannel.ENTITY_READ_TRANSFORMATION]: Transformation;
[IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE]: TransformationType;
[IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE_NAME]: TransformationTypeName;
[IpcChannel.ENTITY_READ_WORK_AUTHOR]: WorkAuthor;
[IpcChannel.ENTITY_READ_WORK_CHARACTER]: WorkCharacter;
[IpcChannel.ENTITY_READ_WORK_CHARACTER_NAME]: WorkCharacterName;
[IpcChannel.ENTITY_READ_WORK]: Work;
[IpcChannel.ENTITY_READ_WORK_NAME]: WorkName;
[IpcChannel.ENTITY_READ_WORK_TAG]: WorkTag;
[IpcChannel.ENTITY_READ_WORLD]: World;
[IpcChannel.ENTITY_READ_WORLD_NAME]: WorldName;
[IpcChannel.ENTITY_READ_WORLD_CHARACTER]: WorldCharacter;
[IpcChannel.ENTITY_READ_WORLD_CHARACTER_NAME]: WorldCharacterName;
};
async function read<T extends keyof ReadSerializedType>(
id: ReadIdType[T],
entityTarget: EntityTarget<ReadEntityType[T]>,
// @ts-ignore -- yeah, i don't get it
serializer: Serializer<ReadEntityType[T], ReadSerializedType[T], ReadIdType[T]>,
): Promise<ReadSerializedType[T]> {
const repository = await getRepository(entityTarget);
const entity = await repository.findOneOrFail(id);
return serializer.serialize(entity);
}
ipcServer.answer(IpcChannel.ENTITY_READ_AUTHOR, (id) =>
read<IpcChannel.ENTITY_READ_AUTHOR>(id, Author, container.get(Service.AUTHOR_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_AUTHOR_NAME, (id) =>
read<IpcChannel.ENTITY_READ_AUTHOR_NAME>(id, AuthorName, container.get(Service.AUTHOR_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_AUTHOR_ROLE, (id) =>
read<IpcChannel.ENTITY_READ_AUTHOR_ROLE>(id, AuthorRole, container.get(Service.AUTHOR_ROLE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_AUTHOR_ROLE_NAME, (id) =>
read<IpcChannel.ENTITY_READ_AUTHOR_ROLE_NAME>(id, AuthorRoleName, container.get(Service.AUTHOR_ROLE_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_CHARACTER_TAG, (id) =>
read<IpcChannel.ENTITY_READ_CHARACTER_TAG>(id, CharacterTag, container.get(Service.CHARACTER_TAG_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_COLLECTION, (id) =>
read<IpcChannel.ENTITY_READ_COLLECTION>(id, Collection, container.get(Service.COLLECTION_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_COLLECTION_NAME, (id) =>
read<IpcChannel.ENTITY_READ_COLLECTION_NAME>(id, CollectionName, container.get(Service.COLLECTION_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_COLLECTION_PART, (id) =>
read<IpcChannel.ENTITY_READ_COLLECTION_PART>(id, CollectionPart, container.get(Service.COLLECTION_PART_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_COPY, (id) =>
read<IpcChannel.ENTITY_READ_COPY>(id, Copy, container.get(Service.COPY_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_INTERACTION_TAG, (id) =>
read<IpcChannel.ENTITY_READ_INTERACTION_TAG>(id, InteractionTag, container.get(Service.INTERACTION_TAG_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_LANGUAGE, (code) =>
read<IpcChannel.ENTITY_READ_LANGUAGE>(code, Language, container.get(Service.LANGUAGE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_SITE, (id) =>
read<IpcChannel.ENTITY_READ_SITE>(id, Site, container.get(Service.SITE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_SITE_NAME, (id) =>
read<IpcChannel.ENTITY_READ_SITE_NAME>(id, SiteName, container.get(Service.SITE_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_SOURCE, (id) =>
read<IpcChannel.ENTITY_READ_SOURCE>(id, Source, container.get(Service.SOURCE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_TAG, (id) =>
read<IpcChannel.ENTITY_READ_TAG>(id, Tag, container.get(Service.TAG_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_TAG_NAME, (id) =>
read<IpcChannel.ENTITY_READ_TAG_NAME>(id, TagName, container.get(Service.TAG_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_TRANSFORMATION, (id) =>
read<IpcChannel.ENTITY_READ_TRANSFORMATION>(id, Transformation, container.get(Service.TRANSFORMATION_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE, (id) =>
read<IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE>(
id,
TransformationType,
container.get(Service.TRANSFORMATION_TYPE_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE_NAME, (id) =>
read<IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE_NAME>(
id,
TransformationTypeName,
container.get(Service.TRANSFORMATION_TYPE_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORK_AUTHOR, (id) =>
read<IpcChannel.ENTITY_READ_WORK_AUTHOR>(id, WorkAuthor, container.get(Service.WORK_AUTHOR_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORK_CHARACTER, (id) =>
read<IpcChannel.ENTITY_READ_WORK_CHARACTER>(id, WorkCharacter, container.get(Service.WORK_CHARACTER_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORK_CHARACTER_NAME, (id) =>
read<IpcChannel.ENTITY_READ_WORK_CHARACTER_NAME>(
id,
WorkCharacterName,
container.get(Service.WORK_CHARACTER_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORK, (id) =>
read<IpcChannel.ENTITY_READ_WORK>(id, Work, container.get(Service.WORK_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORK_NAME, (id) =>
read<IpcChannel.ENTITY_READ_WORK_NAME>(id, WorkName, container.get(Service.WORK_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORK_TAG, (id) =>
read<IpcChannel.ENTITY_READ_WORK_TAG>(id, WorkTag, container.get(Service.WORK_TAG_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORLD, (id) =>
read<IpcChannel.ENTITY_READ_WORLD>(id, World, container.get(Service.WORLD_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORLD_NAME, (id) =>
read<IpcChannel.ENTITY_READ_WORLD_NAME>(id, WorldName, container.get(Service.WORLD_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORLD_CHARACTER, (id) =>
read<IpcChannel.ENTITY_READ_WORLD_CHARACTER>(id, WorldCharacter, container.get(Service.WORLD_CHARACTER_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_READ_WORLD_CHARACTER_NAME, (id) =>
read<IpcChannel.ENTITY_READ_WORLD_CHARACTER_NAME>(
id,
WorldCharacterName,
container.get(Service.WORLD_CHARACTER_NAME_SERIALIZER),
),
);

View File

@ -0,0 +1,281 @@
import type { EntityTarget } from 'typeorm';
import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { container, Service } from '../../core/container';
import { getRepository } from '../../core/database';
import { Author } from '../../entities/library/author';
import { AuthorName } from '../../entities/library/author-name';
import { AuthorRole } from '../../entities/library/author-role';
import { AuthorRoleName } from '../../entities/library/author-role-name';
import { CharacterTag } from '../../entities/library/character-tag';
import { Collection } from '../../entities/library/collection';
import { CollectionName } from '../../entities/library/collection-name';
import { CollectionPart } from '../../entities/library/collection-part';
import { Copy } from '../../entities/library/copy';
import { InteractionTag } from '../../entities/library/interaction-tag';
import { Site } from '../../entities/library/site';
import { SiteName } from '../../entities/library/site-name';
import { Source } from '../../entities/library/source';
import { Tag } from '../../entities/library/tag';
import { TagName } from '../../entities/library/tag-name';
import { Transformation } from '../../entities/library/transformation';
import { TransformationType } from '../../entities/library/transformation-type';
import { TransformationTypeName } from '../../entities/library/transformation-type-name';
import { Work } from '../../entities/library/work';
import { WorkAuthor } from '../../entities/library/work-author';
import { WorkCharacter } from '../../entities/library/work-character';
import { WorkCharacterName } from '../../entities/library/work-character-name';
import { WorkName } from '../../entities/library/work-name';
import { WorkTag } from '../../entities/library/work-tag';
import { World } from '../../entities/library/world';
import { WorldCharacter } from '../../entities/library/world-character';
import { WorldCharacterName } from '../../entities/library/world-character-name';
import { WorldName } from '../../entities/library/world-name';
import { ipcServer } from '../ipc/ipc-server';
import type { Serializer } from '../serialization/serializer';
type UpdateSerializedType = {
[IpcChannel.ENTITY_UPDATE_AUTHOR]: AuthorSerializedInterface;
[IpcChannel.ENTITY_UPDATE_AUTHOR_NAME]: AuthorNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE]: AuthorRoleSerializedInterface;
[IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE_NAME]: AuthorRoleNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_CHARACTER_TAG]: CharacterTagSerializedInterface;
[IpcChannel.ENTITY_UPDATE_COLLECTION]: CollectionSerializedInterface;
[IpcChannel.ENTITY_UPDATE_COLLECTION_NAME]: CollectionNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_COLLECTION_PART]: CollectionPartSerializedInterface;
[IpcChannel.ENTITY_UPDATE_COPY]: CopySerializedInterface;
[IpcChannel.ENTITY_UPDATE_INTERACTION_TAG]: InteractionTagSerializedInterface;
[IpcChannel.ENTITY_UPDATE_SITE]: SiteSerializedInterface;
[IpcChannel.ENTITY_UPDATE_SITE_NAME]: SiteNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_SOURCE]: SourceSerializedInterface;
[IpcChannel.ENTITY_UPDATE_TAG]: TagSerializedInterface;
[IpcChannel.ENTITY_UPDATE_TAG_NAME]: TagNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_TRANSFORMATION]: TransformationSerializedInterface;
[IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE]: TransformationTypeSerializedInterface;
[IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE_NAME]: TransformationTypeNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORK_AUTHOR]: WorkAuthorSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORK_CHARACTER]: WorkCharacterSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORK_CHARACTER_NAME]: WorkCharacterNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORK]: WorkSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORK_NAME]: WorkNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORK_TAG]: WorkTagSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORLD]: WorldSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORLD_NAME]: WorldNameSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER]: WorldCharacterSerializedInterface;
[IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER_NAME]: WorldCharacterNameSerializedInterface;
};
type UpdateEntityType = {
[IpcChannel.ENTITY_UPDATE_AUTHOR]: Author;
[IpcChannel.ENTITY_UPDATE_AUTHOR_NAME]: AuthorName;
[IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE]: AuthorRole;
[IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE_NAME]: AuthorRoleName;
[IpcChannel.ENTITY_UPDATE_CHARACTER_TAG]: CharacterTag;
[IpcChannel.ENTITY_UPDATE_COLLECTION]: Collection;
[IpcChannel.ENTITY_UPDATE_COLLECTION_NAME]: CollectionName;
[IpcChannel.ENTITY_UPDATE_COLLECTION_PART]: CollectionPart;
[IpcChannel.ENTITY_UPDATE_COPY]: Copy;
[IpcChannel.ENTITY_UPDATE_INTERACTION_TAG]: InteractionTag;
[IpcChannel.ENTITY_UPDATE_SITE]: Site;
[IpcChannel.ENTITY_UPDATE_SITE_NAME]: SiteName;
[IpcChannel.ENTITY_UPDATE_SOURCE]: Source;
[IpcChannel.ENTITY_UPDATE_TAG]: Tag;
[IpcChannel.ENTITY_UPDATE_TAG_NAME]: TagName;
[IpcChannel.ENTITY_UPDATE_TRANSFORMATION]: Transformation;
[IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE]: TransformationType;
[IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE_NAME]: TransformationTypeName;
[IpcChannel.ENTITY_UPDATE_WORK_AUTHOR]: WorkAuthor;
[IpcChannel.ENTITY_UPDATE_WORK_CHARACTER]: WorkCharacter;
[IpcChannel.ENTITY_UPDATE_WORK_CHARACTER_NAME]: WorkCharacterName;
[IpcChannel.ENTITY_UPDATE_WORK]: Work;
[IpcChannel.ENTITY_UPDATE_WORK_NAME]: WorkName;
[IpcChannel.ENTITY_UPDATE_WORK_TAG]: WorkTag;
[IpcChannel.ENTITY_UPDATE_WORLD]: World;
[IpcChannel.ENTITY_UPDATE_WORLD_NAME]: WorldName;
[IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER]: WorldCharacter;
[IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER_NAME]: WorldCharacterName;
};
async function update<T extends keyof UpdateSerializedType>(
id: number,
partial: Partial<UpdateSerializedType[T]>,
entityTarget: EntityTarget<UpdateEntityType[T]>,
serializer: Serializer<UpdateEntityType[T], UpdateSerializedType[T]>,
): Promise<UpdateSerializedType[T]> {
const repository = await getRepository(entityTarget);
await repository.update(id, serializer.deserialize(partial) as QueryDeepPartialEntity<UpdateEntityType[T]>);
const entity = await repository.findOneOrFail(id);
return serializer.serialize(entity);
}
ipcServer.answer(IpcChannel.ENTITY_UPDATE_AUTHOR, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_AUTHOR>(id, partial, Author, container.get(Service.AUTHOR_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_AUTHOR_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_AUTHOR_NAME>(id, partial, AuthorName, container.get(Service.AUTHOR_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE>(id, partial, AuthorRole, container.get(Service.AUTHOR_ROLE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE_NAME>(
id,
partial,
AuthorRoleName,
container.get(Service.AUTHOR_ROLE_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_CHARACTER_TAG, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_CHARACTER_TAG>(
id,
partial,
CharacterTag,
container.get(Service.CHARACTER_TAG_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_COLLECTION, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_COLLECTION>(id, partial, Collection, container.get(Service.COLLECTION_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_COLLECTION_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_COLLECTION_NAME>(
id,
partial,
CollectionName,
container.get(Service.COLLECTION_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_COLLECTION_PART, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_COLLECTION_PART>(
id,
partial,
CollectionPart,
container.get(Service.COLLECTION_PART_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_COPY, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_COPY>(id, partial, Copy, container.get(Service.COPY_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_INTERACTION_TAG, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_INTERACTION_TAG>(
id,
partial,
InteractionTag,
container.get(Service.INTERACTION_TAG_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_SITE, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_SITE>(id, partial, Site, container.get(Service.SITE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_SITE_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_SITE_NAME>(id, partial, SiteName, container.get(Service.SITE_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_SOURCE, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_SOURCE>(id, partial, Source, container.get(Service.SOURCE_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_TAG, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_TAG>(id, partial, Tag, container.get(Service.TAG_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_TAG_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_TAG_NAME>(id, partial, TagName, container.get(Service.TAG_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_TRANSFORMATION, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_TRANSFORMATION>(
id,
partial,
Transformation,
container.get(Service.TRANSFORMATION_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE>(
id,
partial,
TransformationType,
container.get(Service.TRANSFORMATION_TYPE_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE_NAME>(
id,
partial,
TransformationTypeName,
container.get(Service.TRANSFORMATION_TYPE_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORK_AUTHOR, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORK_AUTHOR>(id, partial, WorkAuthor, container.get(Service.WORK_AUTHOR_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORK_CHARACTER, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORK_CHARACTER>(
id,
partial,
WorkCharacter,
container.get(Service.WORK_CHARACTER_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORK_CHARACTER_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORK_CHARACTER_NAME>(
id,
partial,
WorkCharacterName,
container.get(Service.WORK_CHARACTER_NAME_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORK, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORK>(id, partial, Work, container.get(Service.WORK_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORK_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORK_NAME>(id, partial, WorkName, container.get(Service.WORK_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORK_TAG, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORK_TAG>(id, partial, WorkTag, container.get(Service.WORK_TAG_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORLD, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORLD>(id, partial, World, container.get(Service.WORLD_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORLD_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORLD_NAME>(id, partial, WorldName, container.get(Service.WORLD_NAME_SERIALIZER)),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER>(
id,
partial,
WorldCharacter,
container.get(Service.WORLD_CHARACTER_SERIALIZER),
),
);
ipcServer.answer(IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER_NAME, async ({ id, partial }) =>
update<IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER_NAME>(
id,
partial,
WorldCharacterName,
container.get(Service.WORLD_CHARACTER_NAME_SERIALIZER),
),
);

View File

@ -1,3 +0,0 @@
interface I18nTranslatorInterface {
t(text: string): string;
}

View File

@ -1,8 +0,0 @@
import { injectable } from 'inversify';
@injectable()
export class I18nTranslator implements I18nTranslatorInterface {
public t(text: string): string {
return text;
}
}

View File

@ -1,7 +0,0 @@
import { registerHandler } from '../ipc-server';
export function answer(channel: IpcChannel): IpcControllerMethodDecorator {
return function (target: IpcController, propertyKey): void {
registerHandler(channel, target, propertyKey);
};
}

View File

@ -1,25 +1,16 @@
import { ipcMain } from 'electron'; import { ipcMain, IpcMainInvokeEvent } from 'electron';
import IpcMainEvent = Electron.IpcMainEvent; import { container, Service } from '../../core/container';
export function registerHandler(channel: IpcChannel, controller: IpcController, handler: string): void { const logger = container.get(Service.LOGGER);
ipcMain.on(channel, (event: IpcMainEvent, payload: IpcPayload) => {
((controller.get() as unknown) as { [x: string]: IpcHandler }) export const ipcServer: IpcServer = {
[handler](payload.data) answer<T extends IpcChannel>(channel: T, answerer: IpcHandler<IpcParameter<T>, IpcAnswer<T>>) {
.then((result: unknown) => { const listener = (data: IpcParameter<T>): Promise<IpcAnswer<T>> =>
const response: IpcResponse = { answerer(data).catch((reason: Error) => {
id: payload.id, void logger.exception(reason);
success: true, throw reason;
data: result,
};
event.reply(channel, response);
})
.catch((reason: Error) => {
const response: IpcResponse = {
id: payload.id,
success: false,
error: reason.message,
};
event.reply(channel, response);
}); });
});
} ipcMain.handle(channel, (event: IpcMainInvokeEvent, data: IpcParameter<T>) => listener(data));
},
};

View File

@ -1,10 +1,10 @@
import { createInterface, Interface } from 'readline'; import { createInterface, Interface } from 'readline';
import chai, { expect } from 'chai'; import chai, { expect } from 'chai';
import 'mocha';
import chaiFs from 'chai-fs'; import chaiFs from 'chai-fs';
import fc from 'fast-check'; import fc from 'fast-check';
import fs from 'fs-extra'; import fs from 'fs-extra';
import { container } from '../../core/container'; import 'mocha';
import { container, Service } from '../../core/container';
import { setDev } from '../../core/env.spec'; import { setDev } from '../../core/env.spec';
import { LogLevel } from './log-level'; import { LogLevel } from './log-level';
@ -41,20 +41,20 @@ describe('Logger Service', () => {
const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber); const logLevelNumberArbitrary = fc.constantFrom(...logLevelsNumber);
it('creates log files', () => { it('creates log files', () => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
expect(logger.getLogFile()).path('log file is not created'); expect(logger.getLogFile()).path('log file is not created');
expect(logger.getExceptionsLogFile()).path('exception log file is not created'); expect(logger.getExceptionsLogFile()).path('exception log file is not created');
}); });
it('logs exceptions', async () => { it('logs exceptions', async () => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
await logger.exception(new Error('this is an error')); await logger.exception(new Error('this is an error'));
}); });
it("default log file doesn't get bigger than 50KB @slow", async () => { it("default log file doesn't get bigger than 50KB @slow", async () => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
let prevLogFileSize = (await fs.stat(logger.getLogFile())).size; let prevLogFileSize = (await fs.stat(logger.getLogFile())).size;
let minNumberOfLines = maxLogSize; let minNumberOfLines = maxLogSize;
@ -75,7 +75,7 @@ describe('Logger Service', () => {
}).timeout(15000); }).timeout(15000);
it("exception log file doesn't get bigger than 50KB @slow", async () => { it("exception log file doesn't get bigger than 50KB @slow", async () => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
let prevLogFileSize = (await fs.stat(logger.getExceptionsLogFile())).size; let prevLogFileSize = (await fs.stat(logger.getExceptionsLogFile())).size;
let minNumberOfLines = maxLogSize; let minNumberOfLines = maxLogSize;
@ -96,7 +96,7 @@ describe('Logger Service', () => {
}).timeout(15000); }).timeout(15000);
it('logs different levels directly', () => { it('logs different levels directly', () => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
return fc.assert( return fc.assert(
fc.asyncProperty(logLevelArbitrary as LogLevelArbitrary, fc.string(), async (logLevel, message) => { fc.asyncProperty(logLevelArbitrary as LogLevelArbitrary, fc.string(), async (logLevel, message) => {
@ -107,12 +107,12 @@ describe('Logger Service', () => {
}), }),
{ {
numRuns: 50, numRuns: 50,
} },
); );
}); });
it('logs different levels indirectly via the generic log function', () => { it('logs different levels indirectly via the generic log function', () => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
return fc.assert( return fc.assert(
fc.asyncProperty(logLevelNumberArbitrary, fc.string(), async (logLevelNumber, message) => { fc.asyncProperty(logLevelNumberArbitrary, fc.string(), async (logLevelNumber, message) => {
@ -121,17 +121,17 @@ describe('Logger Service', () => {
expect(lastLine).contains(message, 'the log line does not contain the message'); expect(lastLine).contains(message, 'the log line does not contain the message');
expect(lastLine).contains( expect(lastLine).contains(
logLevels[logLevelNumber], logLevels[logLevelNumber],
`the log line does not contain the '${logLevels[logLevelNumber]}' keyword` `the log line does not contain the '${logLevels[logLevelNumber]}' keyword`,
); );
}), }),
{ {
numRuns: 50, numRuns: 50,
} },
); );
}); });
it('logs debug only in dev mode', async () => { it('logs debug only in dev mode', async () => {
const logger: LoggerInterface = container.get('logger'); const logger = container.get(Service.LOGGER);
setDev(); setDev();
await logger.debug('this is a development message'); await logger.debug('this is a development message');
@ -143,7 +143,7 @@ describe('Logger Service', () => {
await logger.debug('this is a second development message, should not be here'); await logger.debug('this is a second development message, should not be here');
expect(lastLine).not.contain( expect(lastLine).not.contain(
'this is a second development message, should not be here', 'this is a second development message, should not be here',
'debug is logged even in non-dev mode' 'debug is logged even in non-dev mode',
); );
}); });
}); });

View File

@ -64,9 +64,9 @@ export class Logger implements LoggerInterface {
return Logger.writeStream( return Logger.writeStream(
Readable.from( Readable.from(
`[${new Date().toISOString()}] ${(error as NodeJS.ErrnoException).code ?? 'Error'}: ${error.message} `[${new Date().toISOString()}] ${(error as NodeJS.ErrnoException).code ?? 'Error'}: ${error.message}
${error.stack ?? 'no stack trace'}\n` ${error.stack ?? 'no stack trace'}\n`,
), ),
exceptionLogFile exceptionLogFile,
); );
} }

View File

@ -29,21 +29,18 @@ describe('Simple Mutex', () => {
const acquireOne = mutex.acquire(); const acquireOne = mutex.acquire();
const acquireTwo = mutex.acquire(); const acquireTwo = mutex.acquire();
const acquireThree = mutex.acquire(); const acquireThree = mutex.acquire();
void acquireOne.then((release) => void acquireOne.then(async (release) => {
useResource().then(() => { await useResource();
release(); release();
}) });
); void acquireTwo.then(async (release) => {
void acquireTwo.then((release) => await useResource();
useResource().then(() => { release();
release(); });
}) return acquireThree.then(async (release) => {
); await useResource();
return acquireThree.then((release) => release();
useResource().then(() => { });
release();
})
);
}); });
it('executes consumers in the right order', async () => { it('executes consumers in the right order', async () => {
@ -60,21 +57,18 @@ describe('Simple Mutex', () => {
const acquireOne = mutex.acquire(); const acquireOne = mutex.acquire();
const acquireTwo = mutex.acquire(); const acquireTwo = mutex.acquire();
const acquireThree = mutex.acquire(); const acquireThree = mutex.acquire();
void acquireOne.then((release) => void acquireOne.then(async (release) => {
useResource(1).then(() => { await useResource(1);
release(); release();
}) });
); void acquireTwo.then(async (release) => {
void acquireTwo.then((release) => await useResource(2);
useResource(2).then(() => { release();
release(); });
}) return acquireThree.then(async (release) => {
); await useResource(3);
return acquireThree.then((release) => release();
useResource(3).then(() => { });
release();
})
);
}); });
it('correctly informs if it is currently locked', async () => { it('correctly informs if it is currently locked', async () => {

View File

@ -1,4 +1,5 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { Service } from '../../core/container';
import { inject } from '../../core/inject'; import { inject } from '../../core/inject';
import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface'; import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
@ -6,7 +7,7 @@ import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
export class NhentaiApi implements NhentaiApiInterface { export class NhentaiApi implements NhentaiApiInterface {
private readonly appWindow: NhentaiAppWindowInterface; private readonly appWindow: NhentaiAppWindowInterface;
public constructor(@inject('nhentai-app-window') appWindow: NhentaiAppWindowInterface) { public constructor(@inject(Service.NHENTAI_APP_WINDOW) appWindow: NhentaiAppWindowInterface) {
this.appWindow = appWindow; this.appWindow = appWindow;
} }

View File

@ -1,27 +1,26 @@
import chai, { expect } from 'chai'; import chai, { expect } from 'chai';
import deepEqualInAnyOrder from 'deep-equal-in-any-order'; import deepEqualInAnyOrder from 'deep-equal-in-any-order';
import { describe, it, before } from 'mocha'; import { before, describe, it } from 'mocha';
import { container } from '../../core/container'; import { container, Service } from '../../core/container';
import { LoggerMock } from '../logger/logger.mock'; import { LoggerMock } from '../logger/logger.mock';
import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
chai.use(deepEqualInAnyOrder); chai.use(deepEqualInAnyOrder);
describe('Nhentai App Window', () => { describe('Nhentai App Window', () => {
before(() => { before(() => {
container.unbind('logger'); container.unbind(Service.LOGGER);
container.bind('logger').to(LoggerMock); container.bind(Service.LOGGER).to(LoggerMock);
}); });
it('gets the gallery information from an identifier @slow', async () => { it('gets the gallery information from an identifier @slow', async () => {
const nhentaiAppWindow: NhentaiAppWindowInterface = container.get('nhentai-app-window'); const nhentaiAppWindow = container.get(Service.NHENTAI_APP_WINDOW);
let expectedGallery: Nhentai.Gallery = { let expectedGallery: Nhentai.Gallery = {
url: 'https://nhentai.net/g/117300/', url: 'https://nhentai.net/g/107386/',
title: { title: {
pre: '[Homunculus]', pre: '[Homunculus]',
main: 'Renai Sample', main: 'Renai Sample + Bonus Booklets',
post: '[English] [Decensored]', post: '[English] [Tankoubon version]',
}, },
artists: ['homunculus'], artists: ['homunculus'],
groups: [], groups: [],
@ -32,22 +31,27 @@ describe('Nhentai App Window', () => {
'stockings', 'stockings',
'schoolgirl uniform', 'schoolgirl uniform',
'glasses', 'glasses',
'nakadashi',
'incest', 'incest',
'tankoubon', 'tankoubon',
'defloration', 'defloration',
'milf',
'swimsuit', 'swimsuit',
'ffm threesome', 'ffm threesome',
'impregnation',
'sister', 'sister',
'schoolboy uniform', 'schoolboy uniform',
'bikini', 'bikini',
'uncensored', 'teacher',
'small breasts', 'apron',
'inseki',
'leg lock',
'cousin',
'niece',
], ],
languages: ['english', 'translated'], languages: ['english', 'translated'],
uploadTime: 1411853968970, uploadTime: 1404001890940,
}; };
let gallery = await nhentaiAppWindow.getGallery('117300'); let gallery = await nhentaiAppWindow.getGallery('107386');
expect(gallery).deep.equalInAnyOrder(expectedGallery, 'Renai Sample is not got correctly'); expect(gallery).deep.equalInAnyOrder(expectedGallery, 'Renai Sample is not got correctly');
expectedGallery = { expectedGallery = {

View File

@ -5,37 +5,38 @@ import { Readable } from 'stream';
import { URL } from 'url'; import { URL } from 'url';
import { createReadStream, remove } from 'fs-extra'; import { createReadStream, remove } from 'fs-extra';
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { Service } from '../../core/container';
import { inject } from '../../core/inject'; import { inject } from '../../core/inject';
import { CloudflareSiteAppWindow } from '../cloudflare/cloudflare-site-app-window'; import { CloudflareSiteAppWindow } from '../cloudflare/cloudflare-site-app-window';
import { mergeContentSecurityPolicy } from '../session/session-util'; import { mergeContentSecurityPolicy } from '../session/session-util';
import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface'; import type { NhentaiAppWindowInterface } from './nhentai-app-window-interface';
import { import {
url as nhentaiUrl,
hostname as nhentaiHostname,
paths as nhentaiPaths,
getFavoritePageUrl,
nextFavoritePageSelector,
coverLinkSelector, coverLinkSelector,
downloadLinkId, downloadLinkId,
getGalleryId, favoritePageIsReady,
galleryPageIsReady,
getBookUrl, getBookUrl,
getFavoritePageUrl,
getGalleryId,
hostname as nhentaiHostname,
labeledTagContainerSelector,
loginPageIsReady,
mainTitleSelector,
nextFavoritePageSelector,
pageIsReady,
paths as nhentaiPaths,
postTitleSelector,
preTitleSelector, preTitleSelector,
tagLabelArtists, tagLabelArtists,
labeledTagContainerSelector, tagLabelCharacters,
tagLabelGroups,
tagLabelLanguages,
tagLabelParodies,
tagLabelTags,
tagNameSelector, tagNameSelector,
tagSelector, tagSelector,
tagLabelGroups,
tagLabelParodies,
tagLabelCharacters,
tagLabelTags,
mainTitleSelector,
postTitleSelector,
galleryPageIsReady,
loginPageIsReady,
favoritePageIsReady,
pageIsReady,
timeSelector, timeSelector,
tagLabelLanguages, url as nhentaiUrl,
} from './nhentai-util'; } from './nhentai-util';
const waitInterval = 2000; const waitInterval = 2000;
@ -44,7 +45,7 @@ const waitInterval = 2000;
export class NhentaiAppWindow extends CloudflareSiteAppWindow implements NhentaiAppWindowInterface { export class NhentaiAppWindow extends CloudflareSiteAppWindow implements NhentaiAppWindowInterface {
protected readyCheck = pageIsReady; protected readyCheck = pageIsReady;
public constructor(@inject('logger') logger: LoggerInterface) { public constructor(@inject(Service.LOGGER) logger: LoggerInterface) {
super(logger, nhentaiUrl); super(logger, nhentaiUrl);
} }
@ -64,8 +65,8 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai
for await (const wc of this.getFavoritePageWebContentsGenerator()) { for await (const wc of this.getFavoritePageWebContentsGenerator()) {
bookUrls.push( bookUrls.push(
...((await wc.executeJavaScript( ...((await wc.executeJavaScript(
`Array.from(document.querySelectorAll('${coverLinkSelector}')).map((el) => el.href)` `Array.from(document.querySelectorAll('${coverLinkSelector}')).map((el) => el.href)`,
)) as string[]) )) as string[]),
); );
} }
@ -80,7 +81,7 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai
})(this), })(this),
{ {
objectMode: true, objectMode: true,
} },
); );
readable.once('end', () => { readable.once('end', () => {
this.close(); this.close();
@ -198,7 +199,7 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai
.webContents.executeJavaScript( .webContents.executeJavaScript(
`fetch('${ `fetch('${
nhentaiUrl + nhentaiPaths.favorites nhentaiUrl + nhentaiPaths.favorites
}', {credentials: 'include', redirect: 'manual'}).then((res) => res.status)` }', {credentials: 'include', redirect: 'manual'}).then((res) => res.status)`,
) )
.then((status: number) => status === HttpCode.OK); .then((status: number) => status === HttpCode.OK);
} }
@ -225,11 +226,11 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai
while (true) { while (true) {
yield this.getWindow().webContents; yield this.getWindow().webContents;
const hasNextPage = (await this.getWindow().webContents.executeJavaScript( const hasNextPage = (await this.getWindow().webContents.executeJavaScript(
`!!document.querySelector('${nextFavoritePageSelector}')` `!!document.querySelector('${nextFavoritePageSelector}')`,
)) as boolean; )) as boolean;
if (hasNextPage) { if (hasNextPage) {
const nextPageHref = (await this.getWindow().webContents.executeJavaScript( const nextPageHref = (await this.getWindow().webContents.executeJavaScript(
`document.querySelector('${nextFavoritePageSelector}').href` `document.querySelector('${nextFavoritePageSelector}').href`,
)) as string; )) as string;
await this.loadFavoritesPageSafe(nextPageHref); await this.loadFavoritesPageSafe(nextPageHref);
} else { } else {
@ -245,7 +246,7 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai
const filePath = path.resolve(os.tmpdir(), fileName); const filePath = path.resolve(os.tmpdir(), fileName);
await this.loadGalleryPageSafe(bookUrl); await this.loadGalleryPageSafe(bookUrl);
const downloadLink: string = (await this.getWindow().webContents.executeJavaScript( const downloadLink: string = (await this.getWindow().webContents.executeJavaScript(
`document.getElementById('${downloadLinkId}').href` `document.getElementById('${downloadLinkId}').href`,
)) as string; )) as string;
await this.downloadUrlSafe(downloadLink, filePath); await this.downloadUrlSafe(downloadLink, filePath);
@ -283,7 +284,7 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai
(tagContainer) => Array.from(tagContainer.querySelectorAll('${tagSelector}')) (tagContainer) => Array.from(tagContainer.querySelectorAll('${tagSelector}'))
).flat().map( ).flat().map(
(tagElement) => tagElement.querySelector('${tagNameSelector}').innerHTML (tagElement) => tagElement.querySelector('${tagNameSelector}').innerHTML
)` )`,
) as Promise<string[]>; ) as Promise<string[]>;
} }
} }

View File

@ -1,65 +1,40 @@
import path from 'path'; import path from 'path';
import { createWriteStream } from 'fs-extra'; import { createWriteStream } from 'fs-extra';
import { container } from '../../core/container'; import { t } from '../../../shared/services/translation/t';
import type { DialogInterface } from '../dialog/dialog-interface'; import { container, Service } from '../../core/container';
import { answer } from '../ipc/annotations/answer'; import { ipcServer } from '../ipc/ipc-server';
import type { SourceGetterInterface } from '../source/source-getter-interface';
export class NhentaiIpcController implements IpcController { ipcServer.answer(IpcChannel.NHENTAI_SAVE_FAVORITES, async (): Promise<void> => {
private readonly nhentaiApi: NhentaiApiInterface; const nhentaiApi = container.get(Service.NHENTAI_API);
const dialog = container.get(Service.DIALOG);
private readonly nhentaiSourceGetter: SourceGetterInterface; const result = await dialog.selectFolder({
title: t('imperatives.dialog.select_torrent_save_location'),
});
private readonly translator: I18nTranslatorInterface; if (result.canceled) {
return;
private readonly dialog: DialogInterface;
private constructor(
nhentaiApi: NhentaiApiInterface,
nhentaiSourceGetter: SourceGetterInterface,
translator: I18nTranslatorInterface,
dialog: DialogInterface
) {
this.nhentaiApi = nhentaiApi;
this.nhentaiSourceGetter = nhentaiSourceGetter;
this.translator = translator;
this.dialog = dialog;
} }
@answer(IpcChannel.NHENTAI_SAVE_FAVORITES) const favoritesStream = await nhentaiApi.getFavorites();
public async nhentaiSaveFavorites(): Promise<void> {
const result = await this.dialog.selectFolder({ return new Promise((resolve) => {
title: this.translator.t('Select torrent file save location'), favoritesStream.on('data', (favorite: Nhentai.Favorite) => {
const writable = createWriteStream(path.resolve(result.filePaths[0], favorite.name));
favorite.torrentFile.pipe(writable);
}); });
if (result.canceled) { favoritesStream.once('end', resolve);
return; });
} });
const favoritesStream = await this.nhentaiApi.getFavorites(); ipcServer.answer(
IpcChannel.NHENTAI_GET_WORK,
async ({ galleryId }: { galleryId: string }): Promise<WorkEntityInterface> => {
const nhentaiSourceGetter = container.get(Service.NHENTAI_SOURCE_GETTER);
return new Promise((resolve) => { const work = await nhentaiSourceGetter.find(galleryId);
favoritesStream.on('data', (favorite: Nhentai.Favorite) => {
const writable = createWriteStream(path.resolve(result.filePaths[0], favorite.name));
favorite.torrentFile.pipe(writable);
});
favoritesStream.once('end', resolve);
});
}
@answer(IpcChannel.NHENTAI_GET_WORK)
public async nhentaiGetWork({ galleryId }: { galleryId: string }): Promise<WorkEntityInterface> {
const work = await this.nhentaiSourceGetter.find(galleryId);
return work; return work;
} },
);
public get(): NhentaiIpcController {
const nhentaiApi: NhentaiApiInterface = container.get('nhentai-api');
const nhentaiSourceGetter: SourceGetterInterface = container.get('nhentai-source-getter');
const translator: I18nTranslatorInterface = container.get('i18n-translator');
const dialog: DialogInterface = container.get('dialog');
return new NhentaiIpcController(nhentaiApi, nhentaiSourceGetter, translator, dialog);
}
}

View File

@ -1,4 +1,5 @@
import { injectable } from 'inversify'; import { injectable } from 'inversify';
import { Service } from '../../core/container';
import { Database, getConnection } from '../../core/database'; import { Database, getConnection } from '../../core/database';
import { inject } from '../../core/inject'; import { inject } from '../../core/inject';
import { Copy } from '../../entities/library/copy'; import { Copy } from '../../entities/library/copy';
@ -18,7 +19,7 @@ async function getLanguage(nhentaiLanguageIdentifier: NhentaiRealLanguage): Prom
export class NhentaiSourceGetter implements SourceGetterInterface { export class NhentaiSourceGetter implements SourceGetterInterface {
private nhentaiApi: NhentaiApiInterface; private nhentaiApi: NhentaiApiInterface;
public constructor(@inject('nhentai-api') nhentaiApi: NhentaiApiInterface) { public constructor(@inject(Service.NHENTAI_API) nhentaiApi: NhentaiApiInterface) {
this.nhentaiApi = nhentaiApi; this.nhentaiApi = nhentaiApi;
} }
@ -49,7 +50,7 @@ export class NhentaiSourceGetter implements SourceGetterInterface {
return true; return true;
} }
return false; return false;
} },
); );
work.languages = Promise.all(filteredLanguages.map((language) => getLanguage(language))); work.languages = Promise.all(filteredLanguages.map((language) => getLanguage(language)));
} }

View File

@ -0,0 +1,48 @@
import type { EntityTarget } from 'typeorm';
import { getRepository } from '../../core/database';
export function getDeserializedEntityPromise<T>(serializedProperty: number, entityTarget: EntityTarget<T>): Promise<T>;
export function getDeserializedEntityPromise<T>(
serializedProperty: undefined,
entityTarget: EntityTarget<T>,
): undefined;
export function getDeserializedEntityPromise<T>(serializedProperty: null, entityTarget: EntityTarget<T>): null;
export function getDeserializedEntityPromise<T>(
serializedProperty: number | undefined,
entityTarget: EntityTarget<T>,
): Promise<T> | undefined;
export function getDeserializedEntityPromise<T>(
serializedProperty: number | undefined | null,
entityTarget: EntityTarget<T>,
): Promise<T> | undefined | null;
export function getDeserializedEntityPromise<T>(
serializedProperty: number | undefined | null,
entityTarget: EntityTarget<T>,
): Promise<T> | undefined | null {
return serializedProperty === null
? null
: serializedProperty
? getRepository(entityTarget).then((repo) => repo.findOneOrFail(serializedProperty))
: undefined;
}
export function getDeserializedEntitiesPromise<T>(
serializedProperty: number[],
entityTarget: EntityTarget<T>,
): Promise<T[]>;
export function getDeserializedEntitiesPromise<T>(
serializedProperty: undefined,
entityTarget: EntityTarget<T>,
): undefined;
export function getDeserializedEntitiesPromise<T>(
serializedProperty: number[] | undefined,
entityTarget: EntityTarget<T>,
): Promise<T[]> | undefined;
export function getDeserializedEntitiesPromise<T>(
serializedProperty: number[] | undefined,
entityTarget: EntityTarget<T>,
): Promise<T[]> | undefined {
return serializedProperty
? Promise.all(serializedProperty.map((id) => getRepository(entityTarget).then((repo) => repo.findOneOrFail(id))))
: undefined;
}

View File

@ -0,0 +1,17 @@
export function serializeEntityPromise<T extends Identifier>(
entityPromise: Promise<IdentifiableInterface<T>>,
): Promise<number>;
export function serializeEntityPromise<T extends Identifier>(
entityPromise: Promise<IdentifiableInterface<T>> | null,
): Promise<T | null>;
export async function serializeEntityPromise<T extends Identifier>(
entityPromise: Promise<IdentifiableInterface<T>> | null,
): Promise<T | null> {
return entityPromise ? (await entityPromise).id : null;
}
export async function serializeEntitiesPromise<T extends Identifier>(
entitiesPromise: Promise<Array<IdentifiableInterface<T>>>,
): Promise<T[]> {
return (await entitiesPromise).map((entity) => entity.id);
}

View File

@ -0,0 +1,9 @@
import { serializeEntityPromise } from './serialize-entity-promise';
export async function serializeName(nameEntity: NameEntityInterface): Promise<NameSerializedInterface> {
return {
id: nameEntity.id,
name: nameEntity.name,
entity: await serializeEntityPromise(nameEntity.entity),
};
}

View File

@ -0,0 +1,9 @@
export abstract class Serializer<
Entity extends IdentifiableInterface<Id>,
Serialized extends IdentifiableInterface<Id>,
Id extends Identifier = number,
> {
public abstract serialize(entity: Entity): Promise<Serialized>;
public abstract deserialize(partial: Partial<Serialized>): Partial<Entity>;
}

View File

@ -0,0 +1,20 @@
import { injectable } from 'inversify';
import { Author } from '../../../entities/library/author';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class AuthorNameSerializer extends Serializer<AuthorNameEntityInterface, AuthorNameSerializedInterface> {
public serialize(entity: AuthorNameEntityInterface): Promise<AuthorNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(partial: Partial<AuthorNameSerializedInterface>): Partial<AuthorNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, Author) } : {}),
};
}
}

View File

@ -0,0 +1,23 @@
import { injectable } from 'inversify';
import { AuthorRole } from '../../../entities/library/author-role';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class AuthorRoleNameSerializer extends Serializer<
AuthorRoleNameEntityInterface,
AuthorRoleNameSerializedInterface
> {
public serialize(entity: AuthorRoleNameEntityInterface): Promise<AuthorRoleNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(partial: Partial<AuthorRoleNameSerializedInterface>): Partial<AuthorRoleNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, AuthorRole) } : {}),
};
}
}

View File

@ -0,0 +1,34 @@
import { injectable } from 'inversify';
import { AuthorRoleName } from '../../../entities/library/author-role-name';
import { WorkAuthor } from '../../../entities/library/work-author';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class AuthorRoleSerializer extends Serializer<AuthorRoleEntityInterface, AuthorRoleSerializedInterface> {
public async serialize(entity: AuthorRoleEntityInterface): Promise<AuthorRoleSerializedInterface> {
const [names, workAuthors] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.workAuthors),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
description: entity.description,
names,
workAuthors,
};
}
public deserialize(partial: Partial<AuthorRoleSerializedInterface>): Partial<AuthorRoleEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.description ? { description: partial.description } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, AuthorRoleName) } : {}),
...(partial.workAuthors ? { workAuthors: getDeserializedEntitiesPromise(partial.workAuthors, WorkAuthor) } : {}),
};
}
}

View File

@ -0,0 +1,32 @@
import { injectable } from 'inversify';
import { AuthorName } from '../../../entities/library/author-name';
import { WorkAuthor } from '../../../entities/library/work-author';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise.js';
import { Serializer } from '../serializer.js';
@injectable()
export class AuthorSerializer extends Serializer<AuthorEntityInterface, AuthorSerializedInterface> {
public async serialize(entity: AuthorEntityInterface): Promise<AuthorSerializedInterface> {
const [names, workAuthors] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.workAuthors),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
names,
workAuthors,
};
}
public deserialize(partial: Partial<AuthorSerializedInterface>): Partial<AuthorEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, AuthorName) } : {}),
...(partial.workAuthors ? { workAuthors: getDeserializedEntitiesPromise(partial.workAuthors, WorkAuthor) } : {}),
};
}
}

View File

@ -0,0 +1,34 @@
import { injectable } from 'inversify';
import { Tag } from '../../../entities/library/tag';
import { WorkCharacter } from '../../../entities/library/work-character';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeEntityPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class CharacterTagSerializer extends Serializer<CharacterTagEntityInterface, CharacterTagSerializedInterface> {
public async serialize(entity: CharacterTagEntityInterface): Promise<CharacterTagSerializedInterface> {
const [tag, workCharacter] = await Promise.all([
serializeEntityPromise(entity.tag),
serializeEntityPromise(entity.workCharacter),
]);
return {
id: entity.id,
weight: entity.weight,
tag,
workCharacter,
};
}
public deserialize(partial: Partial<CharacterTagSerializedInterface>): Partial<CharacterTagEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.weight ? { weight: partial.weight } : {}),
...(partial.tag ? { tag: getDeserializedEntityPromise(partial.tag, Tag) } : {}),
...(partial.workCharacter
? { workCharacter: getDeserializedEntityPromise(partial.workCharacter, WorkCharacter) }
: {}),
};
}
}

View File

@ -0,0 +1,23 @@
import { injectable } from 'inversify';
import { Collection } from '../../../entities/library/collection';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class CollectionNameSerializer extends Serializer<
CollectionNameEntityInterface,
CollectionNameSerializedInterface
> {
public serialize(entity: CollectionNameEntityInterface): Promise<CollectionNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(partial: Partial<CollectionNameSerializedInterface>): Partial<CollectionNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, Collection) } : {}),
};
}
}

View File

@ -0,0 +1,35 @@
import { injectable } from 'inversify';
import { Collection } from '../../../entities/library/collection';
import { Work } from '../../../entities/library/work';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeEntityPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class CollectionPartSerializer extends Serializer<
CollectionPartEntityInterface,
CollectionPartSerializedInterface
> {
public async serialize(entity: CollectionPartEntityInterface): Promise<CollectionPartSerializedInterface> {
const [collection, work] = await Promise.all([
serializeEntityPromise(entity.collection),
serializeEntityPromise(entity.work),
]);
return {
id: entity.id,
order: entity.order,
collection,
work,
};
}
public deserialize(partial: Partial<CollectionPartSerializedInterface>): Partial<CollectionPartEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.order ? { order: partial.order } : {}),
...(partial.collection ? { collection: getDeserializedEntityPromise(partial.collection, Collection) } : {}),
...(partial.work ? { work: getDeserializedEntityPromise(partial.work, Work) } : {}),
};
}
}

View File

@ -0,0 +1,32 @@
import { injectable } from 'inversify';
import { CollectionName } from '../../../entities/library/collection-name';
import { CollectionPart } from '../../../entities/library/collection-part';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class CollectionSerializer extends Serializer<CollectionEntityInterface, CollectionSerializedInterface> {
public async serialize(entity: CollectionEntityInterface): Promise<CollectionSerializedInterface> {
const [names, parts] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.parts),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
names,
parts,
};
}
public deserialize(partial: Partial<CollectionSerializedInterface>): Partial<CollectionEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, CollectionName) } : {}),
...(partial.parts ? { parts: getDeserializedEntitiesPromise(partial.parts, CollectionPart) } : {}),
};
}
}

View File

@ -0,0 +1,36 @@
import { injectable } from 'inversify';
import { Source } from '../../../entities/library/source';
import { Work } from '../../../entities/library/work';
import { getDeserializedEntitiesPromise, getDeserializedEntityPromise } from '../get-deserialized';
import { serializeEntitiesPromise, serializeEntityPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class CopySerializer extends Serializer<CopyEntityInterface, CopySerializedInterface> {
public async serialize(entity: CopyEntityInterface): Promise<CopySerializedInterface> {
const [original, sources] = await Promise.all([
serializeEntityPromise(entity.original),
serializeEntitiesPromise(entity.sources),
]);
return {
id: entity.id,
hash: entity.hash,
location: entity.location,
ranking: entity.ranking,
original,
sources,
};
}
public deserialize(partial: Partial<CopySerializedInterface>): Partial<CopyEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.hash ? { hash: partial.hash } : {}),
...(partial.location ? { location: partial.location } : {}),
...(partial.ranking ? { ranking: partial.ranking } : {}),
...(partial.original ? { original: getDeserializedEntityPromise(partial.original, Work) } : {}),
...(partial.sources ? { sources: getDeserializedEntitiesPromise(partial.sources, Source) } : {}),
};
}
}

View File

@ -0,0 +1,42 @@
import { injectable } from 'inversify';
import { Tag } from '../../../entities/library/tag';
import { WorkCharacter } from '../../../entities/library/work-character';
import { getDeserializedEntitiesPromise, getDeserializedEntityPromise } from '../get-deserialized';
import { serializeEntitiesPromise, serializeEntityPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
injectable();
export class InteractionTagSerializer extends Serializer<
InteractionTagEntityInterface,
InteractionTagSerializedInterface
> {
public async serialize(entity: InteractionTagEntityInterface): Promise<InteractionTagSerializedInterface> {
const [tag, objectCharacters, subjectCharacters] = await Promise.all([
serializeEntityPromise(entity.tag),
serializeEntitiesPromise(entity.objectCharacters),
serializeEntitiesPromise(entity.subjectCharacters),
]);
return {
id: entity.id,
weight: entity.weight,
tag,
objectCharacters,
subjectCharacters,
};
}
public deserialize(partial: Partial<InteractionTagSerializedInterface>): Partial<InteractionTagEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.weight ? { weight: partial.weight } : {}),
...(partial.tag ? { tag: getDeserializedEntityPromise(partial.tag, Tag) } : {}),
...(partial.objectCharacters
? { objectCharacters: getDeserializedEntitiesPromise(partial.objectCharacters, WorkCharacter) }
: {}),
...(partial.subjectCharacters
? { subjectCharacters: getDeserializedEntitiesPromise(partial.subjectCharacters, WorkCharacter) }
: {}),
};
}
}

View File

@ -0,0 +1,24 @@
import { injectable } from 'inversify';
import { Work } from '../../../entities/library/work';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise.js';
import { Serializer } from '../serializer.js';
@injectable()
export class LanguageSerializer extends Serializer<LanguageEntityInterface, LanguageSerializedInterface, string> {
public async serialize(entity: LanguageEntityInterface): Promise<LanguageSerializedInterface> {
return {
id: entity.code,
code: entity.code,
works: await serializeEntitiesPromise(entity.works),
};
}
public deserialize(partial: Partial<LanguageSerializedInterface>): Partial<LanguageEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.code ? { code: partial.code } : {}),
...(partial.works ? { works: getDeserializedEntitiesPromise(partial.works, Work) } : {}),
};
}
}

View File

@ -0,0 +1,20 @@
import { injectable } from 'inversify';
import { Site } from '../../../entities/library/site';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class SiteNameSerializer extends Serializer<SiteNameEntityInterface, SiteNameSerializedInterface> {
public serialize(entity: SiteNameEntityInterface): Promise<SiteNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(partial: Partial<SiteNameSerializedInterface>): Partial<SiteNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, Site) } : {}),
};
}
}

View File

@ -0,0 +1,32 @@
import { injectable } from 'inversify';
import { SiteName } from '../../../entities/library/site-name';
import { Source } from '../../../entities/library/source';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class SiteSerializer extends Serializer<SiteEntityInterface, SiteSerializedInterface> {
public async serialize(entity: SiteEntityInterface): Promise<SiteSerializedInterface> {
const [names, sources] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.sources),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
names,
sources,
};
}
public deserialize(partial: Partial<SiteSerializedInterface>): Partial<SiteEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, SiteName) } : {}),
...(partial.sources ? { sources: getDeserializedEntitiesPromise(partial.sources, Source) } : {}),
};
}
}

View File

@ -0,0 +1,32 @@
import { injectable } from 'inversify';
import { Copy } from '../../../entities/library/copy';
import { Site } from '../../../entities/library/site';
import { getDeserializedEntitiesPromise, getDeserializedEntityPromise } from '../get-deserialized';
import { serializeEntitiesPromise, serializeEntityPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
injectable();
export class SourceSerializer extends Serializer<SourceEntityInterface, SourceSerializedInterface> {
public async serialize(entity: SourceEntityInterface): Promise<SourceSerializedInterface> {
const [copies, site] = await Promise.all([
serializeEntitiesPromise(entity.copies),
serializeEntityPromise(entity.site),
]);
return {
id: entity.id,
uri: entity.uri,
copies,
site,
};
}
public deserialize(partial: Partial<SourceSerializedInterface>): Partial<SourceEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.uri ? { uri: partial.uri } : {}),
...(partial.site ? { site: getDeserializedEntityPromise(partial.site, Site) } : {}),
...(partial.copies ? { copies: getDeserializedEntitiesPromise(partial.copies, Copy) } : {}),
};
}
}

View File

@ -0,0 +1,20 @@
import { injectable } from 'inversify';
import { Tag } from '../../../entities/library/tag';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class TagNameSerializer extends Serializer<TagNameEntityInterface, TagNameSerializedInterface> {
public serialize(entity: TagNameEntityInterface): Promise<TagNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(partial: Partial<TagNameSerializedInterface>): Partial<TagNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, Tag) } : {}),
};
}
}

View File

@ -0,0 +1,53 @@
import { injectable } from 'inversify';
import { CharacterTag } from '../../../entities/library/character-tag';
import { InteractionTag } from '../../../entities/library/interaction-tag';
import { Tag } from '../../../entities/library/tag';
import { TagName } from '../../../entities/library/tag-name';
import { WorkTag } from '../../../entities/library/work-tag';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class TagSerializer extends Serializer<TagEntityInterface, TagSerializedInterface> {
public async serialize(entity: TagEntityInterface): Promise<TagSerializedInterface> {
const [names, characterTags, children, parents, interactionTags, workTags] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.characterTags),
serializeEntitiesPromise(entity.children),
serializeEntitiesPromise(entity.parents),
serializeEntitiesPromise(entity.interactionTags),
serializeEntitiesPromise(entity.workTags),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
description: entity.description,
names,
characterTags,
children,
parents,
interactionTags,
workTags,
};
}
public deserialize(partial: Partial<TagSerializedInterface>): Partial<TagEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.description ? { description: partial.description } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, TagName) } : {}),
...(partial.children ? { children: getDeserializedEntitiesPromise(partial.children, Tag) } : {}),
...(partial.parents ? { parents: getDeserializedEntitiesPromise(partial.parents, Tag) } : {}),
...(partial.workTags ? { workTags: getDeserializedEntitiesPromise(partial.workTags, WorkTag) } : {}),
...(partial.interactionTags
? { interactionTags: getDeserializedEntitiesPromise(partial.interactionTags, InteractionTag) }
: {}),
...(partial.characterTags
? { characterTags: getDeserializedEntitiesPromise(partial.characterTags, CharacterTag) }
: {}),
};
}
}

View File

@ -0,0 +1,38 @@
import { injectable } from 'inversify';
import { TransformationType } from '../../../entities/library/transformation-type';
import { Work } from '../../../entities/library/work';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeEntityPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class TransformationSerializer extends Serializer<
TransformationEntityInterface,
TransformationSerializedInterface
> {
public async serialize(entity: TransformationEntityInterface): Promise<TransformationSerializedInterface> {
const [byWork, ofWork, type] = await Promise.all([
serializeEntityPromise(entity.byWork),
serializeEntityPromise(entity.ofWork),
serializeEntityPromise(entity.type),
]);
return {
id: entity.id,
order: entity.order,
byWork,
ofWork,
type,
};
}
public deserialize(partial: Partial<TransformationSerializedInterface>): Partial<TransformationEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.order ? { order: partial.order } : {}),
...(partial.type ? { type: getDeserializedEntityPromise(partial.type, TransformationType) } : {}),
...(partial.ofWork ? { ofWork: getDeserializedEntityPromise(partial.ofWork, Work) } : {}),
...(partial.byWork ? { byWork: getDeserializedEntityPromise(partial.byWork, Work) } : {}),
};
}
}

View File

@ -0,0 +1,25 @@
import { injectable } from 'inversify';
import { TransformationType } from '../../../entities/library/transformation-type';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class TransformationTypeNameSerializer extends Serializer<
TransformationTypeNameEntityInterface,
TransformationTypeNameSerializedInterface
> {
public serialize(entity: TransformationTypeNameEntityInterface): Promise<TransformationTypeNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(
partial: Partial<TransformationTypeNameSerializedInterface>,
): Partial<TransformationTypeNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, TransformationType) } : {}),
};
}
}

View File

@ -0,0 +1,43 @@
import { injectable } from 'inversify';
import { Transformation } from '../../../entities/library/transformation';
import { TransformationTypeName } from '../../../entities/library/transformation-type-name';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class TransformationTypeSerializer extends Serializer<
TransformationTypeEntityInterface,
TransformationTypeSerializedInterface
> {
public async serialize(entity: TransformationTypeEntityInterface): Promise<TransformationTypeSerializedInterface> {
const [names, transformations] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.transformations),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
description: entity.description,
conservesTags: entity.conservesTags,
names,
transformations,
};
}
public deserialize(
partial: Partial<TransformationTypeSerializedInterface>,
): Partial<TransformationTypeEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.description ? { description: partial.description } : {}),
...(partial.conservesTags ? { conservesTags: partial.conservesTags } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, TransformationTypeName) } : {}),
...(partial.transformations
? { transformations: getDeserializedEntitiesPromise(partial.transformations, Transformation) }
: {}),
};
}
}

View File

@ -0,0 +1,36 @@
import { injectable } from 'inversify';
import { Author } from '../../../entities/library/author';
import { AuthorRole } from '../../../entities/library/author-role';
import { Work } from '../../../entities/library/work';
import { getDeserializedEntitiesPromise, getDeserializedEntityPromise } from '../get-deserialized';
import { serializeEntitiesPromise, serializeEntityPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class WorkAuthorSerializer extends Serializer<WorkAuthorEntityInterface, WorkAuthorSerializedInterface> {
public async serialize(entity: WorkAuthorEntityInterface): Promise<WorkAuthorSerializedInterface> {
const [author, work, authorRoles] = await Promise.all([
serializeEntityPromise(entity.author),
serializeEntityPromise(entity.work),
serializeEntitiesPromise(entity.authorRoles),
]);
return {
id: entity.id,
order: entity.order,
author,
work,
authorRoles,
};
}
public deserialize(partial: Partial<WorkAuthorSerializedInterface>): Partial<WorkAuthorEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.work ? { order: partial.work } : {}),
...(partial.work ? { work: getDeserializedEntityPromise(partial.work, Work) } : {}),
...(partial.author ? { author: getDeserializedEntityPromise(partial.author, Author) } : {}),
...(partial.authorRoles ? { authorRoles: getDeserializedEntitiesPromise(partial.authorRoles, AuthorRole) } : {}),
};
}
}

View File

@ -0,0 +1,25 @@
import { injectable } from 'inversify';
import { WorkCharacter } from '../../../entities/library/work-character';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class WorkCharacterNameSerializer extends Serializer<
WorkCharacterNameEntityInterface,
WorkCharacterNameSerializedInterface
> {
public serialize(entity: WorkCharacterNameEntityInterface): Promise<WorkCharacterNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(
partial: Partial<WorkCharacterNameSerializedInterface>,
): Partial<WorkCharacterNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, WorkCharacter) } : {}),
};
}
}

View File

@ -0,0 +1,58 @@
import { injectable } from 'inversify';
import { CharacterTag } from '../../../entities/library/character-tag';
import { InteractionTag } from '../../../entities/library/interaction-tag';
import { Work } from '../../../entities/library/work';
import { WorkCharacterName } from '../../../entities/library/work-character-name';
import { WorldCharacter } from '../../../entities/library/world-character';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class WorkCharacterSerializer extends Serializer<
WorkCharacterEntityInterface,
WorkCharacterSerializedInterface
> {
public async serialize(entity: WorkCharacterEntityInterface): Promise<WorkCharacterSerializedInterface> {
const [names, characterTags, interactedBy, works, interactWith, worldCharacters] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.characterTags),
serializeEntitiesPromise(entity.interactedBy),
serializeEntitiesPromise(entity.works),
serializeEntitiesPromise(entity.interactWith),
serializeEntitiesPromise(entity.worldCharacters),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
names,
characterTags,
interactedBy,
works,
interactWith,
worldCharacters,
};
}
public deserialize(partial: Partial<WorkCharacterSerializedInterface>): Partial<WorkCharacterEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, WorkCharacterName) } : {}),
...(partial.works ? { works: getDeserializedEntitiesPromise(partial.works, Work) } : {}),
...(partial.characterTags
? { characterTags: getDeserializedEntitiesPromise(partial.characterTags, CharacterTag) }
: {}),
...(partial.worldCharacters
? { worldCharacters: getDeserializedEntitiesPromise(partial.worldCharacters, WorldCharacter) }
: {}),
...(partial.interactedBy
? { interactedBy: getDeserializedEntitiesPromise(partial.interactedBy, InteractionTag) }
: {}),
...(partial.interactWith
? { interactWith: getDeserializedEntitiesPromise(partial.interactWith, InteractionTag) }
: {}),
};
}
}

View File

@ -0,0 +1,20 @@
import { injectable } from 'inversify';
import { Work } from '../../../entities/library/work';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class WorkNameSerializer extends Serializer<WorkNameEntityInterface, WorkNameSerializedInterface> {
public serialize(entity: WorkNameEntityInterface): Promise<WorkNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(partial: Partial<WorkNameSerializedInterface>): Partial<WorkNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, Work) } : {}),
};
}
}

View File

@ -0,0 +1,82 @@
import { injectable } from 'inversify';
import { Copy } from '../../../entities/library/copy';
import { Transformation } from '../../../entities/library/transformation';
import { WorkAuthor } from '../../../entities/library/work-author';
import { WorkCharacter } from '../../../entities/library/work-character';
import { WorkName } from '../../../entities/library/work-name';
import { WorkTag } from '../../../entities/library/work-tag';
import { World } from '../../../entities/library/world';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise.js';
import { Serializer } from '../serializer.js';
@injectable()
export class WorkSerializer extends Serializer<WorkEntityInterface, WorkSerializedInterface> {
public async serialize(entity: WorkEntityInterface): Promise<WorkSerializedInterface> {
const [
languages,
collectionParts,
copies,
names,
transformationOf,
transformedBy,
workAuthors,
workCharacters,
workTags,
worlds,
] = await Promise.all([
serializeEntitiesPromise(entity.languages),
serializeEntitiesPromise(entity.collectionParts),
serializeEntitiesPromise(entity.copies),
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.transformationOf),
serializeEntitiesPromise(entity.transformedBy),
serializeEntitiesPromise(entity.workAuthors),
serializeEntitiesPromise(entity.workCharacters),
serializeEntitiesPromise(entity.workTags),
serializeEntitiesPromise(entity.worlds),
]);
return {
id: entity.id,
isCanonical: entity.isCanonical,
nameCanonical: entity.nameCanonical,
rating: entity.rating,
releaseDate: entity.releaseDate,
languages,
collectionParts,
copies,
names,
transformationOf,
transformedBy,
workAuthors,
workCharacters,
workTags,
worlds,
};
}
public deserialize(partial: Partial<WorkSerializedInterface>): Partial<WorkEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.rating ? { rating: partial.rating } : {}),
...(partial.releaseDate ? { releaseDate: partial.releaseDate } : {}),
...(partial.isCanonical ? { isCanonical: partial.isCanonical } : {}),
...(partial.copies ? { copies: getDeserializedEntitiesPromise(partial.copies, Copy) } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, WorkName) } : {}),
...(partial.transformationOf
? { transformationOf: getDeserializedEntitiesPromise(partial.transformationOf, Transformation) }
: {}),
...(partial.transformedBy
? { transformedBy: getDeserializedEntitiesPromise(partial.transformedBy, Transformation) }
: {}),
...(partial.workAuthors ? { workAuthors: getDeserializedEntitiesPromise(partial.workAuthors, WorkAuthor) } : {}),
...(partial.workCharacters
? { workCharacters: getDeserializedEntitiesPromise(partial.workCharacters, WorkCharacter) }
: {}),
...(partial.workTags ? { workTags: getDeserializedEntitiesPromise(partial.workTags, WorkTag) } : {}),
...(partial.worlds ? { worlds: getDeserializedEntitiesPromise(partial.worlds, World) } : {}),
};
}
}

View File

@ -0,0 +1,29 @@
import { injectable } from 'inversify';
import { Tag } from '../../../entities/library/tag';
import { Work } from '../../../entities/library/work';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeEntityPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class WorkTagSerializer extends Serializer<WorkTagEntityInterface, WorkTagSerializedInterface> {
public async serialize(entity: WorkTagEntityInterface): Promise<WorkTagSerializedInterface> {
const [tag, work] = await Promise.all([serializeEntityPromise(entity.tag), serializeEntityPromise(entity.work)]);
return {
id: entity.id,
weight: entity.weight,
tag,
work,
};
}
public deserialize(partial: Partial<WorkTagSerializedInterface>): Partial<WorkTagEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.weight ? { weight: partial.weight } : {}),
...(partial.tag ? { tag: getDeserializedEntityPromise(partial.tag, Tag) } : {}),
...(partial.work ? { work: getDeserializedEntityPromise(partial.work, Work) } : {}),
};
}
}

View File

@ -0,0 +1,25 @@
import { injectable } from 'inversify';
import { WorldCharacter } from '../../../entities/library/world-character';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class WorldCharacterNameSerializer extends Serializer<
WorldCharacterNameEntityInterface,
WorldCharacterNameSerializedInterface
> {
public serialize(entity: WorldCharacterNameEntityInterface): Promise<WorldCharacterNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(
partial: Partial<WorldCharacterNameSerializedInterface>,
): Partial<WorldCharacterNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, WorldCharacter) } : {}),
};
}
}

View File

@ -0,0 +1,48 @@
import { injectable } from 'inversify';
import { WorkCharacter } from '../../../entities/library/work-character';
import { World } from '../../../entities/library/world';
import { WorldCharacter } from '../../../entities/library/world-character';
import { WorldCharacterName } from '../../../entities/library/world-character-name';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class WorldCharacterSerializer extends Serializer<
WorldCharacterEntityInterface,
WorldCharacterSerializedInterface
> {
public async serialize(entity: WorldCharacterEntityInterface): Promise<WorldCharacterSerializedInterface> {
const [names, children, parents, workCharacters, worlds] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.children),
serializeEntitiesPromise(entity.parents),
serializeEntitiesPromise(entity.workCharacters),
serializeEntitiesPromise(entity.worlds),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
names,
children,
parents,
workCharacters,
worlds,
};
}
public deserialize(partial: Partial<WorldCharacterSerializedInterface>): Partial<WorldCharacterEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, WorldCharacterName) } : {}),
...(partial.children ? { children: getDeserializedEntitiesPromise(partial.children, WorldCharacter) } : {}),
...(partial.parents ? { parents: getDeserializedEntitiesPromise(partial.parents, WorldCharacter) } : {}),
...(partial.workCharacters
? { workCharacters: getDeserializedEntitiesPromise(partial.workCharacters, WorkCharacter) }
: {}),
...(partial.worlds ? { worlds: getDeserializedEntitiesPromise(partial.worlds, World) } : {}),
};
}
}

View File

@ -0,0 +1,20 @@
import { injectable } from 'inversify';
import { World } from '../../../entities/library/world';
import { getDeserializedEntityPromise } from '../get-deserialized';
import { serializeName } from '../serialize-name';
import { Serializer } from '../serializer';
@injectable()
export class WorldNameSerializer extends Serializer<WorldNameEntityInterface, WorldNameSerializedInterface> {
public serialize(entity: WorldNameEntityInterface): Promise<WorldNameSerializedInterface> {
return serializeName(entity);
}
public deserialize(partial: Partial<WorldNameSerializedInterface>): Partial<WorldNameEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.name ? { name: partial.name } : {}),
...(partial.entity ? { entity: getDeserializedEntityPromise(partial.entity, World) } : {}),
};
}
}

View File

@ -0,0 +1,45 @@
import { injectable } from 'inversify';
import { Work } from '../../../entities/library/work';
import { World } from '../../../entities/library/world';
import { WorldCharacter } from '../../../entities/library/world-character';
import { WorldName } from '../../../entities/library/world-name';
import { getDeserializedEntitiesPromise } from '../get-deserialized';
import { serializeEntitiesPromise } from '../serialize-entity-promise';
import { Serializer } from '../serializer';
@injectable()
export class WorldSerializer extends Serializer<WorldEntityInterface, WorldSerializedInterface> {
public async serialize(entity: WorldEntityInterface): Promise<WorldSerializedInterface> {
const [names, children, parents, works, worldCharacters] = await Promise.all([
serializeEntitiesPromise(entity.names),
serializeEntitiesPromise(entity.children),
serializeEntitiesPromise(entity.parents),
serializeEntitiesPromise(entity.works),
serializeEntitiesPromise(entity.worldCharacters),
]);
return {
id: entity.id,
nameCanonical: entity.nameCanonical,
names,
children,
parents,
works,
worldCharacters,
};
}
public deserialize(partial: Partial<WorldSerializedInterface>): Partial<WorldEntityInterface> {
return {
...(partial.id ? { id: partial.id } : {}),
...(partial.nameCanonical ? { nameCanonical: partial.nameCanonical } : {}),
...(partial.names ? { names: getDeserializedEntitiesPromise(partial.names, WorldName) } : {}),
...(partial.children ? { children: getDeserializedEntitiesPromise(partial.children, World) } : {}),
...(partial.parents ? { parents: getDeserializedEntitiesPromise(partial.parents, World) } : {}),
...(partial.works ? { works: getDeserializedEntitiesPromise(partial.works, Work) } : {}),
...(partial.worldCharacters
? { worldCharacters: getDeserializedEntitiesPromise(partial.worldCharacters, WorldCharacter) }
: {}),
};
}
}

View File

@ -11,7 +11,7 @@ function stringifyCspHeader(csp: ContentSecurityPolicy): string {
return Object.entries(csp) return Object.entries(csp)
.map( .map(
(directive: [string, Session.CspValue[] | undefined]) => (directive: [string, Session.CspValue[] | undefined]) =>
`${directive[0]} ${directive[1] ? directive[1]?.join(' ') : ''}` `${directive[0]} ${directive[1] ? directive[1]?.join(' ') : ''}`,
) )
.join('; '); .join('; ');
} }

View File

@ -1,12 +1,12 @@
import { expect } from 'chai'; import { expect } from 'chai';
import 'mocha'; import 'mocha';
import { container } from '../../core/container'; import { container, Service } from '../../core/container';
describe('Store Service', function () { describe('Store Service', function () {
this.timeout(10000); this.timeout(10000);
it('loads saved data', () => { it('loads saved data', () => {
const store: StoreInterface = container.get('store'); const store: StoreInterface = container.get(Service.STORE);
const testData = { const testData = {
something: 'gaga', something: 'gaga',
somethingElse: 0, somethingElse: 0,

View File

@ -1,5 +1,5 @@
import App from './renderer/App.svelte'; import App from './renderer/App.svelte';
new App({ new App({
target: document.querySelector('#app'), target: document.querySelector('#app') as Element,
}); });

View File

@ -3,6 +3,11 @@
import NhentaiSaveFavorites from './components/modules/NhentaiSaveFavorites.svelte'; import NhentaiSaveFavorites from './components/modules/NhentaiSaveFavorites.svelte';
</script> </script>
<main>
<NhentaiSaveFavorites />
<NhentaiGetWork />
</main>
<style> <style>
:global(:root) { :global(:root) {
--color-white: #fff; --color-white: #fff;
@ -43,8 +48,3 @@
height: 100%; height: 100%;
} }
</style> </style>
<main>
<NhentaiSaveFavorites></NhentaiSaveFavorites>
<NhentaiGetWork />
</main>

View File

@ -1,16 +1,48 @@
<script> <script>
import { onMount } from 'svelte'; import { onDestroy } from 'svelte';
import { entityApi } from '../../services/api'; import { workRepository } from '../../store/repositories/entities/work-repository';
export let id; export let id = 0;
/** @type {WorkSerializedInterface | undefined} */
let work; let work;
let unsubscribe = () => {};
onMount(() => { $: unsubscribe = workRepository.subscribe(id, (serialized) => {
entityApi.fetchWork(id).then((workSerialized) => { work = serialized;
work = workSerialized; });
});
onDestroy(() => {
unsubscribe();
}); });
</script> </script>
<div class="work">{#if work}{JSON.stringify(work)}{/if}</div> <div class="work">
{#if work}
<dl>
<div>
<dt>id</dt>
<dd>{work?.id}</dd>
</div>
<div>
<dt>name</dt>
<dd>{work?.nameCanonical}</dd>
</div>
<div>
<input
type="text"
on:change="{(input) => {
workRepository.update(id, (w) => {
w.nameCanonical = input.target.value;
return w;
});
}}"
/>
</div>
<div>
<dt>languages</dt>
<dd>{work?.languages.join(', ')}</dd>
</div>
</dl>
{/if}
</div>

View File

@ -1,3 +1,7 @@
<button class="button" on:click|preventDefault>
<slot />
</button>
<style> <style>
.button { .button {
border: none; border: none;
@ -9,7 +13,3 @@
outline-color: var(--color-accent-light); outline-color: var(--color-accent-light);
} }
</style> </style>
<button class="button" on:click|preventDefault>
<slot></slot>
</button>

View File

@ -1,8 +1,8 @@
<script> <script>
import Work from '../content/Work.svelte'; import { t } from '../../../shared/services/translation/t';
import { nhentaiGetWork } from '../../services/api'; import { nhentaiGetWork } from '../../services/api';
import Work from '../content/Work.svelte';
import SvelteButton from '../elements/SvelteButton.svelte'; import SvelteButton from '../elements/SvelteButton.svelte';
import { t } from '../../services/utils';
let galleryId; let galleryId;
let work; let work;
@ -13,9 +13,10 @@
</script> </script>
<div class="nhentai-get-work"> <div class="nhentai-get-work">
<label><input type="text" placeholder="177013" bind:value="{galleryId}" /></label <input type="text" placeholder="177013" bind:value="{galleryId}" /><SvelteButton on:click="{handleClick}"
><SvelteButton on:click="{handleClick}">{t('Get')}</SvelteButton> >{t('labels.actions.get')}</SvelteButton
>
{#if work} {#if work}
<Work id="{work.id}" /> <Work id="{work.id}" />
{/if} {/if}
</div> </div>

View File

@ -1,15 +1,13 @@
<script> <script>
import { t } from '../../services/utils'; import { t } from '../../../shared/services/translation/t';
import SvelteButton from '../elements/SvelteButton.svelte';
import { nhentaiSaveFavorites } from '../../services/api'; import { nhentaiSaveFavorites } from '../../services/api';
import SvelteButton from '../elements/SvelteButton.svelte';
function handleClick() { function handleClick() {
nhentaiSaveFavorites(); nhentaiSaveFavorites();
} }
</script> </script>
<style></style>
<div class="nhentai-login"> <div class="nhentai-login">
<SvelteButton on:click="{handleClick}">{ t('Save nhentai Favorites') }</SvelteButton> <SvelteButton on:click="{handleClick}">{t('labels.actions.save_nhentai_favorites')}</SvelteButton>
</div> </div>

View File

@ -1,15 +1,493 @@
import { ipcClient } from './ipc-client'; import { ipcClient } from './ipc-client';
export function nhentaiSaveFavorites(): Promise<void> { export function nhentaiSaveFavorites(): Promise<void> {
return ipcClient.ask(IpcChannel.NHENTAI_SAVE_FAVORITES) as Promise<void>; return ipcClient.ask(IpcChannel.NHENTAI_SAVE_FAVORITES, undefined);
} }
export function nhentaiGetWork(galleryId: string): Promise<WorkEntityInterface> { export function nhentaiGetWork(galleryId: string): Promise<WorkEntityInterface> {
return ipcClient.ask(IpcChannel.NHENTAI_GET_WORK, { galleryId }) as Promise<WorkEntityInterface>; return ipcClient.ask(IpcChannel.NHENTAI_GET_WORK, { galleryId });
}
type IpcChannelEntityRead =
| IpcChannel.ENTITY_READ_AUTHOR
| IpcChannel.ENTITY_READ_AUTHOR_NAME
| IpcChannel.ENTITY_READ_AUTHOR_ROLE
| IpcChannel.ENTITY_READ_AUTHOR_ROLE_NAME
| IpcChannel.ENTITY_READ_CHARACTER_TAG
| IpcChannel.ENTITY_READ_COLLECTION
| IpcChannel.ENTITY_READ_COLLECTION_NAME
| IpcChannel.ENTITY_READ_COLLECTION_PART
| IpcChannel.ENTITY_READ_COPY
| IpcChannel.ENTITY_READ_INTERACTION_TAG
| IpcChannel.ENTITY_READ_LANGUAGE
| IpcChannel.ENTITY_READ_SITE
| IpcChannel.ENTITY_READ_SITE_NAME
| IpcChannel.ENTITY_READ_SOURCE
| IpcChannel.ENTITY_READ_TAG
| IpcChannel.ENTITY_READ_TAG_NAME
| IpcChannel.ENTITY_READ_TRANSFORMATION
| IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE
| IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE_NAME
| IpcChannel.ENTITY_READ_WORK_AUTHOR
| IpcChannel.ENTITY_READ_WORK_CHARACTER
| IpcChannel.ENTITY_READ_WORK_CHARACTER_NAME
| IpcChannel.ENTITY_READ_WORK
| IpcChannel.ENTITY_READ_WORK_NAME
| IpcChannel.ENTITY_READ_WORK_TAG
| IpcChannel.ENTITY_READ_WORLD
| IpcChannel.ENTITY_READ_WORLD_NAME
| IpcChannel.ENTITY_READ_WORLD_CHARACTER
| IpcChannel.ENTITY_READ_WORLD_CHARACTER_NAME;
/**
* when there are duplicate requests to the same entity, the existing promise is returned
*
* this promise is deleted once resolved, this is not a cache!
*/
const entityReadPromiseCollector: {
[channel in IpcChannelEntityRead]: {
[identifier in Identifier]?: Promise<IpcAnswer<channel>>;
};
} = {
[IpcChannel.ENTITY_READ_AUTHOR]: {},
[IpcChannel.ENTITY_READ_AUTHOR_NAME]: {},
[IpcChannel.ENTITY_READ_AUTHOR_ROLE]: {},
[IpcChannel.ENTITY_READ_AUTHOR_ROLE_NAME]: {},
[IpcChannel.ENTITY_READ_CHARACTER_TAG]: {},
[IpcChannel.ENTITY_READ_COLLECTION]: {},
[IpcChannel.ENTITY_READ_COLLECTION_NAME]: {},
[IpcChannel.ENTITY_READ_COLLECTION_PART]: {},
[IpcChannel.ENTITY_READ_COPY]: {},
[IpcChannel.ENTITY_READ_INTERACTION_TAG]: {},
[IpcChannel.ENTITY_READ_LANGUAGE]: {},
[IpcChannel.ENTITY_READ_SITE]: {},
[IpcChannel.ENTITY_READ_SITE_NAME]: {},
[IpcChannel.ENTITY_READ_SOURCE]: {},
[IpcChannel.ENTITY_READ_TAG]: {},
[IpcChannel.ENTITY_READ_TAG_NAME]: {},
[IpcChannel.ENTITY_READ_TRANSFORMATION]: {},
[IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE]: {},
[IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE_NAME]: {},
[IpcChannel.ENTITY_READ_WORK_AUTHOR]: {},
[IpcChannel.ENTITY_READ_WORK_CHARACTER]: {},
[IpcChannel.ENTITY_READ_WORK_CHARACTER_NAME]: {},
[IpcChannel.ENTITY_READ_WORK]: {},
[IpcChannel.ENTITY_READ_WORK_NAME]: {},
[IpcChannel.ENTITY_READ_WORK_TAG]: {},
[IpcChannel.ENTITY_READ_WORLD]: {},
[IpcChannel.ENTITY_READ_WORLD_NAME]: {},
[IpcChannel.ENTITY_READ_WORLD_CHARACTER]: {},
[IpcChannel.ENTITY_READ_WORLD_CHARACTER_NAME]: {},
};
function readCollected<T extends IpcChannelEntityRead>(channel: T, identifier: IpcParameter<T>): Promise<IpcAnswer<T>> {
if (entityReadPromiseCollector[channel][identifier] === undefined) {
// @ts-ignore -- no fucking clue how to type this correctly
entityReadPromiseCollector[channel][identifier] = ipcClient.ask<T>(channel, identifier).then((value) => {
delete entityReadPromiseCollector[channel][identifier];
return value;
});
}
return entityReadPromiseCollector[channel][identifier] as unknown as Promise<IpcAnswer<T>>;
} }
export const entityApi = { export const entityApi = {
fetchWork(id: number): Promise<WorkSerializedInterface> { createAuthor(data: Partial<AuthorSerializedInterface>): Promise<AuthorSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_GET_WORK, { id }); return ipcClient.ask(IpcChannel.ENTITY_CREATE_AUTHOR, data);
},
createAuthorName(data: Partial<AuthorNameSerializedInterface>): Promise<AuthorNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_AUTHOR_NAME, data);
},
createAuthorRole(data: Partial<AuthorRoleSerializedInterface>): Promise<AuthorRoleSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_AUTHOR_ROLE, data);
},
createAuthorRoleName(data: Partial<AuthorRoleNameSerializedInterface>): Promise<AuthorRoleNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_AUTHOR_ROLE_NAME, data);
},
createCharacterTag(data: Partial<CharacterTagSerializedInterface>): Promise<CharacterTagSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_CHARACTER_TAG, data);
},
createCollection(data: Partial<CollectionSerializedInterface>): Promise<CollectionSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_COLLECTION, data);
},
createCollectionName(data: Partial<CollectionNameSerializedInterface>): Promise<CollectionNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_COLLECTION_NAME, data);
},
createCollectionPart(data: Partial<CollectionPartSerializedInterface>): Promise<CollectionPartSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_COLLECTION_PART, data);
},
createCopy(data: Partial<CopySerializedInterface>): Promise<CopySerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_COPY, data);
},
createInteractionTag(data: Partial<InteractionTagSerializedInterface>): Promise<InteractionTagSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_INTERACTION_TAG, data);
},
createSite(data: Partial<SiteSerializedInterface>): Promise<SiteSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_SITE, data);
},
createSiteName(data: Partial<SiteNameSerializedInterface>): Promise<SiteNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_SITE_NAME, data);
},
createSource(data: Partial<SourceSerializedInterface>): Promise<SourceSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_SOURCE, data);
},
createTag(data: Partial<TagSerializedInterface>): Promise<TagSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_TAG, data);
},
createTagName(data: Partial<TagNameSerializedInterface>): Promise<TagNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_TAG_NAME, data);
},
createTransformation(data: Partial<TransformationSerializedInterface>): Promise<TransformationSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_TRANSFORMATION, data);
},
createTransformationType(
data: Partial<TransformationTypeSerializedInterface>,
): Promise<TransformationTypeSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE, data);
},
createTransformationTypeName(
data: Partial<TransformationTypeNameSerializedInterface>,
): Promise<TransformationTypeNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_TRANSFORMATION_TYPE_NAME, data);
},
createWorkAuthor(data: Partial<WorkAuthorSerializedInterface>): Promise<WorkAuthorSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORK_AUTHOR, data);
},
createWorkCharacter(data: Partial<WorkCharacterSerializedInterface>): Promise<WorkCharacterSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORK_CHARACTER, data);
},
createWorkCharacterName(
data: Partial<WorkCharacterNameSerializedInterface>,
): Promise<WorkCharacterNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORK_CHARACTER_NAME, data);
},
createWork(data: Partial<WorkSerializedInterface>): Promise<WorkSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORK, data);
},
createWorkName(data: Partial<WorkNameSerializedInterface>): Promise<WorkNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORK_NAME, data);
},
createWorkTag(data: Partial<WorkTagSerializedInterface>): Promise<WorkTagSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORK_TAG, data);
},
createWorld(data: Partial<WorldSerializedInterface>): Promise<WorldSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORLD, data);
},
createWorldName(data: Partial<WorldNameSerializedInterface>): Promise<WorldNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORLD_NAME, data);
},
createWorldCharacter(data: Partial<WorldCharacterSerializedInterface>): Promise<WorldCharacterSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORLD_CHARACTER, data);
},
createWorldCharacterName(
data: Partial<WorldCharacterNameSerializedInterface>,
): Promise<WorldCharacterNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_CREATE_WORLD_CHARACTER_NAME, data);
},
readAuthor(id: number): Promise<AuthorSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_AUTHOR, id);
},
readAuthorName(id: number): Promise<AuthorNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_AUTHOR_NAME, id);
},
readAuthorRole(id: number): Promise<AuthorRoleSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_AUTHOR_ROLE, id);
},
readAuthorRoleName(id: number): Promise<AuthorRoleNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_AUTHOR_ROLE_NAME, id);
},
readCharacterTag(id: number): Promise<CharacterTagSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_CHARACTER_TAG, id);
},
readCollection(id: number): Promise<CollectionSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_COLLECTION, id);
},
readCollectionName(id: number): Promise<CollectionNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_COLLECTION_NAME, id);
},
readCollectionPart(id: number): Promise<CollectionPartSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_COLLECTION_PART, id);
},
readCopy(id: number): Promise<CopySerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_COPY, id);
},
readInteractionTag(id: number): Promise<InteractionTagSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_INTERACTION_TAG, id);
},
readLanguage(code: string): Promise<LanguageSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_LANGUAGE, code);
},
readSite(id: number): Promise<SiteSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_SITE, id);
},
readSiteName(id: number): Promise<SiteNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_SITE_NAME, id);
},
readSource(id: number): Promise<SourceSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_SOURCE, id);
},
readTag(id: number): Promise<TagSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_TAG, id);
},
readTagName(id: number): Promise<TagNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_TAG_NAME, id);
},
readTransformation(id: number): Promise<TransformationSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_TRANSFORMATION, id);
},
readTransformationType(id: number): Promise<TransformationTypeSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE, id);
},
readTransformationTypeName(id: number): Promise<TransformationTypeNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_TRANSFORMATION_TYPE_NAME, id);
},
readWorkAuthor(id: number): Promise<WorkAuthorSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORK_AUTHOR, id);
},
readWorkCharacter(id: number): Promise<WorkCharacterSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORK_CHARACTER, id);
},
readWorkCharacterName(id: number): Promise<WorkCharacterNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORK_CHARACTER_NAME, id);
},
readWork(id: number): Promise<WorkSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORK, id);
},
readWorkName(id: number): Promise<WorkNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORK_NAME, id);
},
readWorkTag(id: number): Promise<WorkTagSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORK_TAG, id);
},
readWorld(id: number): Promise<WorldSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORLD, id);
},
readWorldName(id: number): Promise<WorldNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORLD_NAME, id);
},
readWorldCharacter(id: number): Promise<WorldCharacterSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORLD_CHARACTER, id);
},
readWorldCharacterName(id: number): Promise<WorldCharacterNameSerializedInterface> {
return readCollected(IpcChannel.ENTITY_READ_WORLD_CHARACTER_NAME, id);
},
updateAuthor(id: number, partial: Partial<AuthorSerializedInterface>): Promise<AuthorSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_AUTHOR, { id, partial });
},
updateAuthorName(
id: number,
partial: Partial<AuthorNameSerializedInterface>,
): Promise<AuthorNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_AUTHOR_NAME, { id, partial });
},
updateAuthorRole(
id: number,
partial: Partial<AuthorRoleSerializedInterface>,
): Promise<AuthorRoleSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE, { id, partial });
},
updateAuthorRoleName(
id: number,
partial: Partial<AuthorRoleNameSerializedInterface>,
): Promise<AuthorRoleNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_AUTHOR_ROLE_NAME, { id, partial });
},
updateCharacterTag(
id: number,
partial: Partial<CharacterTagSerializedInterface>,
): Promise<CharacterTagSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_CHARACTER_TAG, { id, partial });
},
updateCollection(
id: number,
partial: Partial<CollectionSerializedInterface>,
): Promise<CollectionSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_COLLECTION, { id, partial });
},
updateCollectionName(
id: number,
partial: Partial<CollectionNameSerializedInterface>,
): Promise<CollectionNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_COLLECTION_NAME, { id, partial });
},
updateCollectionPart(
id: number,
partial: Partial<CollectionPartSerializedInterface>,
): Promise<CollectionPartSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_COLLECTION_PART, { id, partial });
},
updateCopy(id: number, partial: Partial<CopySerializedInterface>): Promise<CopySerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_COPY, { id, partial });
},
updateInteractionTag(
id: number,
partial: Partial<InteractionTagSerializedInterface>,
): Promise<InteractionTagSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_INTERACTION_TAG, { id, partial });
},
updateSite(id: number, partial: Partial<SiteSerializedInterface>): Promise<SiteSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_SITE, { id, partial });
},
updateSiteName(id: number, partial: Partial<SiteNameSerializedInterface>): Promise<SiteNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_SITE_NAME, { id, partial });
},
updateSource(id: number, partial: Partial<SourceSerializedInterface>): Promise<SourceSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_SOURCE, { id, partial });
},
updateTag(id: number, partial: Partial<TagSerializedInterface>): Promise<TagSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_TAG, { id, partial });
},
updateTagName(id: number, partial: Partial<TagNameSerializedInterface>): Promise<TagNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_TAG_NAME, { id, partial });
},
updateTransformation(
id: number,
partial: Partial<TransformationSerializedInterface>,
): Promise<TransformationSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_TRANSFORMATION, { id, partial });
},
updateTransformationType(
id: number,
partial: Partial<TransformationTypeSerializedInterface>,
): Promise<TransformationTypeSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE, { id, partial });
},
updateTransformationTypeName(
id: number,
partial: Partial<TransformationTypeNameSerializedInterface>,
): Promise<TransformationTypeNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_TRANSFORMATION_TYPE_NAME, { id, partial });
},
updateWorkAuthor(
id: number,
partial: Partial<WorkAuthorSerializedInterface>,
): Promise<WorkAuthorSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORK_AUTHOR, { id, partial });
},
updateWorkCharacter(
id: number,
partial: Partial<WorkCharacterSerializedInterface>,
): Promise<WorkCharacterSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORK_CHARACTER, { id, partial });
},
updateWorkCharacterName(
id: number,
partial: Partial<WorkCharacterNameSerializedInterface>,
): Promise<WorkCharacterNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORK_CHARACTER_NAME, { id, partial });
},
updateWork(id: number, partial: Partial<WorkSerializedInterface>): Promise<WorkSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORK, { id, partial });
},
updateWorkName(id: number, partial: Partial<WorkNameSerializedInterface>): Promise<WorkNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORK_NAME, { id, partial });
},
updateWorkTag(id: number, partial: Partial<WorkTagSerializedInterface>): Promise<WorkTagSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORK_TAG, { id, partial });
},
updateWorld(id: number, partial: Partial<WorldSerializedInterface>): Promise<WorldSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORLD, { id, partial });
},
updateWorldName(id: number, partial: Partial<WorldNameSerializedInterface>): Promise<WorldNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORLD_NAME, { id, partial });
},
updateWorldCharacter(
id: number,
partial: Partial<WorldCharacterSerializedInterface>,
): Promise<WorldCharacterSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER, { id, partial });
},
updateWorldCharacterName(
id: number,
partial: Partial<WorldCharacterNameSerializedInterface>,
): Promise<WorldCharacterNameSerializedInterface> {
return ipcClient.ask(IpcChannel.ENTITY_UPDATE_WORLD_CHARACTER_NAME, { id, partial });
},
deleteAuthor(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_AUTHOR, id);
},
deleteAuthorName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_AUTHOR_NAME, id);
},
deleteAuthorRole(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_AUTHOR_ROLE, id);
},
deleteAuthorRoleName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_AUTHOR_ROLE_NAME, id);
},
deleteCharacterTag(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_CHARACTER_TAG, id);
},
deleteCollection(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_COLLECTION, id);
},
deleteCollectionName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_COLLECTION_NAME, id);
},
deleteCollectionPart(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_COLLECTION_PART, id);
},
deleteCopy(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_COPY, id);
},
deleteInteractionTag(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_INTERACTION_TAG, id);
},
deleteSite(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_SITE, id);
},
deleteSiteName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_SITE_NAME, id);
},
deleteSource(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_SOURCE, id);
},
deleteTag(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_TAG, id);
},
deleteTagName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_TAG_NAME, id);
},
deleteTransformation(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_TRANSFORMATION, id);
},
deleteTransformationType(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_TRANSFORMATION_TYPE, id);
},
deleteTransformationTypeName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_TRANSFORMATION_TYPE_NAME, id);
},
deleteWorkAuthor(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORK_AUTHOR, id);
},
deleteWorkCharacter(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORK_CHARACTER, id);
},
deleteWorkCharacterName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORK_CHARACTER_NAME, id);
},
deleteWork(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORK, id);
},
deleteWorkName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORK_NAME, id);
},
deleteWorkTag(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORK_TAG, id);
},
deleteWorld(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORLD, id);
},
deleteWorldName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORLD_NAME, id);
},
deleteWorldCharacter(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORLD_CHARACTER, id);
},
deleteWorldCharacterName(id: number): Promise<void> {
return ipcClient.ask(IpcChannel.ENTITY_DELETE_WORLD_CHARACTER_NAME, id);
}, },
}; };

View File

@ -0,0 +1,5 @@
export abstract class EventBusEvent extends Event {
protected constructor(type: EventType, eventInit?: EventInit) {
super(type, eventInit);
}
}

Some files were not shown because too many files have changed in this diff Show More