update: upgrade eslint to major version 7 and rework the application to fit new rules
This commit is contained in:
parent
50fbdeb96c
commit
115782061d
|
@ -24,6 +24,7 @@
|
||||||
"max-classes-per-file": "error",
|
"max-classes-per-file": "error",
|
||||||
"object-shorthand": ["error", "methods"],
|
"object-shorthand": ["error", "methods"],
|
||||||
"no-useless-rename": "error",
|
"no-useless-rename": "error",
|
||||||
|
"prefer-promise-reject-errors": "error",
|
||||||
|
|
||||||
"import/no-extraneous-dependencies": [
|
"import/no-extraneous-dependencies": [
|
||||||
"error",
|
"error",
|
||||||
|
@ -65,22 +66,20 @@
|
||||||
"settings": {
|
"settings": {
|
||||||
"import/extensions": [".ts", "d.ts", ".js", ".json"],
|
"import/extensions": [".ts", "d.ts", ".js", ".json"],
|
||||||
"import/parsers": {
|
"import/parsers": {
|
||||||
"@typescript-eslint/parser": [".ts", "d.ts"]
|
"@typescript-eslint/parser": [".ts", ".d.ts"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-console": "error",
|
"no-console": "error",
|
||||||
"no-magic-numbers": "off",
|
"no-magic-numbers": "off",
|
||||||
|
|
||||||
"@typescript-eslint/no-unused-vars": "off",
|
"@typescript-eslint/ban-types": [
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/typedef": [
|
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
"arrowParameter": true,
|
"extendDefaults": true,
|
||||||
"memberVariableDeclaration": true,
|
"types": {
|
||||||
"parameter": true,
|
"object": false
|
||||||
"propertyDeclaration": true
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@typescript-eslint/explicit-function-return-type": "error",
|
"@typescript-eslint/explicit-function-return-type": "error",
|
||||||
|
@ -103,14 +102,30 @@
|
||||||
],
|
],
|
||||||
"@typescript-eslint/prefer-for-of": "error",
|
"@typescript-eslint/prefer-for-of": "error",
|
||||||
"@typescript-eslint/no-empty-function": "off",
|
"@typescript-eslint/no-empty-function": "off",
|
||||||
"@typescript-eslint/interface-name-prefix": ["error", { "prefixWithI": "always" }],
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "interface",
|
||||||
|
"format": ["PascalCase"],
|
||||||
|
"prefix": ["I"]
|
||||||
|
}
|
||||||
|
],
|
||||||
"@typescript-eslint/explicit-member-accessibility": "error",
|
"@typescript-eslint/explicit-member-accessibility": "error",
|
||||||
"@typescript-eslint/unbound-method": "off",
|
"@typescript-eslint/unbound-method": "off",
|
||||||
"@typescript-eslint/ban-ts-ignore": "off",
|
"@typescript-eslint/ban-ts-ignore": "off",
|
||||||
"@typescript-eslint/no-use-before-define": "off",
|
"@typescript-eslint/no-use-before-define": "off",
|
||||||
"@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }],
|
"@typescript-eslint/no-misused-promises": ["error", { "checksVoidReturn": false }],
|
||||||
"@typescript-eslint/member-ordering": "error",
|
"@typescript-eslint/member-ordering": "error",
|
||||||
"@typescript-eslint/array-type": ["error", { "default": "array-simple" }]
|
"@typescript-eslint/array-type": ["error", { "default": "array-simple" }],
|
||||||
|
"@typescript-eslint/restrict-template-expressions": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowNumber": true,
|
||||||
|
"allowBoolean": false,
|
||||||
|
"allowAny": false,
|
||||||
|
"allowNullish": false
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -123,7 +138,8 @@
|
||||||
"@typescript-eslint/no-magic-numbers": "off",
|
"@typescript-eslint/no-magic-numbers": "off",
|
||||||
"@typescript-eslint/typedef": "off",
|
"@typescript-eslint/typedef": "off",
|
||||||
"@typescript-eslint/no-var-requires": "off",
|
"@typescript-eslint/no-var-requires": "off",
|
||||||
"@typescript-eslint/explicit-function-return-type": "off"
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/ban-ts-comment": "off"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
declare module '*.json' {
|
|
||||||
const value: any;
|
|
||||||
export default value;
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
|
@ -52,50 +52,50 @@
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"sqlite3": "^4.2.0",
|
"sqlite3": "^5.0.0",
|
||||||
"typeorm": "^0.2.25",
|
"typeorm": "^0.2.25",
|
||||||
"uuid": "^7.0.3"
|
"uuid": "^7.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@electron-forge/cli": "^6.0.0-beta.51",
|
"@electron-forge/cli": "^6.0.0-beta.52",
|
||||||
"@electron-forge/maker-squirrel": "^6.0.0-beta.51",
|
"@electron-forge/maker-squirrel": "^6.0.0-beta.52",
|
||||||
"@types/chai": "^4.2.11",
|
"@types/chai": "^4.2.11",
|
||||||
"@types/fs-extra": "^9.0.1",
|
"@types/fs-extra": "^9.0.1",
|
||||||
"@types/jsdom": "^16.2.3",
|
"@types/jsdom": "^16.2.3",
|
||||||
"@types/minimist": "^1.2.0",
|
"@types/minimist": "^1.2.0",
|
||||||
"@types/mocha": "^7.0.2",
|
"@types/mocha": "^7.0.2",
|
||||||
"@types/node": "^12.12.44",
|
"@types/node": "^12.12.51",
|
||||||
"@types/node-fetch": "^2.5.7",
|
"@types/node-fetch": "^2.5.7",
|
||||||
"@types/sinon": "^9.0.4",
|
"@types/sinon": "^9.0.4",
|
||||||
"@types/uuid": "^7.0.4",
|
"@types/uuid": "^7.0.4",
|
||||||
"@types/webpack": "^4.41.17",
|
"@types/webpack": "^4.41.21",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.34.0",
|
"@typescript-eslint/eslint-plugin": "^3.7.0",
|
||||||
"@typescript-eslint/parser": "^2.34.0",
|
"@typescript-eslint/parser": "^3.7.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"chokidar": "^3.4.0",
|
"chokidar": "^3.4.1",
|
||||||
"concurrently": "^5.2.0",
|
"concurrently": "^5.2.0",
|
||||||
"electron": "^8.3.1",
|
"electron": "^8.4.0",
|
||||||
"electron-rebuild": "^1.11.0",
|
"electron-rebuild": "^1.11.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.5.0",
|
||||||
"eslint-config-prettier": "^6.11.0",
|
"eslint-config-prettier": "^6.11.0",
|
||||||
"eslint-plugin-import": "^2.20.2",
|
"eslint-plugin-import": "^2.22.0",
|
||||||
"fast-check": "^1.24.2",
|
"fast-check": "^1.26.0",
|
||||||
"handlebars": "^4.7.6",
|
"handlebars": "^4.7.6",
|
||||||
"husky": "^4.2.5",
|
"husky": "^4.2.5",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.19",
|
||||||
"mocha": "^7.2.0",
|
"mocha": "^7.2.0",
|
||||||
"nock": "^12.0.3",
|
"nock": "^12.0.3",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"prettier": "^2.0.5",
|
"prettier": "^2.0.5",
|
||||||
"rewiremock": "^3.14.2",
|
"rewiremock": "^3.14.3",
|
||||||
"sinon": "^9.0.2",
|
"sinon": "^9.0.2",
|
||||||
"spectron": "^10.0.1",
|
"spectron": "^10.0.1",
|
||||||
"svelte": "^3.23.0",
|
"svelte": "^3.24.0",
|
||||||
"svelte-loader": "^2.13.6",
|
"svelte-loader": "^2.13.6",
|
||||||
"ts-loader": "^7.0.5",
|
"ts-loader": "^7.0.5",
|
||||||
"typescript": "^3.9.5",
|
"typescript": "^3.9.7",
|
||||||
"webpack": "^4.43.0",
|
"webpack": "^4.43.0",
|
||||||
"webpack-cli": "^3.3.11"
|
"webpack-cli": "^3.3.12"
|
||||||
},
|
},
|
||||||
"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",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as electron from 'electron';
|
import * as electron from 'electron';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import { Context } from 'mocha';
|
||||||
import rewiremock from 'rewiremock';
|
import rewiremock from 'rewiremock';
|
||||||
|
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
@ -11,23 +12,33 @@ rewiremock.disable();
|
||||||
describe('Application @slow', function () {
|
describe('Application @slow', function () {
|
||||||
this.timeout(20000);
|
this.timeout(20000);
|
||||||
|
|
||||||
before(function () {
|
interface IApplicationContext extends Context {
|
||||||
this.app = new Application({
|
app: Application;
|
||||||
// @ts-ignore this does give the path to electron executable (hopefully platform agnostic)
|
}
|
||||||
path: electron.default,
|
|
||||||
|
before(function (this, done): void {
|
||||||
|
const context = this as IApplicationContext;
|
||||||
|
context.app = new Application({
|
||||||
|
// @ts-ignore this does give the path to electron executable when this script is running outside of electron (which it does in the test files)
|
||||||
|
path: ((electron as unknown) as { default: string }).default,
|
||||||
args: [packageJson.main],
|
args: [packageJson.main],
|
||||||
});
|
});
|
||||||
return this.app.start();
|
context.app
|
||||||
|
.start()
|
||||||
|
.then(() => done())
|
||||||
|
.catch((reason) => done(reason));
|
||||||
});
|
});
|
||||||
|
|
||||||
after(function () {
|
after(function (this) {
|
||||||
if (this.app && this.app.isRunning()) {
|
const context = this as IApplicationContext;
|
||||||
return this.app.stop();
|
if (context.app && context.app.isRunning()) {
|
||||||
|
return context.app.stop();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('shows an initial window', function () {
|
it('shows an initial window', function (this: Context) {
|
||||||
return this.app.client.getWindowCount().then((count: number) => {
|
const context = this as IApplicationContext;
|
||||||
|
return context.app.client.getWindowCount().then((count: number) => {
|
||||||
expect(count).to.be.gte(1);
|
expect(count).to.be.gte(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
10
src/main.ts
10
src/main.ts
|
@ -1,9 +1,4 @@
|
||||||
/* eslint-disable import/order */
|
/* eslint-disable import/order -- this is the entry point for the application and some things have to happen before others */
|
||||||
/**
|
|
||||||
* Disable Reasons
|
|
||||||
*
|
|
||||||
* 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 { container } from './main/core/container';
|
||||||
import './main/core/install';
|
import './main/core/install';
|
||||||
|
|
||||||
|
@ -23,7 +18,8 @@ async function createWindow(): Promise<void> {
|
||||||
|
|
||||||
// Open the DevTools.
|
// Open the DevTools.
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
appWindowMain.window.webContents.openDevTools();
|
// eslint-disable-next-line no-unused-expressions -- eslint can't handle optional chaining, yet
|
||||||
|
appWindowMain.window?.webContents.openDevTools();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,17 +2,28 @@ import 'reflect-metadata';
|
||||||
import { Container } from 'inversify';
|
import { Container } from 'inversify';
|
||||||
import { MainAppWindow } from '../modules/app-window/main-app-window';
|
import { MainAppWindow } from '../modules/app-window/main-app-window';
|
||||||
import { NhentaiApi } from '../modules/nhentai/nhentai-api';
|
import { NhentaiApi } from '../modules/nhentai/nhentai-api';
|
||||||
import { NhentaiIpcServer } from '../modules/nhentai/nhentai-ipc-server';
|
import '../modules/nhentai/nhentai-ipc-controller';
|
||||||
import { Session } from '../modules/session/session';
|
import { Session } from '../modules/session/session';
|
||||||
|
import { Store } from '../modules/store/store';
|
||||||
|
import { StoreMock } from '../modules/store/store.mock';
|
||||||
import { WebCrawler } from '../modules/web-crawler/web-crawler';
|
import { WebCrawler } from '../modules/web-crawler/web-crawler';
|
||||||
|
|
||||||
export const container = new Container({ defaultScope: 'Singleton' });
|
export const container = new Container({ defaultScope: 'Singleton' });
|
||||||
|
|
||||||
|
container.bind(Symbol.for('store')).to(Store);
|
||||||
|
export function mockStore(unMock = false): void {
|
||||||
|
if (unMock) {
|
||||||
|
container.unbind(Symbol.for('store'));
|
||||||
|
container.bind(Symbol.for('store')).to(Store);
|
||||||
|
} else {
|
||||||
|
container.unbind(Symbol.for('store'));
|
||||||
|
container.bind(Symbol.for('store')).to(StoreMock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
container.bind(Symbol.for('web-crawler')).to(WebCrawler);
|
container.bind(Symbol.for('web-crawler')).to(WebCrawler);
|
||||||
|
|
||||||
container.bind(Symbol.for('nhentai-api')).to(NhentaiApi);
|
container.bind(Symbol.for('nhentai-api')).to(NhentaiApi);
|
||||||
container.bind(Symbol.for('nhentai-ipc-server')).to(NhentaiIpcServer);
|
|
||||||
container.get(Symbol.for('nhentai-ipc-server'));
|
|
||||||
|
|
||||||
container.bind(Symbol.for('app-window-main')).to(MainAppWindow);
|
container.bind(Symbol.for('app-window-main')).to(MainAppWindow);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import '../../../mocks/electron';
|
||||||
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { Databases, getConnection } from './database';
|
import { Database, getConnection } from './database';
|
||||||
|
|
||||||
describe('Database Service', () => {
|
describe('Database Service', () => {
|
||||||
before(() => {
|
before(() => {
|
||||||
|
@ -15,7 +15,7 @@ describe('Database Service', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns a connection', async () => {
|
it('returns a connection', async () => {
|
||||||
const libraryConnection = await getConnection(Databases.LIBRARY);
|
const libraryConnection = await getConnection(Database.LIBRARY);
|
||||||
expect(libraryConnection).to.not.equal(undefined);
|
expect(libraryConnection).to.not.equal(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,48 +3,49 @@ import { Connection, createConnection as ormCreateConnection } from 'typeorm';
|
||||||
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions';
|
import { SqliteConnectionOptions } from 'typeorm/driver/sqlite/SqliteConnectionOptions';
|
||||||
import { appPath } from './app-path';
|
import { appPath } from './app-path';
|
||||||
|
|
||||||
export enum Databases {
|
export enum Database {
|
||||||
LIBRARY = 'library',
|
LIBRARY = 'library',
|
||||||
STORE = 'store',
|
STORE = 'store',
|
||||||
}
|
}
|
||||||
|
|
||||||
type MyConnectionOptions = { [key in Databases]?: SqliteConnectionOptions };
|
type MyConnectionOptions = { [key in Database]: SqliteConnectionOptions };
|
||||||
|
|
||||||
const databaseDir = path.resolve(appPath, 'database');
|
const databaseDir = path.resolve(appPath, 'database');
|
||||||
const connectionOptions: MyConnectionOptions = Object.values(Databases).reduce(
|
|
||||||
(prev: MyConnectionOptions, database: Databases) => {
|
|
||||||
prev[database] = {
|
|
||||||
name: database,
|
|
||||||
type: 'sqlite',
|
|
||||||
database: path.resolve(databaseDir, `${database}.db`),
|
|
||||||
cache: true,
|
|
||||||
entities: [`./src/main/entities/${database}/*.js`],
|
|
||||||
migrations: [`./src/main/migrations/${database}/*.js`],
|
|
||||||
cli: {
|
|
||||||
migrationsDir: `./src/main/migrations/${database}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
return prev;
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
|
|
||||||
const connections: {
|
function getConnectionOptionsFor(database: Database): SqliteConnectionOptions {
|
||||||
[key in Databases]?: Connection;
|
return {
|
||||||
} = {};
|
name: database,
|
||||||
|
type: 'sqlite',
|
||||||
function createConnection(database: Databases): Promise<Connection> {
|
database: path.resolve(databaseDir, `${database}.db`),
|
||||||
return ormCreateConnection(connectionOptions[database])
|
cache: true,
|
||||||
.then((connection: Connection) => {
|
entities: [`./src/main/entities/${database}/*.js`],
|
||||||
connections[database] = connection;
|
migrations: [`./src/main/migrations/${database}/*.js`],
|
||||||
return connection.runMigrations();
|
cli: {
|
||||||
})
|
migrationsDir: `./src/main/migrations/${database}`,
|
||||||
.then(() => connections[database]);
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getConnection(database: Databases): Promise<Connection> {
|
const connectionOptions: MyConnectionOptions = {
|
||||||
if (connections[database] === undefined) {
|
[Database.LIBRARY]: getConnectionOptionsFor(Database.LIBRARY),
|
||||||
return createConnection(database);
|
[Database.STORE]: getConnectionOptionsFor(Database.STORE),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
function createConnection(database: Database): Promise<Connection> {
|
||||||
|
let connection: Connection;
|
||||||
|
return ormCreateConnection(connectionOptions[database])
|
||||||
|
.then((c) => {
|
||||||
|
connection = c;
|
||||||
|
return c.runMigrations();
|
||||||
|
})
|
||||||
|
.then(() => connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
const connections = {
|
||||||
|
[Database.LIBRARY]: createConnection(Database.LIBRARY),
|
||||||
|
[Database.STORE]: createConnection(Database.STORE),
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getConnection(database: Database): Promise<Connection> {
|
||||||
return Promise.resolve(connections[database]);
|
return Promise.resolve(connections[database]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
export const enum Errors {
|
|
||||||
ENOLOGIN = 'ENOLOGIN',
|
|
||||||
ELOGINFAIL = 'ELOGINFAIL',
|
|
||||||
EINITFAIL = 'EINITFAIL',
|
|
||||||
ECOOKIESAVEFAIL = 'ECOOKIESAVEFAIL',
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages = {
|
|
||||||
[Errors.ENOLOGIN]: 'no login form found',
|
|
||||||
[Errors.ELOGINFAIL]: 'login failed',
|
|
||||||
[Errors.EINITFAIL]: 'initialization failed',
|
|
||||||
[Errors.ECOOKIESAVEFAIL]: 'failed to save cookies',
|
|
||||||
};
|
|
||||||
|
|
||||||
export class RenaiError extends Error {
|
|
||||||
public constructor(eno: Errors, msg: string = '') {
|
|
||||||
super(`${messages[eno]}.${msg ? ` ${msg}` : ''}`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,6 +6,7 @@ export const maxValue = Number.MAX_SAFE_INTEGER;
|
||||||
/**
|
/**
|
||||||
* @param column the column which needs to be checked
|
* @param column the column which needs to be checked
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-types -- the type would be "(classOrObject: object, propertyName: string) => object" but typeorm does not provide it
|
||||||
export function PercentCheck(column: string): Function {
|
export function PercentCheck(column: string): Function {
|
||||||
return Check(
|
return Check(
|
||||||
`${column} needs to be between ${minValue} and ${maxValue}`,
|
`${column} needs to be between ${minValue} and ${maxValue}`,
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { Author } from './author';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class AuthorName implements IIdentifiableEntity, INameEntity {
|
export class AuthorName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => Author, (author: Author) => author.names, {
|
@ManyToOne(() => Author, (author: Author) => author.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<Author>;
|
public entity!: Promise<Author>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { AuthorRole } from './author-role';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class AuthorRoleName implements IIdentifiableEntity, INameEntity {
|
export class AuthorRoleName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => AuthorRole, (authorRole: AuthorRole) => authorRole.names, {
|
@ManyToOne(() => AuthorRole, (authorRole: AuthorRole) => authorRole.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<AuthorRole>;
|
public entity!: Promise<AuthorRole>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,22 +9,22 @@ import { WorkAuthor } from './work-author';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class AuthorRole implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity {
|
export class AuthorRole implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => AuthorRoleName, (authorRoleName: AuthorRoleName) => authorRoleName.entity)
|
@OneToMany(() => AuthorRoleName, (authorRoleName: AuthorRoleName) => authorRoleName.entity)
|
||||||
public names: Promise<AuthorRoleName[]>;
|
public names!: Promise<AuthorRoleName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* relation to the entity connecting with the author and work
|
* relation to the entity connecting with the author and work
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.authorRoles)
|
@ManyToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.authorRoles)
|
||||||
public workAuthors: Promise<WorkAuthor[]>;
|
public workAuthors!: Promise<WorkAuthor[]>;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
public description: string;
|
public description!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,19 @@ import { WorkAuthor } from './work-author';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Author implements IIdentifiableEntity, IMultiNamedEntity {
|
export class Author implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => AuthorName, (authorName: AuthorName) => authorName.entity)
|
@OneToMany(() => AuthorName, (authorName: AuthorName) => authorName.entity)
|
||||||
public names: Promise<AuthorName[]>;
|
public names!: Promise<AuthorName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ultimately connects the author with a work and their role in that work
|
* ultimately connects the author with a work and their role in that work
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.author)
|
@OneToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.author)
|
||||||
public workAuthors: Promise<WorkAuthor[]>;
|
public workAuthors!: Promise<WorkAuthor[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { WorkCharacter } from './work-character';
|
||||||
@PercentCheck('weight')
|
@PercentCheck('weight')
|
||||||
export class CharacterTag implements IIdentifiableEntity, IWeightedEntity {
|
export class CharacterTag implements IIdentifiableEntity, IWeightedEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the character ina work this tag describes
|
* the character ina work this tag describes
|
||||||
|
@ -20,7 +20,7 @@ export class CharacterTag implements IIdentifiableEntity, IWeightedEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public workCharacter: Promise<WorkCharacter>;
|
public workCharacter!: Promise<WorkCharacter>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the describing tag
|
* the describing tag
|
||||||
|
@ -30,8 +30,8 @@ export class CharacterTag implements IIdentifiableEntity, IWeightedEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public tag: Promise<Tag>;
|
public tag!: Promise<Tag>;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
public weight: number;
|
public weight!: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { Collection } from './collection';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class CollectionName implements IIdentifiableEntity, INameEntity {
|
export class CollectionName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => Collection, (collection: Collection) => collection.names, {
|
@ManyToOne(() => Collection, (collection: Collection) => collection.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<Collection>;
|
public entity!: Promise<Collection>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Work } from './work';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class CollectionPart implements IIdentifiableEntity, IOrderableEntity {
|
export class CollectionPart implements IIdentifiableEntity, IOrderableEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the collection thw work is a part of
|
* the collection thw work is a part of
|
||||||
|
@ -19,7 +19,7 @@ export class CollectionPart implements IIdentifiableEntity, IOrderableEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public collection: Promise<Collection>;
|
public collection!: Promise<Collection>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the work inside the collection
|
* the work inside the collection
|
||||||
|
@ -29,11 +29,11 @@ export class CollectionPart implements IIdentifiableEntity, IOrderableEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public work: Promise<Work>;
|
public work!: Promise<Work>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
public order: number;
|
public order!: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,17 +13,17 @@ import { CollectionPart } from './collection-part';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Collection implements IIdentifiableEntity, IMultiNamedEntity {
|
export class Collection implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => CollectionName, (collectionName: CollectionName) => collectionName.entity)
|
@OneToMany(() => CollectionName, (collectionName: CollectionName) => collectionName.entity)
|
||||||
public names: Promise<CollectionName[]>;
|
public names!: Promise<CollectionName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the connecting entity between this collection and the work
|
* the connecting entity between this collection and the work
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => CollectionPart, (collectionPart: CollectionPart) => collectionPart.collection)
|
@OneToMany(() => CollectionPart, (collectionPart: CollectionPart) => collectionPart.collection)
|
||||||
public parts: Promise<CollectionPart[]>;
|
public parts!: Promise<CollectionPart[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Work } from './work';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Copy implements IIdentifiableEntity {
|
export class Copy implements IIdentifiableEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the work this entity is a copy of
|
* the work this entity is a copy of
|
||||||
|
@ -21,14 +21,14 @@ export class Copy implements IIdentifiableEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public original: Promise<Work>;
|
public original!: Promise<Work>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* where to find this specific copy
|
* where to find this specific copy
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => Source, (source: Source) => source.copies)
|
@ManyToMany(() => Source, (source: Source) => source.copies)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public sources: Promise<Source[]>;
|
public sources!: Promise<Source[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* identifying hash of the file contents
|
* identifying hash of the file contents
|
||||||
|
@ -36,7 +36,7 @@ export class Copy implements IIdentifiableEntity {
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public hash: string;
|
public hash!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* device location of the copy
|
* device location of the copy
|
||||||
|
@ -44,7 +44,7 @@ export class Copy implements IIdentifiableEntity {
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
public location: string;
|
public location!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the ordering of the copies belonging to the same work,
|
* the ordering of the copies belonging to the same work,
|
||||||
|
@ -54,5 +54,5 @@ export class Copy implements IIdentifiableEntity {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
public ranking: number;
|
public ranking!: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { WorkCharacter } from './work-character';
|
||||||
@PercentCheck('weight')
|
@PercentCheck('weight')
|
||||||
export class InteractionTag implements IIdentifiableEntity, IWeightedEntity {
|
export class InteractionTag implements IIdentifiableEntity, IWeightedEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the describing tag
|
* the describing tag
|
||||||
|
@ -20,20 +20,20 @@ export class InteractionTag implements IIdentifiableEntity, IWeightedEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public tag: Promise<Tag>;
|
public tag!: Promise<Tag>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the actors of this interaction
|
* the actors of this interaction
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.interactWith)
|
@ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.interactWith)
|
||||||
public subjectCharacters: Promise<WorkCharacter[]>;
|
public subjectCharacters!: Promise<WorkCharacter[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the receivers of this interaction
|
* the receivers of this interaction
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.interactedBy)
|
@ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.interactedBy)
|
||||||
public objectCharacters: Promise<WorkCharacter[]>;
|
public objectCharacters!: Promise<WorkCharacter[]>;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
public weight: number;
|
public weight!: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,11 @@ export class Language {
|
||||||
* ISO 639-1 two-letter language code
|
* ISO 639-1 two-letter language code
|
||||||
*/
|
*/
|
||||||
@PrimaryColumn()
|
@PrimaryColumn()
|
||||||
public code: string;
|
public code!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the works using this language
|
* the works using this language
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => Work, (work: Work) => work.languages)
|
@ManyToMany(() => Work, (work: Work) => work.languages)
|
||||||
public works: Promise<Work[]>;
|
public works!: Promise<Work[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { Site } from './site';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class SiteName implements IIdentifiableEntity, INameEntity {
|
export class SiteName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => Site, (site: Site) => site.names, {
|
@ManyToOne(() => Site, (site: Site) => site.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<Site>;
|
public entity!: Promise<Site>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,19 +8,19 @@ import { Source } from './source';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Site implements IIdentifiableEntity, IMultiNamedEntity {
|
export class Site implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => SiteName, (siteName: SiteName) => siteName.entity)
|
@OneToMany(() => SiteName, (siteName: SiteName) => siteName.entity)
|
||||||
public names: Promise<SiteName[]>;
|
public names!: Promise<SiteName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sources belonging to this site
|
* sources belonging to this site
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => Source, (source: Source) => source.site)
|
@OneToMany(() => Source, (source: Source) => source.site)
|
||||||
public sources: Promise<Source[]>;
|
public sources!: Promise<Source[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Site } from './site';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Source implements IIdentifiableEntity {
|
export class Source implements IIdentifiableEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the uri to the sauce
|
* the uri to the sauce
|
||||||
|
@ -16,7 +16,7 @@ export class Source implements IIdentifiableEntity {
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public uri: string;
|
public uri!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the site connected to the source
|
* the site connected to the source
|
||||||
|
@ -26,11 +26,11 @@ export class Source implements IIdentifiableEntity {
|
||||||
onDelete: 'RESTRICT',
|
onDelete: 'RESTRICT',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public site: Promise<Site>;
|
public site!: Promise<Site>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the copies which can be found here
|
* the copies which can be found here
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => Copy, (copy: Copy) => copy.sources)
|
@ManyToMany(() => Copy, (copy: Copy) => copy.sources)
|
||||||
public copies: Promise<Copy[]>;
|
public copies!: Promise<Copy[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { Tag } from './tag';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class TagName implements IIdentifiableEntity, INameEntity {
|
export class TagName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => Tag, (tag: Tag) => tag.names, {
|
@ManyToOne(() => Tag, (tag: Tag) => tag.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<Tag>;
|
public entity!: Promise<Tag>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,43 +13,43 @@ import { WorkTag } from './work-tag';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Tag implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity, IHierachicalEntity<Tag> {
|
export class Tag implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity, IHierachicalEntity<Tag> {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => TagName, (tagName: TagName) => tagName.entity)
|
@OneToMany(() => TagName, (tagName: TagName) => tagName.entity)
|
||||||
public names: Promise<TagName[]>;
|
public names!: Promise<TagName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this tag tagging a work
|
* this tag tagging a work
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => WorkTag, (workTag: WorkTag) => workTag.tag)
|
@OneToMany(() => WorkTag, (workTag: WorkTag) => workTag.tag)
|
||||||
public workTags: Promise<WorkTag[]>;
|
public workTags!: Promise<WorkTag[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this tag tagging characters
|
* this tag tagging characters
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => CharacterTag, (characterTag: CharacterTag) => characterTag.tag)
|
@OneToMany(() => CharacterTag, (characterTag: CharacterTag) => characterTag.tag)
|
||||||
public characterTags: Promise<CharacterTag[]>;
|
public characterTags!: Promise<CharacterTag[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this tag tagging a character interaction
|
* this tag tagging a character interaction
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.tag)
|
@OneToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.tag)
|
||||||
public interactionTags: Promise<InteractionTag[]>;
|
public interactionTags!: Promise<InteractionTag[]>;
|
||||||
|
|
||||||
@ManyToMany(() => Tag, (tag: Tag) => tag.children)
|
@ManyToMany(() => Tag, (tag: Tag) => tag.children)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public parents: Promise<Tag[]>;
|
public parents!: Promise<Tag[]>;
|
||||||
|
|
||||||
@ManyToMany(() => Tag, (tag: Tag) => tag.parents)
|
@ManyToMany(() => Tag, (tag: Tag) => tag.parents)
|
||||||
public children: Promise<Tag[]>;
|
public children!: Promise<Tag[]>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
public description: string;
|
public description!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { TransformationType } from './transformation-type';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class TransformationTypeName implements IIdentifiableEntity, INameEntity {
|
export class TransformationTypeName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => TransformationType, (transformationType: TransformationType) => transformationType.names, {
|
@ManyToOne(() => TransformationType, (transformationType: TransformationType) => transformationType.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<TransformationType>;
|
public entity!: Promise<TransformationType>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,29 +9,29 @@ import { TransformationTypeName } from './transformation-type-name';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class TransformationType implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity {
|
export class TransformationType implements IIdentifiableEntity, IMultiNamedEntity, IDescribableEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(
|
@OneToMany(
|
||||||
() => TransformationTypeName,
|
() => TransformationTypeName,
|
||||||
(transformationTypeName: TransformationTypeName) => transformationTypeName.entity
|
(transformationTypeName: TransformationTypeName) => transformationTypeName.entity
|
||||||
)
|
)
|
||||||
public names: Promise<TransformationTypeName[]>;
|
public names!: Promise<TransformationTypeName[]>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
public description: string;
|
public description!: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the transformations of this type
|
* the transformations of this type
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => Transformation, (transformation: Transformation) => transformation.type)
|
@OneToMany(() => Transformation, (transformation: Transformation) => transformation.type)
|
||||||
public transformations: Promise<Transformation[]>;
|
public transformations!: Promise<Transformation[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if that transformation conserves the tags of the original work
|
* if that transformation conserves the tags of the original work
|
||||||
|
@ -40,5 +40,5 @@ export class TransformationType implements IIdentifiableEntity, IMultiNamedEntit
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
public conservesTags: boolean;
|
public conservesTags!: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Work } from './work';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class Transformation implements IIdentifiableEntity, IOrderableEntity {
|
export class Transformation implements IIdentifiableEntity, IOrderableEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the work based on the original
|
* the work based on the original
|
||||||
|
@ -18,7 +18,7 @@ export class Transformation implements IIdentifiableEntity, IOrderableEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public byWork: Promise<Work>;
|
public byWork!: Promise<Work>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the transformation type
|
* the transformation type
|
||||||
|
@ -28,7 +28,7 @@ export class Transformation implements IIdentifiableEntity, IOrderableEntity {
|
||||||
onDelete: 'RESTRICT',
|
onDelete: 'RESTRICT',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public type: Promise<TransformationType>;
|
public type!: Promise<TransformationType>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the original work
|
* the original work
|
||||||
|
@ -38,11 +38,11 @@ export class Transformation implements IIdentifiableEntity, IOrderableEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public ofWork: Promise<Work>;
|
public ofWork!: Promise<Work>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
public order: number;
|
public order!: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Work } from './work';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorkAuthor implements IIdentifiableEntity, IOrderableEntity {
|
export class WorkAuthor implements IIdentifiableEntity, IOrderableEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the work
|
* the work
|
||||||
|
@ -19,14 +19,14 @@ export class WorkAuthor implements IIdentifiableEntity, IOrderableEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public work: Promise<Work>;
|
public work!: Promise<Work>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the roles of the author in the work
|
* the roles of the author in the work
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => AuthorRole, (authorRole: AuthorRole) => authorRole.workAuthors)
|
@ManyToMany(() => AuthorRole, (authorRole: AuthorRole) => authorRole.workAuthors)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public authorRoles: Promise<AuthorRole[]>;
|
public authorRoles!: Promise<AuthorRole[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the author
|
* the author
|
||||||
|
@ -36,11 +36,11 @@ export class WorkAuthor implements IIdentifiableEntity, IOrderableEntity {
|
||||||
onDelete: 'RESTRICT',
|
onDelete: 'RESTRICT',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public author: Promise<Author>;
|
public author!: Promise<Author>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: 0,
|
default: 0,
|
||||||
})
|
})
|
||||||
public order: number;
|
public order!: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { WorkCharacter } from './work-character';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorkCharacterName implements IIdentifiableEntity, INameEntity {
|
export class WorkCharacterName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.names, {
|
@ManyToOne(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<WorkCharacter>;
|
public entity!: Promise<WorkCharacter>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,15 +12,15 @@ import { WorldCharacter } from './world-character';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorkCharacter implements IIdentifiableEntity, IMultiNamedEntity {
|
export class WorkCharacter implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => WorkCharacterName, (workCharacterName: WorkCharacterName) => workCharacterName.entity)
|
@OneToMany(() => WorkCharacterName, (workCharacterName: WorkCharacterName) => workCharacterName.entity)
|
||||||
public names: Promise<WorkCharacterName[]>;
|
public names!: Promise<WorkCharacterName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the work the character is a part of
|
* the work the character is a part of
|
||||||
|
@ -30,32 +30,32 @@ export class WorkCharacter implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public work: Promise<Work>;
|
public work!: Promise<Work>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* interaction with other characters as actor
|
* interaction with other characters as actor
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.subjectCharacters)
|
@ManyToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.subjectCharacters)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public interactWith: Promise<InteractionTag[]>;
|
public interactWith!: Promise<InteractionTag[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* interaction with other characters as receiver
|
* interaction with other characters as receiver
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.objectCharacters)
|
@ManyToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.objectCharacters)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public interactedBy: Promise<InteractionTag[]>;
|
public interactedBy!: Promise<InteractionTag[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tags connected to the character
|
* tags connected to the character
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => CharacterTag, (characterTag: CharacterTag) => characterTag.workCharacter)
|
@OneToMany(() => CharacterTag, (characterTag: CharacterTag) => characterTag.workCharacter)
|
||||||
public characterTags: Promise<CharacterTag[]>;
|
public characterTags!: Promise<CharacterTag[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* existing characters character is based on
|
* existing characters character is based on
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.workCharacters)
|
@ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.workCharacters)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public worldCharacters: Promise<WorldCharacter[]>;
|
public worldCharacters!: Promise<WorldCharacter[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { Work } from './work';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorkName implements IIdentifiableEntity, INameEntity {
|
export class WorkName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => Work, (work: Work) => work.names, {
|
@ManyToOne(() => Work, (work: Work) => work.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<Work>;
|
public entity!: Promise<Work>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { Work } from './work';
|
||||||
@PercentCheck('weight')
|
@PercentCheck('weight')
|
||||||
export class WorkTag implements IIdentifiableEntity, IWeightedEntity {
|
export class WorkTag implements IIdentifiableEntity, IWeightedEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the describing tag
|
* the describing tag
|
||||||
|
@ -20,7 +20,7 @@ export class WorkTag implements IIdentifiableEntity, IWeightedEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public tag: Promise<Tag>;
|
public tag!: Promise<Tag>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the tagged work
|
* the tagged work
|
||||||
|
@ -30,8 +30,8 @@ export class WorkTag implements IIdentifiableEntity, IWeightedEntity {
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public work: Promise<Work>;
|
public work!: Promise<Work>;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
public weight: number;
|
public weight!: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,58 +19,58 @@ import { World } from './world';
|
||||||
@PercentCheck('rating')
|
@PercentCheck('rating')
|
||||||
export class Work implements IIdentifiableEntity, IMultiNamedEntity {
|
export class Work implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => WorkName, (workName: WorkName) => workName.entity)
|
@OneToMany(() => WorkName, (workName: WorkName) => workName.entity)
|
||||||
public names: Promise<WorkName[]>;
|
public names!: Promise<WorkName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* digital representations of this work
|
* digital representations of this work
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => Copy, (copy: Copy) => copy.original)
|
@OneToMany(() => Copy, (copy: Copy) => copy.original)
|
||||||
public copies: Promise<Copy[]>;
|
public copies!: Promise<Copy[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* other works this work is a transformation of
|
* other works this work is a transformation of
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => Transformation, (transformation: Transformation) => transformation.byWork)
|
@OneToMany(() => Transformation, (transformation: Transformation) => transformation.byWork)
|
||||||
public transformationOf: Promise<Transformation[]>;
|
public transformationOf!: Promise<Transformation[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* other works this work is transformed by
|
* other works this work is transformed by
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => Transformation, (transformation: Transformation) => transformation.ofWork)
|
@OneToMany(() => Transformation, (transformation: Transformation) => transformation.ofWork)
|
||||||
public transformedBy: Promise<Transformation[]>;
|
public transformedBy!: Promise<Transformation[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the authors/publishers of this work
|
* the authors/publishers of this work
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.work)
|
@OneToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.work)
|
||||||
public workAuthors: Promise<WorkAuthor[]>;
|
public workAuthors!: Promise<WorkAuthor[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* tags describing this work
|
* tags describing this work
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => WorkTag, (workTag: WorkTag) => workTag.work)
|
@OneToMany(() => WorkTag, (workTag: WorkTag) => workTag.work)
|
||||||
public workTags: Promise<WorkTag[]>;
|
public workTags!: Promise<WorkTag[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* characters in this work
|
* characters in this work
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.work)
|
@OneToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.work)
|
||||||
public workCharacters: Promise<WorkCharacter[]>;
|
public workCharacters!: Promise<WorkCharacter[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fictional worlds in which this work takes place
|
* fictional worlds in which this work takes place
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => World, (world: World) => world.works)
|
@ManyToMany(() => World, (world: World) => world.works)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public worlds: Promise<World[]>;
|
public worlds!: Promise<World[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if this work i canon in above fictional world
|
* if this work i canon in above fictional world
|
||||||
|
@ -79,7 +79,7 @@ export class Work implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
default: false,
|
default: false,
|
||||||
})
|
})
|
||||||
public isCanonical: boolean;
|
public isCanonical!: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the user rating of this work
|
* the user rating of this work
|
||||||
|
@ -87,7 +87,7 @@ export class Work implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
public rating: number;
|
public rating!: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the release date of the work
|
* the release date of the work
|
||||||
|
@ -95,18 +95,18 @@ export class Work implements IIdentifiableEntity, IMultiNamedEntity {
|
||||||
@Column({
|
@Column({
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
public releaseDate: Date;
|
public releaseDate!: Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the languages of the work (if applicable)
|
* the languages of the work (if applicable)
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => Language, (language: Language) => language.works)
|
@ManyToMany(() => Language, (language: Language) => language.works)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public languages: Promise<Language[]>;
|
public languages!: Promise<Language[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the collections this work is a part of
|
* the collections this work is a part of
|
||||||
*/
|
*/
|
||||||
@OneToMany(() => CollectionPart, (collectionPart: CollectionPart) => collectionPart.work)
|
@OneToMany(() => CollectionPart, (collectionPart: CollectionPart) => collectionPart.work)
|
||||||
public collectionParts: Promise<CollectionPart[]>;
|
public collectionParts!: Promise<CollectionPart[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { WorldCharacter } from './world-character';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorldCharacterName implements IIdentifiableEntity, INameEntity {
|
export class WorldCharacterName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.names, {
|
@ManyToOne(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<WorldCharacter>;
|
public entity!: Promise<WorldCharacter>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,32 +9,32 @@ import { WorldCharacterName } from './world-character-name';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorldCharacter implements IIdentifiableEntity, IMultiNamedEntity, IHierachicalEntity<WorldCharacter> {
|
export class WorldCharacter implements IIdentifiableEntity, IMultiNamedEntity, IHierachicalEntity<WorldCharacter> {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => WorldCharacterName, (worldCharacterName: WorldCharacterName) => worldCharacterName.entity)
|
@OneToMany(() => WorldCharacterName, (worldCharacterName: WorldCharacterName) => worldCharacterName.entity)
|
||||||
public names: Promise<WorldCharacterName[]>;
|
public names!: Promise<WorldCharacterName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the characters in works which are based on this one
|
* the characters in works which are based on this one
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.worldCharacters)
|
@ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.worldCharacters)
|
||||||
public workCharacters: Promise<WorkCharacter[]>;
|
public workCharacters!: Promise<WorkCharacter[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the fictional worlds this character is a part of
|
* the fictional worlds this character is a part of
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => World, (world: World) => world.worldCharacters)
|
@ManyToMany(() => World, (world: World) => world.worldCharacters)
|
||||||
public worlds: Promise<World[]>;
|
public worlds!: Promise<World[]>;
|
||||||
|
|
||||||
@ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.children)
|
@ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.children)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public parents: Promise<WorldCharacter[]>;
|
public parents!: Promise<WorldCharacter[]>;
|
||||||
|
|
||||||
@ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.parents)
|
@ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.parents)
|
||||||
public children: Promise<WorldCharacter[]>;
|
public children!: Promise<WorldCharacter[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,17 +4,17 @@ import { World } from './world';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class WorldName implements IIdentifiableEntity, INameEntity {
|
export class WorldName implements IIdentifiableEntity, INameEntity {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@ManyToOne(() => World, (world: World) => world.names, {
|
@ManyToOne(() => World, (world: World) => world.names, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
onDelete: 'CASCADE',
|
onDelete: 'CASCADE',
|
||||||
onUpdate: 'CASCADE',
|
onUpdate: 'CASCADE',
|
||||||
})
|
})
|
||||||
public entity: Promise<World>;
|
public entity!: Promise<World>;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public name: string;
|
public name!: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,33 +9,33 @@ import { WorldName } from './world-name';
|
||||||
@Entity()
|
@Entity()
|
||||||
export class World implements IIdentifiableEntity, IMultiNamedEntity, IHierachicalEntity<World> {
|
export class World implements IIdentifiableEntity, IMultiNamedEntity, IHierachicalEntity<World> {
|
||||||
@PrimaryGeneratedColumn()
|
@PrimaryGeneratedColumn()
|
||||||
public id: number;
|
public id!: number;
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
nullable: false,
|
nullable: false,
|
||||||
})
|
})
|
||||||
public nameCanonical: string;
|
public nameCanonical!: string;
|
||||||
|
|
||||||
@OneToMany(() => WorldName, (worldName: WorldName) => worldName.entity)
|
@OneToMany(() => WorldName, (worldName: WorldName) => worldName.entity)
|
||||||
public names: Promise<WorldName[]>;
|
public names!: Promise<WorldName[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* works taking place in this world
|
* works taking place in this world
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => Work, (work: Work) => work.worlds)
|
@ManyToMany(() => Work, (work: Work) => work.worlds)
|
||||||
public works: Promise<Work[]>;
|
public works!: Promise<Work[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* canon characters in this world
|
* canon characters in this world
|
||||||
*/
|
*/
|
||||||
@ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.worlds)
|
@ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.worlds)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public worldCharacters: Promise<WorldCharacter[]>;
|
public worldCharacters!: Promise<WorldCharacter[]>;
|
||||||
|
|
||||||
@ManyToMany(() => World, (world: World) => world.parents)
|
@ManyToMany(() => World, (world: World) => world.parents)
|
||||||
public children: Promise<World[]>;
|
public children!: Promise<World[]>;
|
||||||
|
|
||||||
@ManyToMany(() => World, (world: World) => world.children)
|
@ManyToMany(() => World, (world: World) => world.children)
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
public parents: Promise<World[]>;
|
public parents!: Promise<World[]>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,11 @@ export class StoreValue {
|
||||||
* the key
|
* the key
|
||||||
*/
|
*/
|
||||||
@PrimaryColumn()
|
@PrimaryColumn()
|
||||||
public key: StoreKey;
|
public key: StoreKey | string = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the value
|
* the value
|
||||||
*/
|
*/
|
||||||
@Column('simple-json')
|
@Column('simple-json')
|
||||||
public value: any;
|
public value: unknown;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,13 +27,13 @@ switch (os.platform()) {
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export abstract class AppWindow implements IAppWindow {
|
export abstract class AppWindow implements IAppWindow {
|
||||||
protected _window: BrowserWindow | null;
|
protected _window: BrowserWindow | null = null;
|
||||||
|
|
||||||
protected constructor(options: BrowserWindowConstructorOptions = {}) {
|
protected constructor(options: BrowserWindowConstructorOptions = {}) {
|
||||||
this.initialize(options);
|
this.initialize(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get window(): BrowserWindow {
|
public get window(): BrowserWindow | null {
|
||||||
return this._window;
|
return this._window;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +42,11 @@ export abstract class AppWindow implements IAppWindow {
|
||||||
this.initialize();
|
this.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._window.loadFile('frontend/index.html');
|
if (this._window) {
|
||||||
|
return this._window.loadFile('frontend/index.html');
|
||||||
|
} else {
|
||||||
|
return Promise.reject(new Error('the window was not initialized'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public isClosed(): boolean {
|
public isClosed(): boolean {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import BrowserWindow = Electron.BrowserWindow;
|
import BrowserWindow = Electron.BrowserWindow;
|
||||||
|
|
||||||
export interface IAppWindow {
|
export interface IAppWindow {
|
||||||
window: BrowserWindow;
|
window: BrowserWindow | null;
|
||||||
open(): Promise<void>;
|
open(): Promise<void>;
|
||||||
isClosed(): boolean;
|
isClosed(): boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* Error thrown when cookies could not be saved.
|
||||||
|
*/
|
||||||
|
export class CookieSaveError extends Error {
|
||||||
|
public constructor(message: string = 'failed to save cookies') {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* Error thrown when there is an error while initializing services or the app in general.
|
||||||
|
*
|
||||||
|
* You're pretty much fucked at this point, try to avoid unstable code on initialization
|
||||||
|
*/
|
||||||
|
export class InitializationError extends Error {
|
||||||
|
public constructor(message: string = 'initialization failed') {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
/**
|
||||||
|
* generic web crawler error
|
||||||
|
*/
|
||||||
|
export class WebCrawlerError extends Error {
|
||||||
|
public constructor(message: string = 'web crawler failed') {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { WebCrawlerError } from './web-crawler-error';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when the web crawler can't work with the provided form.
|
||||||
|
*/
|
||||||
|
export class WebCrawlerFormError extends WebCrawlerError {
|
||||||
|
public constructor(message: string = 'web crawler failed') {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { WebCrawlerError } from './web-crawler-error';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error thrown when the web crawler can't login
|
||||||
|
*/
|
||||||
|
export class WebCrawlerLoginError extends WebCrawlerError {
|
||||||
|
public constructor(message: string = 'login failed') {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { registerHandler } from '../ipc-server';
|
||||||
|
|
||||||
|
export function answer(channel: IpcChannel): DecoratorFactory<IIpcController, IpcHandler> {
|
||||||
|
return function (target: IIpcController, propertyKey): void {
|
||||||
|
registerHandler(channel, target, propertyKey);
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,33 +1,25 @@
|
||||||
import { ipcMain } from 'electron';
|
import { ipcMain } from 'electron';
|
||||||
import { injectable } from 'inversify';
|
|
||||||
import IpcMainEvent = Electron.IpcMainEvent;
|
import IpcMainEvent = Electron.IpcMainEvent;
|
||||||
import BrowserWindow = Electron.BrowserWindow;
|
|
||||||
|
|
||||||
@injectable()
|
export function registerHandler(channel: IpcChannel, controller: IIpcController, handler: string): void {
|
||||||
export abstract class IpcServer {
|
ipcMain.on(channel, (event: IpcMainEvent, payload: IIpcPayload) => {
|
||||||
protected answer(channel: IpcChannels, handler: (data?: any) => Promise<any>): void {
|
((controller.get() as unknown) as { [x: string]: IpcHandler })
|
||||||
ipcMain.on(channel, (event: IpcMainEvent, payload: IIpcPayload) => {
|
[handler](payload.data)
|
||||||
handler(payload.data)
|
.then((result: unknown) => {
|
||||||
.then((result: any) => {
|
const response: IIpcResponse = {
|
||||||
const response: IIpcResponse = {
|
id: payload.id,
|
||||||
id: payload.id,
|
success: true,
|
||||||
success: true,
|
data: result,
|
||||||
data: result,
|
};
|
||||||
};
|
event.reply(channel, response);
|
||||||
event.reply(channel, response);
|
})
|
||||||
})
|
.catch((reason: Error) => {
|
||||||
.catch((reason: any) => {
|
const response: IIpcResponse = {
|
||||||
const response: IIpcResponse = {
|
id: payload.id,
|
||||||
id: payload.id,
|
success: false,
|
||||||
success: false,
|
error: reason.message,
|
||||||
error: reason.toString(),
|
};
|
||||||
};
|
event.reply(channel, response);
|
||||||
event.reply(channel, response);
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected send(window: BrowserWindow, channel: IpcChannels, data: any): void {
|
|
||||||
window.webContents.send(channel, data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { inject, injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { JSDOM } from 'jsdom';
|
import { JSDOM } from 'jsdom';
|
||||||
import { RequestInit, Response } from 'node-fetch';
|
import { RequestInit, Response } from 'node-fetch';
|
||||||
import { Errors, RenaiError } from '../../core/error';
|
import { WebCrawlerFormError } from '../error/web-crawler-form-error';
|
||||||
|
import { WebCrawlerLoginError } from '../error/web-crawler-login-error';
|
||||||
import { IWebCrawler } from '../web-crawler/i-web-crawler';
|
import { IWebCrawler } from '../web-crawler/i-web-crawler';
|
||||||
import { INhentaiApi } from './i-nhentai-api';
|
import { INhentaiApi } from './i-nhentai-api';
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ export class NhentaiApi implements INhentaiApi {
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch(() => Promise.reject(new RenaiError(Errors.ELOGINFAIL)));
|
.catch(() => Promise.reject(new WebCrawlerLoginError()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNHentai(path: string): Promise<Document> {
|
private getNHentai(path: string): Promise<Document> {
|
||||||
|
@ -111,14 +112,17 @@ export class NhentaiApi implements INhentaiApi {
|
||||||
if (name === usernameInput || name === passwordInput) {
|
if (name === usernameInput || name === passwordInput) {
|
||||||
isLoginForm = true;
|
isLoginForm = true;
|
||||||
} else if (name) {
|
} else if (name) {
|
||||||
valueStore[name] = input.getAttribute('value');
|
const value = input.getAttribute('value');
|
||||||
|
if (value) {
|
||||||
|
valueStore[name] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isLoginForm) {
|
if (isLoginForm) {
|
||||||
return valueStore;
|
return valueStore;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.reject(new RenaiError(Errors.ENOLOGIN));
|
return Promise.reject(new WebCrawlerFormError());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { container } from '../../core/container';
|
||||||
|
import { answer } from '../ipc/annotations/answer';
|
||||||
|
import { INhentaiApi } from './i-nhentai-api';
|
||||||
|
|
||||||
|
export class NhentaiIpcController implements IIpcController {
|
||||||
|
private nhentaiApi: INhentaiApi;
|
||||||
|
|
||||||
|
public constructor(nhentaiApi: INhentaiApi) {
|
||||||
|
this.nhentaiApi = nhentaiApi;
|
||||||
|
}
|
||||||
|
|
||||||
|
@answer(IpcChannel.LOGIN)
|
||||||
|
public login(credentials: ICredentials): Promise<void> {
|
||||||
|
return this.nhentaiApi.login(credentials.name, credentials.password);
|
||||||
|
}
|
||||||
|
|
||||||
|
@answer(IpcChannel.LOGGED_IN)
|
||||||
|
public loggedIn(): Promise<boolean> {
|
||||||
|
return this.nhentaiApi.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(): NhentaiIpcController {
|
||||||
|
const nhentaiApi: INhentaiApi = container.get(Symbol.for('nhentai-api'));
|
||||||
|
return new NhentaiIpcController(nhentaiApi);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
import { inject, injectable } from 'inversify';
|
|
||||||
import { IpcServer } from '../ipc/ipc-server';
|
|
||||||
import { INhentaiApi } from './i-nhentai-api';
|
|
||||||
|
|
||||||
@injectable()
|
|
||||||
export class NhentaiIpcServer extends IpcServer {
|
|
||||||
private nhentaiApi: INhentaiApi;
|
|
||||||
|
|
||||||
public constructor(@inject(Symbol.for('nhentai-api')) nhentaiApi: INhentaiApi) {
|
|
||||||
super();
|
|
||||||
this.nhentaiApi = nhentaiApi;
|
|
||||||
|
|
||||||
this.answer(IpcChannels.LOGIN, (credentials: ICredentials) =>
|
|
||||||
this.nhentaiApi.login(credentials.name, credentials.password)
|
|
||||||
);
|
|
||||||
this.answer(IpcChannels.LOGGED_IN, () => this.nhentaiApi.isLoggedIn());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,29 +2,26 @@ import { session } from 'electron';
|
||||||
import { injectable } from 'inversify';
|
import { injectable } from 'inversify';
|
||||||
import { isDev } from '../../core/dev';
|
import { isDev } from '../../core/dev';
|
||||||
import { ISession } from './i-session';
|
import { ISession } from './i-session';
|
||||||
import OnHeadersReceivedListenerDetails = Electron.OnHeadersReceivedListenerDetails;
|
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class Session implements ISession {
|
export class Session implements ISession {
|
||||||
public setHeaders(): void {
|
public setHeaders(): void {
|
||||||
// these headers only work on web requests, file:// protocol is handled via meta tags in the html
|
// these headers only work on web requests, file:// protocol is handled via meta tags in the html
|
||||||
session.defaultSession.webRequest.onHeadersReceived(
|
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
|
||||||
(details: OnHeadersReceivedListenerDetails, callback: (response: {}) => void) => {
|
callback({
|
||||||
callback({
|
responseHeaders: {
|
||||||
responseHeaders: {
|
...details.responseHeaders,
|
||||||
...details.responseHeaders,
|
'Content-Security-Policy': isDev()
|
||||||
'Content-Security-Policy': isDev()
|
? [
|
||||||
? [
|
'default-src devtools:;' +
|
||||||
'default-src devtools:;' +
|
"script-src 'unsafe-eval';" +
|
||||||
"script-src 'unsafe-eval';" +
|
"script-src-elem devtools: 'sha256-hl04hLzKBpmsfWF2wIA/0Vs6ZNV5T9ZNFY//3uXrgSk=';" +
|
||||||
"script-src-elem devtools: 'sha256-hl04hLzKBpmsfWF2wIA/0Vs6ZNV5T9ZNFY//3uXrgSk=';" +
|
"style-src devtools: 'unsafe-inline';" +
|
||||||
"style-src devtools: 'unsafe-inline';" +
|
'connect-src devtools: data:',
|
||||||
'connect-src devtools: data:',
|
]
|
||||||
]
|
: ["default-src 'none'"],
|
||||||
: ["default-src 'none'"],
|
},
|
||||||
},
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export interface IStore {
|
||||||
|
load: (key: StoreKey) => Promise<unknown>;
|
||||||
|
save: (key: StoreKey, data: unknown) => Promise<void>;
|
||||||
|
}
|
|
@ -1,33 +1,19 @@
|
||||||
import { load, save } from './store';
|
import { injectable } from 'inversify';
|
||||||
const store = require('./store');
|
import { IStore } from './i-store';
|
||||||
|
|
||||||
interface IStoreMock extends IMock {
|
/**
|
||||||
original: {
|
* This mock store saves the data in memory.
|
||||||
load: typeof load;
|
*/
|
||||||
save: typeof save;
|
@injectable()
|
||||||
};
|
export class StoreMock implements IStore {
|
||||||
mock: {
|
private store: { [x in StoreKey]?: unknown } = {};
|
||||||
load: (mock: typeof load) => void;
|
|
||||||
save: (mock: typeof save) => void;
|
public load(key: StoreKey): Promise<unknown> {
|
||||||
};
|
return Promise.resolve(this.store[key]);
|
||||||
restore: () => void;
|
}
|
||||||
|
|
||||||
|
public save(key: StoreKey, data: unknown): Promise<void> {
|
||||||
|
this.store[key] = data;
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const storeMock: IStoreMock = {
|
|
||||||
original: {
|
|
||||||
load: store.load,
|
|
||||||
save: store.save,
|
|
||||||
},
|
|
||||||
mock: {
|
|
||||||
load(mock) {
|
|
||||||
store.load = mock;
|
|
||||||
},
|
|
||||||
save(mock) {
|
|
||||||
store.save = mock;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
restore() {
|
|
||||||
store.load = this.original.load;
|
|
||||||
store.save = this.original.save;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ import '../../../../mocks/electron';
|
||||||
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { load, save } from './store';
|
import { container } from '../../core/container';
|
||||||
|
import { IStore } from './i-store';
|
||||||
|
|
||||||
describe('Store Service', function () {
|
describe('Store Service', function () {
|
||||||
this.timeout(10000);
|
this.timeout(10000);
|
||||||
|
@ -17,7 +18,8 @@ describe('Store Service', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('loads saved data', () => {
|
it('loads saved data', () => {
|
||||||
const testData: any = {
|
const store: IStore = container.get(Symbol.for('store'));
|
||||||
|
const testData = {
|
||||||
something: 'gaga',
|
something: 'gaga',
|
||||||
somethingElse: 0,
|
somethingElse: 0,
|
||||||
deepObject: {
|
deepObject: {
|
||||||
|
@ -32,11 +34,12 @@ describe('Store Service', function () {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const expectedJson = JSON.stringify(testData);
|
const expectedJson = JSON.stringify(testData);
|
||||||
return save(StoreKey.COOKIES, testData)
|
return store
|
||||||
.then(() => load(StoreKey.COOKIES))
|
.save(StoreKey.COOKIES, testData)
|
||||||
|
.then(() => store.load(StoreKey.COOKIES))
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
expect(JSON.stringify(data)).to.equal(expectedJson, 'store does not save and load data correctly');
|
expect(JSON.stringify(data)).to.equal(expectedJson, 'store does not save and load data correctly');
|
||||||
return load(StoreKey.COOKIES);
|
return store.load(StoreKey.COOKIES);
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
expect(JSON.stringify(data)).to.equal(expectedJson, 'store does not load data correctly when loaded twice');
|
expect(JSON.stringify(data)).to.equal(expectedJson, 'store does not load data correctly when loaded twice');
|
||||||
|
|
|
@ -1,26 +1,31 @@
|
||||||
import { Databases, getConnection } from '../../core/database';
|
import { injectable } from 'inversify';
|
||||||
|
import { Database, getConnection } from '../../core/database';
|
||||||
import { StoreValue } from '../../entities/store/store-value';
|
import { StoreValue } from '../../entities/store/store-value';
|
||||||
|
import { IStore } from './i-store';
|
||||||
|
|
||||||
const CACHE_ID = 'store';
|
const CACHE_ID = 'store';
|
||||||
|
|
||||||
export async function load(key: StoreKey): Promise<any> {
|
@injectable()
|
||||||
const c = await getConnection(Databases.STORE);
|
export class Store implements IStore {
|
||||||
const repository = c.getRepository(StoreValue);
|
public async load(key: StoreKey): Promise<unknown> {
|
||||||
const storeValue = await repository.findOne(key, {
|
const c = await getConnection(Database.STORE);
|
||||||
cache: {
|
const repository = c.getRepository(StoreValue);
|
||||||
id: CACHE_ID,
|
const storeValue = await repository.findOne(key, {
|
||||||
milliseconds: 0,
|
cache: {
|
||||||
},
|
id: CACHE_ID,
|
||||||
});
|
milliseconds: 0,
|
||||||
return storeValue.value;
|
},
|
||||||
}
|
});
|
||||||
|
return storeValue?.value;
|
||||||
|
}
|
||||||
|
|
||||||
export async function save(key: StoreKey, data: any): Promise<void> {
|
public async save(key: StoreKey, data: unknown): Promise<void> {
|
||||||
const c = await getConnection(Databases.STORE);
|
const c = await getConnection(Database.STORE);
|
||||||
const manager = c.manager;
|
const manager = c.manager;
|
||||||
const storeValue = new StoreValue();
|
const storeValue = new StoreValue();
|
||||||
storeValue.key = key;
|
storeValue.key = key;
|
||||||
storeValue.value = data;
|
storeValue.value = data;
|
||||||
await manager.save(storeValue);
|
await manager.save(storeValue);
|
||||||
await c.queryResultCache.remove([CACHE_ID]);
|
await c.queryResultCache?.remove([CACHE_ID]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,12 @@ import rewiremock from 'rewiremock';
|
||||||
import '../../../../mocks/electron';
|
import '../../../../mocks/electron';
|
||||||
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { CookieJar } from 'jsdom';
|
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import nock from 'nock';
|
import nock from 'nock';
|
||||||
import { Response } from 'node-fetch';
|
import { Response } from 'node-fetch';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { WebCrawler } from './web-crawler';
|
import { container, mockStore } from '../../core/container';
|
||||||
import { storeMock } from '../store/store.mock';
|
import { IWebCrawler } from './i-web-crawler';
|
||||||
|
|
||||||
describe('Web Crawler', function () {
|
describe('Web Crawler', function () {
|
||||||
this.timeout(2000);
|
this.timeout(2000);
|
||||||
|
@ -16,9 +15,7 @@ describe('Web Crawler', function () {
|
||||||
before(() => {
|
before(() => {
|
||||||
rewiremock.enable();
|
rewiremock.enable();
|
||||||
|
|
||||||
storeMock.mock.load(() => Promise.resolve(new CookieJar().serializeSync()));
|
mockStore();
|
||||||
|
|
||||||
storeMock.mock.save(() => Promise.resolve());
|
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
@ -33,7 +30,7 @@ describe('Web Crawler', function () {
|
||||||
|
|
||||||
after(() => {
|
after(() => {
|
||||||
rewiremock.disable();
|
rewiremock.disable();
|
||||||
storeMock.restore();
|
mockStore(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fetches websites', async () => {
|
it('fetches websites', async () => {
|
||||||
|
@ -52,11 +49,11 @@ describe('Web Crawler', function () {
|
||||||
)
|
)
|
||||||
.persist();
|
.persist();
|
||||||
|
|
||||||
const webCrawler = new WebCrawler();
|
const webCrawler: IWebCrawler = container.get(Symbol.for('web-crawler'));
|
||||||
|
|
||||||
const res: Response = await webCrawler.fetch(testUrl);
|
const res: Response = await webCrawler.fetch(testUrl);
|
||||||
expect(callback.callCount).to.equal(1, 'multiple requests (or none) are sent when only one should be');
|
expect(callback.callCount).to.equal(1, 'multiple requests (or none) are sent when only one should be');
|
||||||
const json = await res.json();
|
const json = (await res.json()) as unknown;
|
||||||
expect(json).to.deep.equal([{ id: 12, comment: 'Hey there' }], 'response body is incorrect');
|
expect(json).to.deep.equal([{ id: 12, comment: 'Hey there' }], 'response body is incorrect');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { injectable } from 'inversify';
|
import { inject, injectable } from 'inversify';
|
||||||
import { CookieJar } from 'jsdom';
|
import { CookieJar } from 'jsdom';
|
||||||
import nodeFetch, { RequestInit, Response } from 'node-fetch';
|
import nodeFetch, { RequestInit, Response } from 'node-fetch';
|
||||||
import { Errors, RenaiError } from '../../core/error';
|
import { CookieSaveError } from '../error/cookie-save-error';
|
||||||
import { load, save } from '../store/store';
|
import { IStore } from '../store/i-store';
|
||||||
import { IWebCrawler } from './i-web-crawler';
|
import { IWebCrawler } from './i-web-crawler';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
|
@ -11,9 +11,12 @@ export class WebCrawler implements IWebCrawler {
|
||||||
|
|
||||||
private initialized: boolean;
|
private initialized: boolean;
|
||||||
|
|
||||||
public constructor() {
|
private store: IStore;
|
||||||
|
|
||||||
|
public constructor(@inject(Symbol.for('store')) store: IStore) {
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.cookieJar = new CookieJar();
|
this.cookieJar = new CookieJar();
|
||||||
|
this.store = store;
|
||||||
}
|
}
|
||||||
|
|
||||||
public fetch(url: string, requestInit: RequestInit = {}): Promise<Response> {
|
public fetch(url: string, requestInit: RequestInit = {}): Promise<Response> {
|
||||||
|
@ -30,8 +33,8 @@ export class WebCrawler implements IWebCrawler {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
return nodeFetch(url, cookiedInit).then((res: Response) => {
|
return nodeFetch(url, cookiedInit).then((res: Response) => {
|
||||||
this.setCookies(res.headers.raw()['set-cookie'], url).catch((reason: any) => {
|
this.setCookies(res.headers.raw()['set-cookie'], url).catch((reason: Error) => {
|
||||||
throw new RenaiError(Errors.ECOOKIESAVEFAIL, reason);
|
throw new CookieSaveError(reason.message);
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
});
|
});
|
||||||
|
@ -40,9 +43,9 @@ export class WebCrawler implements IWebCrawler {
|
||||||
|
|
||||||
private init(): Promise<void> {
|
private init(): Promise<void> {
|
||||||
if (!this.initialized) {
|
if (!this.initialized) {
|
||||||
return load(StoreKey.COOKIES).then((cookies: any) => {
|
return this.store.load(StoreKey.COOKIES).then((cookies: unknown) => {
|
||||||
if (cookies !== undefined) {
|
if (cookies !== undefined) {
|
||||||
this.cookieJar = CookieJar.deserializeSync(cookies);
|
this.cookieJar = CookieJar.deserializeSync(cookies as string);
|
||||||
}
|
}
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
});
|
});
|
||||||
|
@ -56,8 +59,8 @@ export class WebCrawler implements IWebCrawler {
|
||||||
header.forEach((cookie: string) => {
|
header.forEach((cookie: string) => {
|
||||||
this.cookieJar.setCookieSync(cookie, url);
|
this.cookieJar.setCookieSync(cookie, url);
|
||||||
});
|
});
|
||||||
return save(StoreKey.COOKIES, this.cookieJar.serializeSync()).catch((reason: any) => {
|
return this.store.save(StoreKey.COOKIES, this.cookieJar.serializeSync()).catch((reason: Error) => {
|
||||||
throw new RenaiError(Errors.ECOOKIESAVEFAIL, reason);
|
throw new CookieSaveError(reason.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import App from './renderer/App.svelte';
|
import App from './renderer/App.svelte';
|
||||||
|
|
||||||
((): void =>
|
((): void =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-return -- fis this together with the typescript support of svelte
|
||||||
new App({
|
new App({
|
||||||
target: document.querySelector('#app'),
|
target: document.querySelector('#app'),
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -3,20 +3,20 @@ import { uuid } from '../../services/uuid';
|
||||||
import IpcRendererEvent = Electron.IpcRendererEvent;
|
import IpcRendererEvent = Electron.IpcRendererEvent;
|
||||||
|
|
||||||
const ipcClient: IIpcClient = {
|
const ipcClient: IIpcClient = {
|
||||||
ask: (channel: IpcChannels, data?: any): Promise<any> => {
|
ask: (channel: IpcChannel, data?: unknown): Promise<unknown> => {
|
||||||
const id = uuid();
|
const id = uuid();
|
||||||
const payload: IIpcPayload = {
|
const payload: IIpcPayload = {
|
||||||
id,
|
id,
|
||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
|
|
||||||
return new Promise((resolve: (value?: any) => void, reject: (reason?: any) => void): void => {
|
return new Promise((resolve: (value?: unknown) => void, reject: (reason?: Error) => void): void => {
|
||||||
const listener = (event: IpcRendererEvent, response: IIpcResponse): void => {
|
const listener = (event: IpcRendererEvent, response: IIpcResponse): void => {
|
||||||
if (response.id === id) {
|
if (response.id === id) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
} else {
|
} else {
|
||||||
reject(response.error);
|
reject(new Error(response.error));
|
||||||
}
|
}
|
||||||
ipcRenderer.removeListener(channel, listener);
|
ipcRenderer.removeListener(channel, listener);
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,9 @@ const ipcClient: IIpcClient = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export function login(credentials: ICredentials): Promise<void> {
|
export function login(credentials: ICredentials): Promise<void> {
|
||||||
return ipcClient.ask(IpcChannels.LOGIN, credentials);
|
return ipcClient.ask(IpcChannel.LOGIN, credentials) as Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isLoggedIn(): Promise<boolean> {
|
export function isLoggedIn(): Promise<boolean> {
|
||||||
return ipcClient.ask(IpcChannels.LOGGED_IN);
|
return ipcClient.ask(IpcChannel.LOGGED_IN) as Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
import { writable } from 'svelte/store';
|
import { writable, Readable } from 'svelte/store';
|
||||||
import * as api from './api';
|
import * as api from './api';
|
||||||
|
|
||||||
const { subscribe, set } = writable<boolean>(false);
|
const { subscribe, set } = writable<boolean>(false);
|
||||||
|
|
||||||
export const loggedIn = {
|
interface ILoggedIn extends Readable<boolean> {
|
||||||
|
fetchIsLoggedIn(): Promise<void>;
|
||||||
|
fetchLogin(credentials: ICredentials): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const loggedIn: ILoggedIn = {
|
||||||
subscribe,
|
subscribe,
|
||||||
fetchIsLoggedIn(): Promise<void> {
|
fetchIsLoggedIn(): Promise<void> {
|
||||||
return api.isLoggedIn().then((isLoggedIn: boolean) => {
|
return api.isLoggedIn().then((isLoggedIn: boolean) => {
|
||||||
set(isLoggedIn);
|
set(isLoggedIn);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
fetchLogin(credentials: ICredentials): Promise<void> {
|
fetchLogin(this: ILoggedIn, credentials: ICredentials): Promise<void> {
|
||||||
return api.login(credentials).then(this.fetchIsLoggedIn);
|
return api.login(credentials).then(this.fetchIsLoggedIn);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { v1 as uuidv1 } from 'uuid';
|
import { v1 as uuidV1 } from 'uuid';
|
||||||
|
|
||||||
const R = 0x52;
|
const R = 0x52;
|
||||||
const e = 0x65;
|
const e = 0x65;
|
||||||
|
@ -13,7 +13,7 @@ const nice = 0x45;
|
||||||
* see RFC 4122 4.2.1.
|
* see RFC 4122 4.2.1.
|
||||||
*/
|
*/
|
||||||
export function uuid(): string {
|
export function uuid(): string {
|
||||||
return uuidv1({
|
return uuidV1({
|
||||||
node: [R, e, n, a, i, nice],
|
node: [R, e, n, a, i, nice],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
|
"strict": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"preserveConstEnums": false,
|
"preserveConstEnums": false,
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
type DecoratorFactory<T, U> = (target: T, propertyKey: string, descriptor: PropertyDescriptor) => void;
|
|
@ -1,19 +1,19 @@
|
||||||
declare const enum IpcChannels {
|
declare const enum IpcChannel {
|
||||||
ERROR = 'ERROR',
|
|
||||||
LOGIN = 'LOGIN',
|
LOGIN = 'LOGIN',
|
||||||
LOGGED_IN = 'LOGGED_IN',
|
LOGGED_IN = 'LOGGED_IN',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IIpcPayload {
|
interface IIpcPayload {
|
||||||
id: string;
|
id: string;
|
||||||
data: any;
|
data: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IIpcResponse {
|
interface IIpcResponse {
|
||||||
id: string;
|
id: string;
|
||||||
success: boolean;
|
success: boolean;
|
||||||
data?: any;
|
data?: unknown;
|
||||||
error?: any;
|
// just the error message
|
||||||
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ICredentials {
|
interface ICredentials {
|
||||||
|
@ -22,10 +22,11 @@ interface ICredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IIpcClient {
|
interface IIpcClient {
|
||||||
ask: (channel: IpcChannels, data?: any) => Promise<any>;
|
ask: (channel: IpcChannel, data?: unknown) => Promise<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IIpcServer {
|
type IpcHandler = (data?: unknown) => Promise<unknown>;
|
||||||
answer: (channel: IpcChannels, handler: (data?: any) => Promise<any>) => void;
|
|
||||||
send: (channel: IpcChannels, data: any) => void;
|
interface IIpcController {
|
||||||
|
get(): IIpcController;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
interface IMock {
|
|
||||||
original: object;
|
|
||||||
mock: {
|
|
||||||
[key: string]: (mock: any) => void;
|
|
||||||
};
|
|
||||||
restore: () => void;
|
|
||||||
}
|
|
Loading…
Reference in New Issue