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