diff --git a/.eslintrc.json b/.eslintrc.json index 23edeca..7ac6c65 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,14 +1,11 @@ { "root": true, "plugins": ["@typescript-eslint", "import"], - "extends": ["eslint:recommended", "prettier"], + "extends": ["eslint:recommended", "prettier", "plugin:import/recommended"], "parserOptions": { "ecmaVersion": 2019, "sourceType": "module" }, - "settings": { - "import/core-modules": ["electron"] - }, "env": { "browser": true, "node": true @@ -43,6 +40,8 @@ "no-constant-condition": ["error", { "checkLoops": false }], "no-throw-literal": "error", "curly": "error", + "no-promise-executor-return": "error", + "no-return-await": "error", "import/no-extraneous-dependencies": [ "error", @@ -50,6 +49,7 @@ "devDependencies": [ "**/*.{spec,mock}.*", "src/**/test/*", + "src/renderer.ts", "src/renderer/**/*", "declarations/**/*", "templates/**/*", @@ -57,6 +57,31 @@ ] } ], + "import/no-restricted-paths": [ + "error", + { + "zones": [ + { + "target": "./src/main", + "from": "./src", + "except": ["./main", "./shared"], + "message": "only import from main/shared" + }, + { + "target": "./src/renderer", + "from": "./src", + "except": ["./renderer", "./shared"], + "message": "only import from renderer/shared" + }, + { + "target": "./src/shared", + "from": "./src", + "except": ["./shared"], + "message": "only import from shared" + } + ] + } + ], "import/no-default-export": "error", "import/first": "error", "import/order": [ @@ -75,7 +100,9 @@ "plugin:@typescript-eslint/eslint-recommended", "plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/recommended-requiring-type-checking", - "prettier/@typescript-eslint" + "prettier/@typescript-eslint", + "plugin:import/typescript", + "plugin:import/electron" ], "parser": "@typescript-eslint/parser", "parserOptions": { diff --git a/src/main/core/container.ts b/src/main/core/container.ts index f8e0dec..5c117c1 100644 --- a/src/main/core/container.ts +++ b/src/main/core/container.ts @@ -9,6 +9,7 @@ import '../modules/nhentai/nhentai-ipc-controller'; import { NhentaiAppWindow } from '../modules/nhentai/nhentai-app-window'; import { NhentaiSourceGetter } from '../modules/nhentai/nhentai-source-getter'; import { Store } from '../modules/store/store'; +import '../modules/entity-api/entity-api-ipc-controller'; import BindingToSyntax = interfaces.BindingToSyntax; export const container = { diff --git a/src/main/entities/library/author-name.ts b/src/main/entities/library/author-name.ts index cb52395..e378cb0 100644 --- a/src/main/entities/library/author-name.ts +++ b/src/main/entities/library/author-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { Author } from './author'; @Entity() -export class AuthorName implements IdentifiableEntityInterface, NameEntityInterface { +export class AuthorName implements AuthorNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => Author, (author: Author) => author.names, { + @ManyToOne(() => Author, (author: AuthorEntityInterface) => author.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/author-role-name.ts b/src/main/entities/library/author-role-name.ts index 9130870..e65e64c 100644 --- a/src/main/entities/library/author-role-name.ts +++ b/src/main/entities/library/author-role-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { AuthorRole } from './author-role'; @Entity() -export class AuthorRoleName implements IdentifiableEntityInterface, NameEntityInterface { +export class AuthorRoleName implements AuthorRoleNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => AuthorRole, (authorRole: AuthorRole) => authorRole.names, { + @ManyToOne(() => AuthorRole, (authorRole: AuthorRoleEntityInterface) => authorRole.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/author-role.ts b/src/main/entities/library/author-role.ts index 636ee86..ce6aab8 100644 --- a/src/main/entities/library/author-role.ts +++ b/src/main/entities/library/author-role.ts @@ -2,14 +2,10 @@ import { Column, Entity, ManyToMany, OneToMany, PrimaryGeneratedColumn } from 't import { AuthorRoleName } from './author-role-name'; import { WorkAuthor } from './work-author'; -/** - * This entity describes the role an author has in a work. - * Examples: story writing, drawing, animating, publishing, ... - */ @Entity() -export class AuthorRole implements IdentifiableEntityInterface, MultiNamedEntityInterface, DescribableEntityInterface { +export class AuthorRole implements AuthorRoleEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -17,14 +13,11 @@ export class AuthorRole implements IdentifiableEntityInterface, MultiNamedEntity }) public nameCanonical!: string; - @OneToMany(() => AuthorRoleName, (authorRoleName: AuthorRoleName) => authorRoleName.entity) - public names!: Promise; + @OneToMany(() => AuthorRoleName, (authorRoleName: AuthorRoleNameEntityInterface) => authorRoleName.entity) + public names!: Promise; - /** - * relation to the entity connecting with the author and work - */ - @ManyToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.authorRoles) - public workAuthors!: Promise; + @ManyToMany(() => WorkAuthor, (workAuthor: WorkAuthorEntityInterface) => workAuthor.authorRoles) + public workAuthors!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/author.ts b/src/main/entities/library/author.ts index 6f45bc4..09bea0f 100644 --- a/src/main/entities/library/author.ts +++ b/src/main/entities/library/author.ts @@ -2,13 +2,10 @@ import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { AuthorName } from './author-name'; import { WorkAuthor } from './work-author'; -/** - * This entity represents a single real-world entity, be it a person or named group of persons. - */ @Entity() -export class Author implements IdentifiableEntityInterface, MultiNamedEntityInterface { +export class Author implements AuthorEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -16,12 +13,9 @@ export class Author implements IdentifiableEntityInterface, MultiNamedEntityInte }) public nameCanonical!: string; - @OneToMany(() => AuthorName, (authorName: AuthorName) => authorName.entity) - public names!: Promise; + @OneToMany(() => AuthorName, (authorName: AuthorNameEntityInterface) => authorName.entity) + public names!: Promise; - /** - * ultimately connects the author with a work and their role in that work - */ - @OneToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.author) - public workAuthors!: Promise; + @OneToMany(() => WorkAuthor, (workAuthor: WorkAuthorEntityInterface) => workAuthor.author, {}) + public workAuthors!: Promise; } diff --git a/src/main/entities/library/character-tag.ts b/src/main/entities/library/character-tag.ts index e323034..13f16aa 100644 --- a/src/main/entities/library/character-tag.ts +++ b/src/main/entities/library/character-tag.ts @@ -3,34 +3,25 @@ import { PercentCheck } from '../decorators/percent-check'; import { Tag } from './tag'; import { WorkCharacter } from './work-character'; -/** - * This tag entity tags a character in a work. - */ @Entity() @PercentCheck('weight') -export class CharacterTag implements IdentifiableEntityInterface, WeightedEntityInterface { +export class CharacterTag implements CharacterTagEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - /** - * the character ina work this tag describes - */ - @ManyToOne(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.characterTags, { + @ManyToOne(() => WorkCharacter, (workCharacter: WorkCharacterEntityInterface) => workCharacter.characterTags, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public workCharacter!: Promise; + public workCharacter!: Promise; - /** - * the describing tag - */ - @ManyToOne(() => Tag, (tag: Tag) => tag.characterTags, { + @ManyToOne(() => Tag, (tag: TagEntityInterface) => tag.characterTags, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public tag!: Promise; + public tag!: Promise; @Column('int', { nullable: true, diff --git a/src/main/entities/library/collection-name.ts b/src/main/entities/library/collection-name.ts index 3e8bf13..42c34c7 100644 --- a/src/main/entities/library/collection-name.ts +++ b/src/main/entities/library/collection-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { Collection } from './collection'; @Entity() -export class CollectionName implements IdentifiableEntityInterface, NameEntityInterface { +export class CollectionName implements CollectionNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => Collection, (collection: Collection) => collection.names, { + @ManyToOne(() => Collection, (collection: CollectionEntityInterface) => collection.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/collection-part.ts b/src/main/entities/library/collection-part.ts index 4f59aef..cd3f12e 100644 --- a/src/main/entities/library/collection-part.ts +++ b/src/main/entities/library/collection-part.ts @@ -2,34 +2,24 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { Collection } from './collection'; import { Work } from './work'; -/** - * This entity orders works in a collection. - * The main use case is chronological ordering. - */ @Entity() -export class CollectionPart implements IdentifiableEntityInterface, OrderableEntityInterface { +export class CollectionPart implements CollectionPartEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - /** - * the collection thw work is a part of - */ - @ManyToOne(() => Collection, (collection: Collection) => collection.parts, { + @ManyToOne(() => Collection, (collection: CollectionEntityInterface) => collection.parts, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public collection!: Promise; + public collection!: Promise; - /** - * the work inside the collection - */ - @ManyToOne(() => Work, (work: Work) => work.collectionParts, { + @ManyToOne(() => Work, (work: WorkEntityInterface) => work.collectionParts, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public work!: Promise; + public work!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/collection.ts b/src/main/entities/library/collection.ts index efa37c5..a2aca0c 100644 --- a/src/main/entities/library/collection.ts +++ b/src/main/entities/library/collection.ts @@ -2,18 +2,10 @@ import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { CollectionName } from './collection-name'; import { CollectionPart } from './collection-part'; -/** - * A collection is a set of works. - * For example, this can be a series or a set of alternate angles. - * What constitutes as a collection is ultimately up to the user. - * - * As a general rule of thumb: - * If authors of works see them as belonging together, they are a collection - */ @Entity() -export class Collection implements IdentifiableEntityInterface, MultiNamedEntityInterface { +export class Collection implements CollectionEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -21,12 +13,9 @@ export class Collection implements IdentifiableEntityInterface, MultiNamedEntity }) public nameCanonical!: string; - @OneToMany(() => CollectionName, (collectionName: CollectionName) => collectionName.entity) - public names!: Promise; + @OneToMany(() => CollectionName, (collectionName: CollectionNameEntityInterface) => collectionName.entity) + public names!: Promise; - /** - * the connecting entity between this collection and the work - */ - @OneToMany(() => CollectionPart, (collectionPart: CollectionPart) => collectionPart.collection) - public parts!: Promise; + @OneToMany(() => CollectionPart, (collectionPart: CollectionPartEntityInterface) => collectionPart.collection) + public parts!: Promise; } diff --git a/src/main/entities/library/copy.ts b/src/main/entities/library/copy.ts index d2074c9..f166da9 100644 --- a/src/main/entities/library/copy.ts +++ b/src/main/entities/library/copy.ts @@ -2,55 +2,33 @@ import { Column, Entity, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColum import { Source } from './source'; import { Work } from './work'; -/** - * A copy is the digital counterpart of a work. - * It corresponds to a unique file or set of files which represent the work on the users device. - * - * Multiple works can have multiple copies (think of different scans of a physical work, or lossy compression). - */ @Entity() -export class Copy implements IdentifiableEntityInterface { +export class Copy implements CopyEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - /** - * the work this entity is a copy of - */ - @ManyToOne(() => Work, (work: Work) => work.copies, { + @ManyToOne(() => Work, (work: WorkEntityInterface) => work.copies, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public original!: Promise; + public original!: Promise; - /** - * where to find this specific copy - */ - @ManyToMany(() => Source, (source: Source) => source.copies) + @ManyToMany(() => Source, (source: SourceEntityInterface) => source.copies) @JoinTable() - public sources!: Promise; + public sources!: Promise; - /** - * identifying hash of the file contents - */ @Column({ nullable: false, default: '', }) public hash!: string; - /** - * device location of the copy - */ @Column('text', { nullable: true, }) public location!: string | null; - /** - * the ordering of the copies belonging to the same work, - * lower number is higher ranked - */ @Column({ nullable: false, default: 0, diff --git a/src/main/entities/library/interaction-tag.ts b/src/main/entities/library/interaction-tag.ts index a95525b..c06b481 100644 --- a/src/main/entities/library/interaction-tag.ts +++ b/src/main/entities/library/interaction-tag.ts @@ -3,36 +3,24 @@ import { PercentCheck } from '../decorators/percent-check'; import { Tag } from './tag'; import { WorkCharacter } from './work-character'; -/** - * This tag entity tags an interaction between two characters. - */ @Entity() @PercentCheck('weight') -export class InteractionTag implements IdentifiableEntityInterface, WeightedEntityInterface { +export class InteractionTag implements InteractionTagEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - /** - * the describing tag - */ - @ManyToOne(() => Tag, (tag: Tag) => tag.interactionTags, { + @ManyToOne(() => Tag, (tag: TagEntityInterface) => tag.interactionTags, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public tag!: Promise; + public tag!: Promise; - /** - * the actors of this interaction - */ - @ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.interactWith) - public subjectCharacters!: Promise; + @ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacterEntityInterface) => workCharacter.interactWith) + public subjectCharacters!: Promise; - /** - * the receivers of this interaction - */ - @ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.interactedBy) - public objectCharacters!: Promise; + @ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacterEntityInterface) => workCharacter.interactedBy) + public objectCharacters!: Promise; @Column('int', { nullable: true, diff --git a/src/main/entities/library/language.ts b/src/main/entities/library/language.ts index c9dfc85..6759891 100644 --- a/src/main/entities/library/language.ts +++ b/src/main/entities/library/language.ts @@ -1,20 +1,11 @@ import { Entity, ManyToMany, PrimaryColumn } from 'typeorm'; import { Work } from './work'; -/** - * This entity is non-user-maintained and describes a language. - */ @Entity() -export class Language { - /** - * ISO 639-1 two-letter language code - */ +export class Language implements LanguageEntityInterface { @PrimaryColumn() public code!: string; - /** - * the works using this language - */ - @ManyToMany(() => Work, (work: Work) => work.languages) - public works!: Promise; + @ManyToMany(() => Work, (work: WorkEntityInterface) => work.languages) + public works!: Promise; } diff --git a/src/main/entities/library/site-name.ts b/src/main/entities/library/site-name.ts index 6b6d3b1..6a8254e 100644 --- a/src/main/entities/library/site-name.ts +++ b/src/main/entities/library/site-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { Site } from './site'; @Entity() -export class SiteName implements IdentifiableEntityInterface, NameEntityInterface { +export class SiteName implements SiteNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => Site, (site: Site) => site.names, { + @ManyToOne(() => Site, (site: SiteEntityInterface) => site.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/site.ts b/src/main/entities/library/site.ts index 6ea75f6..3318be3 100644 --- a/src/main/entities/library/site.ts +++ b/src/main/entities/library/site.ts @@ -2,13 +2,10 @@ import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { SiteName } from './site-name'; import { Source } from './source'; -/** - * This non-user-maintained entity describes an online provider of works which can be scraped. - */ @Entity() -export class Site implements IdentifiableEntityInterface, MultiNamedEntityInterface { +export class Site implements SiteEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -16,12 +13,9 @@ export class Site implements IdentifiableEntityInterface, MultiNamedEntityInterf }) public nameCanonical!: string; - @OneToMany(() => SiteName, (siteName: SiteName) => siteName.entity) - public names!: Promise; + @OneToMany(() => SiteName, (siteName: SiteNameEntityInterface) => siteName.entity) + public names!: Promise; - /** - * sources belonging to this site - */ - @OneToMany(() => Source, (source: Source) => source.site) - public sources!: Promise; + @OneToMany(() => Source, (source: SourceEntityInterface) => source.site) + public sources!: Promise; } diff --git a/src/main/entities/library/source.ts b/src/main/entities/library/source.ts index c458634..5418894 100644 --- a/src/main/entities/library/source.ts +++ b/src/main/entities/library/source.ts @@ -2,36 +2,24 @@ import { Column, Entity, ManyToMany, ManyToOne, PrimaryGeneratedColumn } from 't import { Copy } from './copy'; import { Site } from './site'; -/** - * This entity describes an external source of a copy, in most cases that is a website. - */ @Entity() -export class Source implements IdentifiableEntityInterface { +export class Source implements SourceEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - /** - * the uri to the sauce - */ @Column({ nullable: false, default: '', }) public uri!: string; - /** - * the site connected to the source - */ - @ManyToOne(() => Site, (site: Site) => site.sources, { + @ManyToOne(() => Site, (site: SiteEntityInterface) => site.sources, { nullable: true, onDelete: 'RESTRICT', onUpdate: 'CASCADE', }) - public site!: Promise | null; + public site!: Promise | null; - /** - * the copies which can be found here - */ - @ManyToMany(() => Copy, (copy: Copy) => copy.sources) - public copies!: Promise; + @ManyToMany(() => Copy, (copy: CopyEntityInterface) => copy.sources) + public copies!: Promise; } diff --git a/src/main/entities/library/tag-name.ts b/src/main/entities/library/tag-name.ts index a4bbdec..e87ba3d 100644 --- a/src/main/entities/library/tag-name.ts +++ b/src/main/entities/library/tag-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { Tag } from './tag'; @Entity() -export class TagName implements IdentifiableEntityInterface, NameEntityInterface { +export class TagName implements TagNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => Tag, (tag: Tag) => tag.names, { + @ManyToOne(() => Tag, (tag: TagEntityInterface) => tag.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/tag.ts b/src/main/entities/library/tag.ts index 35c3d0f..0ccb477 100644 --- a/src/main/entities/library/tag.ts +++ b/src/main/entities/library/tag.ts @@ -4,21 +4,10 @@ import { InteractionTag } from './interaction-tag'; import { TagName } from './tag-name'; import { WorkTag } from './work-tag'; -/** - * This entity is the main tag entity. - * Tags have a name and a description. - * They can tag a work, a character, or a character interaction. - * They can also be in a hierarchy - */ @Entity() -export class Tag - implements - IdentifiableEntityInterface, - MultiNamedEntityInterface, - DescribableEntityInterface, - HierarchicalEntityInterface { +export class Tag implements TagEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -26,26 +15,17 @@ export class Tag }) public nameCanonical!: string; - @OneToMany(() => TagName, (tagName: TagName) => tagName.entity) - public names!: Promise; + @OneToMany(() => TagName, (tagName: TagNameEntityInterface) => tagName.entity) + public names!: Promise; - /** - * this tag tagging a work - */ - @OneToMany(() => WorkTag, (workTag: WorkTag) => workTag.tag) - public workTags!: Promise; + @OneToMany(() => WorkTag, (workTag: WorkTagEntityInterface) => workTag.tag) + public workTags!: Promise; - /** - * this tag tagging characters - */ - @OneToMany(() => CharacterTag, (characterTag: CharacterTag) => characterTag.tag) - public characterTags!: Promise; + @OneToMany(() => CharacterTag, (characterTag: CharacterTagEntityInterface) => characterTag.tag) + public characterTags!: Promise; - /** - * this tag tagging a character interaction - */ - @OneToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.tag) - public interactionTags!: Promise; + @OneToMany(() => InteractionTag, (interactionTag: InteractionTagEntityInterface) => interactionTag.tag) + public interactionTags!: Promise; @ManyToMany(() => Tag, (tag: Tag) => tag.children) @JoinTable() diff --git a/src/main/entities/library/transformation-type-name.ts b/src/main/entities/library/transformation-type-name.ts index 3efa365..08a65c0 100644 --- a/src/main/entities/library/transformation-type-name.ts +++ b/src/main/entities/library/transformation-type-name.ts @@ -2,16 +2,20 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { TransformationType } from './transformation-type'; @Entity() -export class TransformationTypeName implements IdentifiableEntityInterface, NameEntityInterface { +export class TransformationTypeName implements TransformationTypeNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => TransformationType, (transformationType: TransformationType) => transformationType.names, { - nullable: false, - onDelete: 'CASCADE', - onUpdate: 'CASCADE', - }) - public entity!: Promise; + @ManyToOne( + () => TransformationType, + (transformationType: TransformationTypeEntityInterface) => transformationType.names, + { + nullable: false, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + } + ) + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/transformation-type.ts b/src/main/entities/library/transformation-type.ts index ed47ebc..3cb9fe3 100644 --- a/src/main/entities/library/transformation-type.ts +++ b/src/main/entities/library/transformation-type.ts @@ -2,15 +2,10 @@ import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; import { Transformation } from './transformation'; import { TransformationTypeName } from './transformation-type-name'; -/** - * This entity describes a transformation type. - * Possible type: translation, decensor, collection, ... - */ @Entity() -export class TransformationType - implements IdentifiableEntityInterface, MultiNamedEntityInterface, DescribableEntityInterface { +export class TransformationType implements TransformationTypeEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -20,9 +15,10 @@ export class TransformationType @OneToMany( () => TransformationTypeName, - (transformationTypeName: TransformationTypeName) => transformationTypeName.entity + (transformationTypeName: TransformationTypeNameEntityInterface) => transformationTypeName.entity, + {} ) - public names!: Promise; + public names!: Promise; @Column({ nullable: false, @@ -33,8 +29,8 @@ export class TransformationType /** * the transformations of this type */ - @OneToMany(() => Transformation, (transformation: Transformation) => transformation.type) - public transformations!: Promise; + @OneToMany(() => Transformation, (transformation: TransformationEntityInterface) => transformation.type) + public transformations!: Promise; /** * if that transformation conserves the tags of the original work diff --git a/src/main/entities/library/transformation.ts b/src/main/entities/library/transformation.ts index 29b7987..865878e 100644 --- a/src/main/entities/library/transformation.ts +++ b/src/main/entities/library/transformation.ts @@ -2,17 +2,11 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { TransformationType } from './transformation-type'; import { Work } from './work'; -/** - * This entity describes how one work is transformed to another. - */ @Entity() -export class Transformation implements IdentifiableEntityInterface, OrderableEntityInterface { +export class Transformation implements TransformationEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - /** - * the work based on the original - */ @ManyToOne(() => Work, (work: Work) => work.transformationOf, { nullable: false, onDelete: 'CASCADE', @@ -20,25 +14,23 @@ export class Transformation implements IdentifiableEntityInterface, OrderableEnt }) public byWork!: Promise; - /** - * the transformation type - */ - @ManyToOne(() => TransformationType, (transformationType: TransformationType) => transformationType.transformations, { - nullable: false, - onDelete: 'RESTRICT', - onUpdate: 'CASCADE', - }) - public type!: Promise; + @ManyToOne( + () => TransformationType, + (transformationType: TransformationTypeEntityInterface) => transformationType.transformations, + { + nullable: false, + onDelete: 'RESTRICT', + onUpdate: 'CASCADE', + } + ) + public type!: Promise; - /** - * the original work - */ - @ManyToOne(() => Work, (work: Work) => work.transformedBy, { - nullable: false, + @ManyToOne(() => Work, (work: WorkEntityInterface) => work.transformedBy, { + nullable: true, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public ofWork!: Promise; + public ofWork!: Promise | null; @Column({ nullable: false, diff --git a/src/main/entities/library/work-author.ts b/src/main/entities/library/work-author.ts index ef3d261..c90ed0e 100644 --- a/src/main/entities/library/work-author.ts +++ b/src/main/entities/library/work-author.ts @@ -3,40 +3,28 @@ import { Author } from './author'; import { AuthorRole } from './author-role'; import { Work } from './work'; -/** - * This entity connects authors with their work and their role therein. - */ @Entity() -export class WorkAuthor implements IdentifiableEntityInterface, OrderableEntityInterface { +export class WorkAuthor implements WorkAuthorEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - /** - * the work - */ - @ManyToOne(() => Work, (work: Work) => work.workAuthors, { + @ManyToOne(() => Work, (work: WorkEntityInterface) => work.workAuthors, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public work!: Promise; + public work!: Promise; - /** - * the roles of the author in the work - */ - @ManyToMany(() => AuthorRole, (authorRole: AuthorRole) => authorRole.workAuthors) + @ManyToMany(() => AuthorRole, (authorRole: AuthorRoleEntityInterface) => authorRole.workAuthors) @JoinTable() - public authorRoles!: Promise; + public authorRoles!: Promise; - /** - * the author - */ - @ManyToOne(() => Author, (author: Author) => author.workAuthors, { + @ManyToOne(() => Author, (author: AuthorEntityInterface) => author.workAuthors, { nullable: false, onDelete: 'RESTRICT', onUpdate: 'CASCADE', }) - public author!: Promise; + public author!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/work-character-name.ts b/src/main/entities/library/work-character-name.ts index 6fe4241..5976cb8 100644 --- a/src/main/entities/library/work-character-name.ts +++ b/src/main/entities/library/work-character-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { WorkCharacter } from './work-character'; @Entity() -export class WorkCharacterName implements IdentifiableEntityInterface, NameEntityInterface { +export class WorkCharacterName implements WorkCharacterNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.names, { + @ManyToOne(() => WorkCharacter, (workCharacter: WorkCharacterEntityInterface) => workCharacter.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/work-character.ts b/src/main/entities/library/work-character.ts index 0e246fe..de32dbb 100644 --- a/src/main/entities/library/work-character.ts +++ b/src/main/entities/library/work-character.ts @@ -5,14 +5,10 @@ import { Work } from './work'; import { WorkCharacterName } from './work-character-name'; import { WorldCharacter } from './world-character'; -/** - * This entity describes a character in a work. - * The character can be original or based on one or more existing characters. - */ @Entity() -export class WorkCharacter implements IdentifiableEntityInterface, MultiNamedEntityInterface { +export class WorkCharacter implements WorkCharacterEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -20,40 +16,24 @@ export class WorkCharacter implements IdentifiableEntityInterface, MultiNamedEnt }) public nameCanonical!: string; - @OneToMany(() => WorkCharacterName, (workCharacterName: WorkCharacterName) => workCharacterName.entity) - public names!: Promise; + @OneToMany(() => WorkCharacterName, (workCharacterName: WorkCharacterNameEntityInterface) => workCharacterName.entity) + public names!: Promise; - /** - * the works the character is a part of - * one work character can be part of multiple works because of series - */ - @ManyToMany(() => Work, (work: Work) => work.workCharacters) - public works!: Promise; + @ManyToMany(() => Work, (work: WorkEntityInterface) => work.workCharacters) + public works!: Promise; - /** - * interaction with other characters as actor - */ - @ManyToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.subjectCharacters) + @ManyToMany(() => InteractionTag, (interactionTag: InteractionTagEntityInterface) => interactionTag.subjectCharacters) @JoinTable() - public interactWith!: Promise; + public interactWith!: Promise; - /** - * interaction with other characters as receiver - */ - @ManyToMany(() => InteractionTag, (interactionTag: InteractionTag) => interactionTag.objectCharacters) + @ManyToMany(() => InteractionTag, (interactionTag: InteractionTagEntityInterface) => interactionTag.objectCharacters) @JoinTable() - public interactedBy!: Promise; + public interactedBy!: Promise; - /** - * tags connected to the character - */ - @OneToMany(() => CharacterTag, (characterTag: CharacterTag) => characterTag.workCharacter) - public characterTags!: Promise; + @OneToMany(() => CharacterTag, (characterTag: CharacterTagEntityInterface) => characterTag.workCharacter) + public characterTags!: Promise; - /** - * existing characters character is based on - */ - @ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.workCharacters) + @ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacterEntityInterface) => worldCharacter.workCharacters) @JoinTable() - public worldCharacters!: Promise; + public worldCharacters!: Promise; } diff --git a/src/main/entities/library/work-name.ts b/src/main/entities/library/work-name.ts index e7bb189..1866dc5 100644 --- a/src/main/entities/library/work-name.ts +++ b/src/main/entities/library/work-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { Work } from './work'; @Entity() -export class WorkName implements IdentifiableEntityInterface, NameEntityInterface { +export class WorkName implements WorkNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => Work, (work: Work) => work.names, { + @ManyToOne(() => Work, (work: WorkEntityInterface) => work.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/work-tag.ts b/src/main/entities/library/work-tag.ts index dce059e..7fb3989 100644 --- a/src/main/entities/library/work-tag.ts +++ b/src/main/entities/library/work-tag.ts @@ -3,34 +3,25 @@ import { PercentCheck } from '../decorators/percent-check'; import { Tag } from './tag'; import { Work } from './work'; -/** - * This tag entity tags a work. - */ @Entity() @PercentCheck('weight') -export class WorkTag implements IdentifiableEntityInterface, WeightedEntityInterface { +export class WorkTag implements WorkTagEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - /** - * the describing tag - */ - @ManyToOne(() => Tag, (tag: Tag) => tag.workTags, { + @ManyToOne(() => Tag, (tag: TagEntityInterface) => tag.workTags, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public tag!: Promise; + public tag!: Promise; - /** - * the tagged work - */ - @ManyToOne(() => Work, (work: Work) => work.workTags, { + @ManyToOne(() => Work, (work: WorkEntityInterface) => work.workTags, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public work!: Promise; + public work!: Promise; @Column('int', { nullable: true, diff --git a/src/main/entities/library/work.ts b/src/main/entities/library/work.ts index 1b609a2..06f0547 100644 --- a/src/main/entities/library/work.ts +++ b/src/main/entities/library/work.ts @@ -10,16 +10,11 @@ import { WorkName } from './work-name'; import { WorkTag } from './work-tag'; import { World } from './world'; -/** - * This is the main library entity. - * - * It describes a work of art organized by this software. - */ @Entity() @PercentCheck('rating') -export class Work implements IdentifiableEntityInterface, MultiNamedEntityInterface { +export class Work implements WorkEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -27,88 +22,55 @@ export class Work implements IdentifiableEntityInterface, MultiNamedEntityInterf }) public nameCanonical!: string; - @OneToMany(() => WorkName, (workName: WorkName) => workName.entity) - public names!: Promise; + @OneToMany(() => WorkName, (workName: WorkNameEntityInterface) => workName.entity) + public names!: Promise; - /** - * digital representations of this work - */ - @OneToMany(() => Copy, (copy: Copy) => copy.original) - public copies!: Promise; + @OneToMany(() => Copy, (copy: CopyEntityInterface) => copy.original, {}) + public copies!: Promise; - /** - * other works this work is a transformation of - */ - @OneToMany(() => Transformation, (transformation: Transformation) => transformation.byWork) - public transformationOf!: Promise; + @OneToMany(() => Transformation, (transformation: TransformationEntityInterface) => transformation.byWork) + public transformationOf!: Promise; - /** - * other works this work is transformed by - */ - @OneToMany(() => Transformation, (transformation: Transformation) => transformation.ofWork) - public transformedBy!: Promise; + @OneToMany(() => Transformation, (transformation: TransformationEntityInterface) => transformation.ofWork) + public transformedBy!: Promise; - /** - * the authors/publishers of this work - */ - @OneToMany(() => WorkAuthor, (workAuthor: WorkAuthor) => workAuthor.work) - public workAuthors!: Promise; + @OneToMany(() => WorkAuthor, (workAuthor: WorkAuthorEntityInterface) => workAuthor.work) + public workAuthors!: Promise; - /** - * tags describing this work - */ - @OneToMany(() => WorkTag, (workTag: WorkTag) => workTag.work) - public workTags!: Promise; + @OneToMany(() => WorkTag, (workTag: WorkTagEntityInterface) => workTag.work) + public workTags!: Promise; - /** - * characters in this work - */ - @ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.works) + @ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacterEntityInterface) => workCharacter.works) @JoinTable() - public workCharacters!: Promise; + public workCharacters!: Promise; - /** - * fictional worlds in which this work takes place - */ - @ManyToMany(() => World, (world: World) => world.works) + @ManyToMany(() => World, (world: WorldEntityInterface) => world.works) @JoinTable() - public worlds!: Promise; + public worlds!: Promise; - /** - * if this work i canon in above fictional world - */ @Column({ nullable: false, default: false, }) public isCanonical!: boolean; - /** - * the user rating of this work - */ @Column('int', { nullable: true, }) public rating!: number | null; - /** - * the release date of the work - */ @Column('date', { nullable: true, }) - public releaseDate!: Date | null; + public releaseDate!: string | null; - /** - * the languages of the work (if applicable) - */ - @ManyToMany(() => Language, (language: Language) => language.works) + @ManyToMany(() => Language, (language: LanguageEntityInterface) => language.works) @JoinTable() - public languages!: Promise; + public languages!: Promise; /** * the collections this work is a part of */ - @OneToMany(() => CollectionPart, (collectionPart: CollectionPart) => collectionPart.work) - public collectionParts!: Promise; + @OneToMany(() => CollectionPart, (collectionPart: CollectionPartEntityInterface) => collectionPart.work) + public collectionParts!: Promise; } diff --git a/src/main/entities/library/world-character-name.ts b/src/main/entities/library/world-character-name.ts index 233f64c..0807d92 100644 --- a/src/main/entities/library/world-character-name.ts +++ b/src/main/entities/library/world-character-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { WorldCharacter } from './world-character'; @Entity() -export class WorldCharacterName implements IdentifiableEntityInterface, NameEntityInterface { +export class WorldCharacterName implements WorldCharacterNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.names, { + @ManyToOne(() => WorldCharacter, (worldCharacter: WorldCharacterEntityInterface) => worldCharacter.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/world-character.ts b/src/main/entities/library/world-character.ts index 6666ee3..582d429 100644 --- a/src/main/entities/library/world-character.ts +++ b/src/main/entities/library/world-character.ts @@ -3,14 +3,10 @@ import { WorkCharacter } from './work-character'; import { World } from './world'; import { WorldCharacterName } from './world-character-name'; -/** - * This entity describes a canon character in a fictional world. - */ @Entity() -export class WorldCharacter - implements IdentifiableEntityInterface, MultiNamedEntityInterface, HierarchicalEntityInterface { +export class WorldCharacter implements WorldCharacterEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -18,25 +14,22 @@ export class WorldCharacter }) public nameCanonical!: string; - @OneToMany(() => WorldCharacterName, (worldCharacterName: WorldCharacterName) => worldCharacterName.entity) - public names!: Promise; + @OneToMany( + () => WorldCharacterName, + (worldCharacterName: WorldCharacterNameEntityInterface) => worldCharacterName.entity + ) + public names!: Promise; - /** - * the characters in works which are based on this one - */ - @ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacter) => workCharacter.worldCharacters) + @ManyToMany(() => WorkCharacter, (workCharacter: WorkCharacterEntityInterface) => workCharacter.worldCharacters) public workCharacters!: Promise; - /** - * the fictional worlds this character is a part of - */ - @ManyToMany(() => World, (world: World) => world.worldCharacters) - public worlds!: Promise; + @ManyToMany(() => World, (world: WorldEntityInterface) => world.worldCharacters) + public worlds!: Promise; - @ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.children) + @ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacterEntityInterface) => worldCharacter.children) @JoinTable() - public parents!: Promise; + public parents!: Promise; - @ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.parents) - public children!: Promise; + @ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacterEntityInterface) => worldCharacter.parents) + public children!: Promise; } diff --git a/src/main/entities/library/world-name.ts b/src/main/entities/library/world-name.ts index 4bedd3d..0a8b2c9 100644 --- a/src/main/entities/library/world-name.ts +++ b/src/main/entities/library/world-name.ts @@ -2,16 +2,16 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; import { World } from './world'; @Entity() -export class WorldName implements IdentifiableEntityInterface, NameEntityInterface { +export class WorldName implements WorldNameEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; - @ManyToOne(() => World, (world: World) => world.names, { + @ManyToOne(() => World, (world: WorldEntityInterface) => world.names, { nullable: false, onDelete: 'CASCADE', onUpdate: 'CASCADE', }) - public entity!: Promise; + public entity!: Promise; @Column({ nullable: false, diff --git a/src/main/entities/library/world.ts b/src/main/entities/library/world.ts index fc60b56..742e92f 100644 --- a/src/main/entities/library/world.ts +++ b/src/main/entities/library/world.ts @@ -3,14 +3,10 @@ import { Work } from './work'; import { WorldCharacter } from './world-character'; import { WorldName } from './world-name'; -/** - * This entity describes a fictional world. - */ @Entity() -export class World - implements IdentifiableEntityInterface, MultiNamedEntityInterface, HierarchicalEntityInterface { +export class World implements WorldEntityInterface { @PrimaryGeneratedColumn() - public id!: number; + public readonly id!: number; @Column({ nullable: false, @@ -18,26 +14,20 @@ export class World }) public nameCanonical!: string; - @OneToMany(() => WorldName, (worldName: WorldName) => worldName.entity) - public names!: Promise; + @OneToMany(() => WorldName, (worldName: WorldNameEntityInterface) => worldName.entity) + public names!: Promise; - /** - * works taking place in this world - */ - @ManyToMany(() => Work, (work: Work) => work.worlds) - public works!: Promise; + @ManyToMany(() => Work, (work: WorkEntityInterface) => work.worlds) + public works!: Promise; - /** - * canon characters in this world - */ - @ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacter) => worldCharacter.worlds) + @ManyToMany(() => WorldCharacter, (worldCharacter: WorldCharacterEntityInterface) => worldCharacter.worlds) @JoinTable() - public worldCharacters!: Promise; + public worldCharacters!: Promise; - @ManyToMany(() => World, (world: World) => world.parents) - public children!: Promise; + @ManyToMany(() => World, (world: WorldEntityInterface) => world.parents) + public children!: Promise; - @ManyToMany(() => World, (world: World) => world.children) + @ManyToMany(() => World, (world: WorldEntityInterface) => world.children) @JoinTable() - public parents!: Promise; + public parents!: Promise; } diff --git a/src/main/migrations/library/1597705000730-initial_migration.ts b/src/main/migrations/library/1611508597488-initial.ts similarity index 99% rename from src/main/migrations/library/1597705000730-initial_migration.ts rename to src/main/migrations/library/1611508597488-initial.ts index a58d000..5ab3e61 100644 --- a/src/main/migrations/library/1597705000730-initial_migration.ts +++ b/src/main/migrations/library/1611508597488-initial.ts @@ -1,7 +1,7 @@ import type { MigrationInterface, QueryRunner } from 'typeorm'; -export class initialMigration1597705000730 implements MigrationInterface { - name = 'initialMigration1597705000730'; +export class initial1611508597488 implements MigrationInterface { + name = 'initial1611508597488'; public async up(queryRunner: QueryRunner): Promise { await queryRunner.query( @@ -39,7 +39,7 @@ export class initialMigration1597705000730 implements MigrationInterface { `CREATE TABLE "transformation_type" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "nameCanonical" varchar NOT NULL DEFAULT (''), "description" varchar NOT NULL DEFAULT (''), "conservesTags" boolean NOT NULL DEFAULT (0))` ); await queryRunner.query( - `CREATE TABLE "transformation" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "order" integer NOT NULL DEFAULT (0), "byWorkId" integer NOT NULL, "typeId" integer NOT NULL, "ofWorkId" integer NOT NULL)` + `CREATE TABLE "transformation" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "order" integer NOT NULL DEFAULT (0), "byWorkId" integer NOT NULL, "typeId" integer NOT NULL, "ofWorkId" integer)` ); await queryRunner.query( `CREATE TABLE "interaction_tag" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "weight" integer, "tagId" integer NOT NULL, CONSTRAINT "weight needs to be between 0 and 9007199254740991" CHECK (weight >= 0 AND weight <= 9007199254740991))` @@ -236,7 +236,7 @@ export class initialMigration1597705000730 implements MigrationInterface { await queryRunner.query(`DROP TABLE "transformation_type_name"`); await queryRunner.query(`ALTER TABLE "temporary_transformation_type_name" RENAME TO "transformation_type_name"`); await queryRunner.query( - `CREATE TABLE "temporary_transformation" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "order" integer NOT NULL DEFAULT (0), "byWorkId" integer NOT NULL, "typeId" integer NOT NULL, "ofWorkId" integer NOT NULL, CONSTRAINT "FK_263a368f9017f5725c4fa12351b" FOREIGN KEY ("byWorkId") REFERENCES "work" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_4deb36ce15d6547c1ed7e994720" FOREIGN KEY ("typeId") REFERENCES "transformation_type" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "FK_d41fc0471e72b5d1dda372a662c" FOREIGN KEY ("ofWorkId") REFERENCES "work" ("id") ON DELETE CASCADE ON UPDATE CASCADE)` + `CREATE TABLE "temporary_transformation" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "order" integer NOT NULL DEFAULT (0), "byWorkId" integer NOT NULL, "typeId" integer NOT NULL, "ofWorkId" integer, CONSTRAINT "FK_263a368f9017f5725c4fa12351b" FOREIGN KEY ("byWorkId") REFERENCES "work" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_4deb36ce15d6547c1ed7e994720" FOREIGN KEY ("typeId") REFERENCES "transformation_type" ("id") ON DELETE RESTRICT ON UPDATE CASCADE, CONSTRAINT "FK_d41fc0471e72b5d1dda372a662c" FOREIGN KEY ("ofWorkId") REFERENCES "work" ("id") ON DELETE CASCADE ON UPDATE CASCADE)` ); await queryRunner.query( `INSERT INTO "temporary_transformation"("id", "order", "byWorkId", "typeId", "ofWorkId") SELECT "id", "order", "byWorkId", "typeId", "ofWorkId" FROM "transformation"` @@ -788,7 +788,7 @@ export class initialMigration1597705000730 implements MigrationInterface { await queryRunner.query(`DROP TABLE "temporary_interaction_tag"`); await queryRunner.query(`ALTER TABLE "transformation" RENAME TO "temporary_transformation"`); await queryRunner.query( - `CREATE TABLE "transformation" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "order" integer NOT NULL DEFAULT (0), "byWorkId" integer NOT NULL, "typeId" integer NOT NULL, "ofWorkId" integer NOT NULL)` + `CREATE TABLE "transformation" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "order" integer NOT NULL DEFAULT (0), "byWorkId" integer NOT NULL, "typeId" integer NOT NULL, "ofWorkId" integer)` ); await queryRunner.query( `INSERT INTO "transformation"("id", "order", "byWorkId", "typeId", "ofWorkId") SELECT "id", "order", "byWorkId", "typeId", "ofWorkId" FROM "temporary_transformation"` diff --git a/src/main/migrations/library/1611508644004-add_languages.ts b/src/main/migrations/library/1611508644004-add_languages.ts new file mode 100644 index 0000000..1c20bf8 --- /dev/null +++ b/src/main/migrations/library/1611508644004-add_languages.ts @@ -0,0 +1,198 @@ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class addLanguages1611508644004 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ABKHAZIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.AFAR}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.AFRIKAANS}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.AKAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ALBANIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.AMHARIC}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ARABIC}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ARAGONESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ARMENIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ASSAMESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.AVARIC}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.AVESTAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.AYMARA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.AZERBAIJANI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BAMBARA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BASHKIR}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BASQUE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BELARUSIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BENGALI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BIHARI_LANGUAGES}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BISLAMA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BOSNIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BRETON}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BULGARIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.BURMESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CATALAN_VALENCIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CHAMORRO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CHECHEN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CHICHEWA_CHEWA_NYANJA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CHINESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CHUVASH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CORNISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CORSICAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CREE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CROATIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CZECH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.DANISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.DIVEHI_DHIVEHI_MALDIVIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.DUTCH_FLEMISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.DZONGKHA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ENGLISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ESPERANTO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ESTONIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.EWE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.FAROESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.FIJIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.FINNISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.FRENCH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.FULAH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.GALICIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.GEORGIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.GERMAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.GREEK_MODERN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.GUARANI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.GUJARATI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HAITIAN_HAITIAN_CREOLE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HAUSA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HEBREW}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HERERO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HINDI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HIRI_MOTU}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.HUNGARIAN}')`); + await queryRunner.query( + `INSERT INTO language VALUES('${LangCode.INTERLINGUA_INTERNATIONAL_AUXILIARY_LANGUAGE_ASSOCIATION}')` + ); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.INDONESIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.INTERLINGUE_OCCIDENTAL}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.IRISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.IGBO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.INUPIAQ}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.IDO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ICELANDIC}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ITALIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.INUKTITUT}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.JAPANESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.JAVANESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KALAALLISUT_GREENLANDIC}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KANNADA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KANURI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KASHMIRI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KAZAKH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.CENTRAL_KHMER}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KIKUYU_GIKUYU}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KINYARWANDA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KIRGHIZ_KYRGYZ}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KOMI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KONGO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KOREAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KURDISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.KUANYAMA_KWANYAMA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.LATIN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.LUXEMBOURGISH_LETZEBURGESCH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.GANDA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.LIMBURGAN_LIMBURGER_LIMBURGISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.LINGALA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.LAO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.LITHUANIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.LUBA_KATANGA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.LATVIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MANX}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MACEDONIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MALAGASY}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MALAY}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MALAYALAM}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MALTESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MAORI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MARATHI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MARSHALLESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.MONGOLIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NAURU}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NAVAJO_NAVAHO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NORTH_NDEBELE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NEPALI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NDONGA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NORWEGIAN_BOKMAL}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NORWEGIAN_NYNORSK}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NORWEGIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SICHUAN_YI_NUOSU}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SOUTH_NDEBELE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OCCITAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OJIBWA}')`); + await queryRunner.query( + `INSERT INTO language VALUES('${LangCode.CHURCH_SLAVIC_OLD_SLAVONIC_CHURCH_SLAVONIC_OLD_BULGARIAN_OLD_CHURCH_SLAVONIC}')` + ); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OROMO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ORIYA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.OSSETIAN_OSSETIC}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.PUNJABI_PANJABI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.PALI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.PERSIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.POLISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.PASHTO_PUSHTO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.PORTUGUESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.QUECHUA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ROMANSH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.RUNDI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ROMANIAN_MOLDAVIAN_MOLDOVAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.RUSSIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SANSKRIT}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SARDINIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SINDHI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.NORTHERN_SAMI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SAMOAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SANGO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SERBIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.GAELIC_SCOTTISH_GAELIC}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SHONA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SINHALA_SINHALESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SLOVAK}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SLOVENIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SOMALI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SOUTHERN_SOTHO}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SPANISH_CASTILIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SUNDANESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SWAHILI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SWATI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.SWEDISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TAMIL}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TELUGU}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TAJIK}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.THAI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TIGRINYA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TIBETAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TURKMEN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TAGALOG}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TSWANA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TONGA_TONGA_ISLANDS}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TURKISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TSONGA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TATAR}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TWI}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.TAHITIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.UIGHUR_UYGHUR}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.UKRAINIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.URDU}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.UZBEK}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.VENDA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.VIETNAMESE}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.VOLAPUK}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.WALLOON}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.WELSH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.WOLOF}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.WESTERN_FRISIAN}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.XHOSA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.YIDDISH}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.YORUBA}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ZHUANG_CHUANG}')`); + await queryRunner.query(`INSERT INTO language VALUES('${LangCode.ZULU}')`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.clearTable('language'); + } +} diff --git a/src/main/modules/app-window/app-window.ts b/src/main/modules/app-window/app-window.ts index 4f38955..6070c03 100644 --- a/src/main/modules/app-window/app-window.ts +++ b/src/main/modules/app-window/app-window.ts @@ -131,5 +131,21 @@ export abstract class AppWindow implements AppWindowInterface { }); } + protected getTime(selector: string): Promise { + return new Promise((resolve) => { + this.getWindow() + .webContents.executeJavaScript(`Date(document.querySelector('${selector}').dateTime).getTime()`) + .then((time) => { + resolve(time); + }) + .catch(() => { + void this.logger.warning( + `Could not get the of the presumed HTMLTimeElement with the selector '${selector}'.` + ); + resolve(undefined); + }); + }); + } + protected abstract load(window: BrowserWindow): Promise; } diff --git a/src/main/modules/app-window/url-app-window.ts b/src/main/modules/app-window/url-app-window.ts index 4d5dad1..b6a1a75 100644 --- a/src/main/modules/app-window/url-app-window.ts +++ b/src/main/modules/app-window/url-app-window.ts @@ -1,5 +1,4 @@ -import type { WebContents } from 'electron'; -import type { BrowserWindowConstructorOptions, LoadURLOptions } from 'electron'; +import type { WebContents, BrowserWindowConstructorOptions, LoadURLOptions } from 'electron'; import { promisify } from 'util'; import { AppWindow } from './app-window'; import type { UrlAppWindowInterface } from './url-app-window-interface'; diff --git a/src/main/modules/date/date-util.ts b/src/main/modules/date/date-util.ts new file mode 100644 index 0000000..6ffd371 --- /dev/null +++ b/src/main/modules/date/date-util.ts @@ -0,0 +1,6 @@ +export function dateObjectToString(date: Date): string { + return `${date.getUTCFullYear()}-${`${date.getUTCMonth()}`.padStart(2, '0')}-${`${date.getUTCDate()}`.padStart( + 2, + '0' + )}`; +} diff --git a/src/main/modules/entity-api/entity-api-ipc-controller.ts b/src/main/modules/entity-api/entity-api-ipc-controller.ts new file mode 100644 index 0000000..8bd7d27 --- /dev/null +++ b/src/main/modules/entity-api/entity-api-ipc-controller.ts @@ -0,0 +1,20 @@ +import { workSerializer } from '../../../shared/services/serialization/serializers/work'; +import { Database, getConnection } from '../../core/database'; +import { Work } from '../../entities/library/work'; +import { answer } from '../ipc/annotations/answer'; + +export class EntityApiIpcController implements IpcController { + private constructor() {} + + @answer(IpcChannel.ENTITY_GET_WORK) + public async getWork({ id }: { id: number }): Promise { + const connection = await getConnection(Database.LIBRARY); + const work = await connection.manager.getRepository(Work).findOneOrFail(id); + + return workSerializer.serialize(work); + } + + public get(): EntityApiIpcController { + return new EntityApiIpcController(); + } +} diff --git a/src/main/modules/nhentai/nhentai-app-window.spec.ts b/src/main/modules/nhentai/nhentai-app-window.spec.ts index 7346f36..e071660 100644 --- a/src/main/modules/nhentai/nhentai-app-window.spec.ts +++ b/src/main/modules/nhentai/nhentai-app-window.spec.ts @@ -17,6 +17,7 @@ describe('Nhentai App Window', () => { const nhentaiAppWindow: NhentaiAppWindowInterface = container.get('nhentai-app-window'); let expectedGallery: Nhentai.Gallery = { + url: 'https://nhentai.net/g/117300/', title: { pre: '[Homunculus]', main: 'Renai Sample', @@ -43,11 +44,14 @@ describe('Nhentai App Window', () => { 'uncensored', 'small breasts', ], + languages: ['english', 'translated'], + uploadTime: 1411853968970, }; let gallery = await nhentaiAppWindow.getGallery('117300'); expect(gallery).deep.equalInAnyOrder(expectedGallery, 'Renai Sample is not got correctly'); expectedGallery = { + url: 'https://nhentai.net/g/273405/', title: { pre: '(COMIC1☆12) [MOSQUITONE. (Great Mosu)]', main: 'Koisuru Dai Akuma | The Archdemon In Love', @@ -58,6 +62,8 @@ describe('Nhentai App Window', () => { parodies: ['gabriel dropout'], characters: ['satanichia kurumizawa mcdowell'], tags: ['sole female', 'sole male', 'defloration', 'uncensored', 'kissing'], + languages: ['english', 'translated'], + uploadTime: 1558833881932, }; gallery = await nhentaiAppWindow.getGallery('273405'); expect(gallery).deep.equalInAnyOrder(expectedGallery, 'The Archdemon in Love is not got correctly!'); diff --git a/src/main/modules/nhentai/nhentai-app-window.ts b/src/main/modules/nhentai/nhentai-app-window.ts index d1dff40..8ccb38b 100644 --- a/src/main/modules/nhentai/nhentai-app-window.ts +++ b/src/main/modules/nhentai/nhentai-app-window.ts @@ -34,6 +34,8 @@ import { loginPageIsReady, favoritePageIsReady, pageIsReady, + timeSelector, + tagLabelLanguages, } from './nhentai-util'; const waitInterval = 2000; @@ -98,7 +100,9 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai await this.open(); } + const bookUrl = getBookUrl(identifier); const gallery: Nhentai.Gallery = { + url: bookUrl, title: { pre: '', main: '', @@ -109,10 +113,11 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai parodies: [], characters: [], tags: [], + languages: [], + uploadTime: undefined, }; const release = await this.acquireLock(); - const bookUrl = getBookUrl(identifier); try { await this.loadGalleryPageSafe(bookUrl); @@ -141,6 +146,12 @@ export class NhentaiAppWindow extends CloudflareSiteAppWindow implements Nhentai this.getTags(tagLabelTags).then((tags: string[]) => { gallery.tags = tags; }), + this.getTags(tagLabelLanguages).then((languages: string[]) => { + gallery.languages = languages; + }), + this.getTime(timeSelector).then((time?: number) => { + gallery.uploadTime = time; + }), ]); this.close(); release(); diff --git a/src/main/modules/nhentai/nhentai-ipc-controller.ts b/src/main/modules/nhentai/nhentai-ipc-controller.ts index 03c08e9..ad206d6 100644 --- a/src/main/modules/nhentai/nhentai-ipc-controller.ts +++ b/src/main/modules/nhentai/nhentai-ipc-controller.ts @@ -1,7 +1,6 @@ import path from 'path'; import { createWriteStream } from 'fs-extra'; import { container } from '../../core/container'; -import { Database, getConnection } from '../../core/database'; import type { Work } from '../../entities/library/work'; import type { DialogInterface } from '../dialog/dialog-interface'; import { answer } from '../ipc/annotations/answer'; @@ -53,9 +52,8 @@ export class NhentaiIpcController implements IpcController { @answer(IpcChannel.NHENTAI_GET_WORK) public async nhentaiGetWork({ galleryId }: { galleryId: string }): Promise { const work = await this.nhentaiSourceGetter.find(galleryId); - const { manager } = await getConnection(Database.LIBRARY); - return manager.save(work); + return work; } public get(): NhentaiIpcController { diff --git a/src/main/modules/nhentai/nhentai-source-getter.ts b/src/main/modules/nhentai/nhentai-source-getter.ts index c945471..4f57005 100644 --- a/src/main/modules/nhentai/nhentai-source-getter.ts +++ b/src/main/modules/nhentai/nhentai-source-getter.ts @@ -1,7 +1,18 @@ import { injectable } from 'inversify'; +import { Database, getConnection } from '../../core/database'; import { inject } from '../../core/inject'; +import { Copy } from '../../entities/library/copy'; +import { Language } from '../../entities/library/language'; +import { Source } from '../../entities/library/source'; import { Work } from '../../entities/library/work'; +import { dateObjectToString } from '../date/date-util'; import type { SourceGetterInterface } from '../source/source-getter-interface'; +import { isNhentaiRealLanguage, languageToLangCode, NhentaiRealLanguage } from './nhentai-util'; + +async function getLanguage(nhentaiLanguageIdentifier: NhentaiRealLanguage): Promise { + const { manager } = await getConnection(Database.LIBRARY); + return manager.getRepository(Language).findOneOrFail(languageToLangCode[nhentaiLanguageIdentifier]); +} @injectable() export class NhentaiSourceGetter implements SourceGetterInterface { @@ -11,12 +22,39 @@ export class NhentaiSourceGetter implements SourceGetterInterface { this.nhentaiApi = nhentaiApi; } - public async find(identifier: string): Promise { + public async find(identifier: string): Promise { const gallery = await this.nhentaiApi.getGallery(identifier); const work = new Work(); + const copy = new Copy(); + const source = new Source(); + + const { manager } = await getConnection(Database.LIBRARY); + + source.uri = gallery.url; + + copy.sources = Promise.resolve([source]); work.nameCanonical = gallery.title.main; + if (gallery.uploadTime) { + work.releaseDate = dateObjectToString(new Date(gallery.uploadTime)); + } + if (gallery.languages.length) { + const filteredLanguages: NhentaiRealLanguage[] = gallery.languages.filter( + (language): language is NhentaiRealLanguage => { + if (language === 'translated') { + // new transformation of type 'translation' here (source getter needs more abstraction before this can be done elegantly) + return false; + } else if (isNhentaiRealLanguage(language)) { + return true; + } + return false; + } + ); + work.languages = Promise.all(filteredLanguages.map((language) => getLanguage(language))); + } + work.copies = Promise.resolve([copy]); + await manager.save([work, copy, source]); return work; } diff --git a/src/main/modules/nhentai/nhentai-util.ts b/src/main/modules/nhentai/nhentai-util.ts index e875395..15bba7d 100644 --- a/src/main/modules/nhentai/nhentai-util.ts +++ b/src/main/modules/nhentai/nhentai-util.ts @@ -19,12 +19,30 @@ export const postTitleSelector = 'h1.title .after'; export const labeledTagContainerSelector = '.tag-container.field-name'; export const tagSelector = '.tag'; export const tagNameSelector = 'span.name'; +export const timeSelector = 'time'; export const tagLabelParodies = 'Parodies'; export const tagLabelCharacters = 'Characters'; export const tagLabelTags = 'Tags'; export const tagLabelArtists = 'Artists'; export const tagLabelGroups = 'Groups'; +export const tagLabelLanguages = 'Languages'; + +export const languageToLangCode = { + japanese: LangCode.JAPANESE, + english: LangCode.ENGLISH, + chinese: LangCode.CHINESE, +}; +export type NhentaiRealLanguage = keyof typeof languageToLangCode; +export type NhentaiLanguage = NhentaiRealLanguage | 'translated'; + +export function isNhentaiLanguage(language: string): language is NhentaiLanguage { + return isNhentaiRealLanguage(language) || language === 'translated'; +} + +export function isNhentaiRealLanguage(language: string): language is NhentaiRealLanguage { + return Object.keys(languageToLangCode).includes(language); +} export function pageIsReady(webContents: WebContents): Promise { return webContents.executeJavaScript(`!!document.getElementById('content')`) as Promise; diff --git a/src/main/modules/nhentai/nhentai.d.ts b/src/main/modules/nhentai/nhentai.d.ts index 5829958..844c0d4 100644 --- a/src/main/modules/nhentai/nhentai.d.ts +++ b/src/main/modules/nhentai/nhentai.d.ts @@ -5,6 +5,7 @@ declare namespace Nhentai { }; type Gallery = { + url: string; title: { pre: string; main: string; @@ -15,5 +16,7 @@ declare namespace Nhentai { parodies: string[]; characters: string[]; tags: string[]; + languages: string[]; + uploadTime?: number; }; } diff --git a/src/main/modules/source/source-getter-interface.d.ts b/src/main/modules/source/source-getter-interface.d.ts index 23ca512..ad7fab1 100644 --- a/src/main/modules/source/source-getter-interface.d.ts +++ b/src/main/modules/source/source-getter-interface.d.ts @@ -1,5 +1,10 @@ -import { Work } from '../../entities/library/work'; - -interface SourceGetterInterface { - find(identifier: string): Promise; +/** + * This interface describes an object which can find works based on some identifying property. + */ +export interface SourceGetterInterface { + /** + * This method finds the work, deserializes it and all its relations into entities and persists them. + * @returns the persisted work + */ + find(identifier: string): Promise; } diff --git a/src/renderer/components/content/Work.svelte b/src/renderer/components/content/Work.svelte new file mode 100644 index 0000000..09ee471 --- /dev/null +++ b/src/renderer/components/content/Work.svelte @@ -0,0 +1,16 @@ + + +
{#if work}{JSON.stringify(work)}{/if}
diff --git a/src/renderer/components/modules/NhentaiGetWork.svelte b/src/renderer/components/modules/NhentaiGetWork.svelte index ff34512..aaee2b6 100644 --- a/src/renderer/components/modules/NhentaiGetWork.svelte +++ b/src/renderer/components/modules/NhentaiGetWork.svelte @@ -1,4 +1,5 @@