diff --git a/package-lock.json b/package-lock.json index 4c1f537..2dc5296 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zibri", - "version": "2.1.2", + "version": "2.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zibri", - "version": "2.1.2", + "version": "2.1.3", "license": "MIT", "dependencies": { "@fastify/busboy": "^3.2.0", diff --git a/package.json b/package.json index fa5096d..8125ec3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zibri", - "version": "2.1.2", + "version": "2.1.3", "main": "./dist/index.js", "module": "./dist/index.mjs", "repository": { diff --git a/src/data-source/data-sources/postgres-data-source.model.ts b/src/data-source/data-sources/postgres-data-source.model.ts index e4aa01e..a842ba7 100644 --- a/src/data-source/data-sources/postgres-data-source.model.ts +++ b/src/data-source/data-sources/postgres-data-source.model.ts @@ -247,35 +247,27 @@ export abstract class PostgresDataSource implements DataSourceInterface { const inverseHasUpdate: boolean = this.hasCascadeFlag(inv.cascade, 'update'); const inverseHasInsert: boolean = this.hasCascadeFlag(inv.cascade, 'insert'); - let onDelete: OnDeleteType | undefined; - let onUpdate: OnUpdateType | undefined; - let persistence: boolean = false; - if (thisHasRemove || inverseHasRemove) { - onDelete = 'CASCADE'; - } - if (thisHasUpdate || inverseHasUpdate) { - onUpdate = 'CASCADE'; - } - if (thisHasInsert || inverseHasInsert) { - persistence = true; - } + const onDelete: OnDeleteType | undefined = thisHasRemove || inverseHasRemove ? 'CASCADE' : undefined; + const onUpdate: OnUpdateType | undefined = thisHasUpdate || inverseHasUpdate ? 'CASCADE' : undefined; + const persistence: boolean = 'persistence' in metadata ? metadata.persistence : thisHasInsert || inverseHasInsert; + const nullable: boolean = typeof metadata.required === 'boolean' ? !metadata.required : true; switch (metadata.type) { case Relation.ONE_TO_ONE: case Relation.ONE_TO_MANY: case Relation.MANY_TO_MANY: { return { - nullable: typeof metadata.required === 'boolean' ? !metadata.required : true, + nullable, ...metadata, inverseSide: metadata.inverseSide as string, onDelete, onUpdate, - persistence: 'persistence' in metadata ? metadata.persistence : persistence + persistence }; } case Relation.MANY_TO_ONE: { return { - nullable: typeof metadata.required === 'boolean' ? !metadata.required : true, + nullable, joinColumn: true, ...metadata, inverseSide: metadata.inverseSide as string, @@ -303,10 +295,10 @@ export abstract class PostgresDataSource implements DataSourceInterface { * @returns Typeorm column options. * @throws When the metadata is incorrect. */ - // eslint-disable-next-line sonar/cognitive-complexity protected propertyToColumnOptions( metadata: ExcludeStrict> ): EntitySchemaColumnOptions { + const nullable: boolean = typeof metadata.required === 'boolean' ? !metadata.required : true; switch (metadata.type) { case 'file': case 'boolean': @@ -314,7 +306,7 @@ export abstract class PostgresDataSource implements DataSourceInterface { case 'unknown': case 'date': { return { - nullable: typeof metadata.required === 'boolean' ? !metadata.required : true, + nullable, ...metadata, type: this.columnTypeMapping[metadata.type], default: undefined @@ -323,14 +315,14 @@ export abstract class PostgresDataSource implements DataSourceInterface { case 'array': { if (metadata.items.type === 'object') { return { - nullable: typeof metadata.required === 'boolean' ? !metadata.required : true, + nullable, ...metadata, type: this.columnTypeMapping[metadata.items.type], default: undefined }; } return { - nullable: typeof metadata.required === 'boolean' ? !metadata.required : true, + nullable, ...metadata, type: this.columnTypeMapping[metadata.items.type], array: true, @@ -339,21 +331,22 @@ export abstract class PostgresDataSource implements DataSourceInterface { } case 'number': { return { - nullable: typeof metadata.required === 'boolean' ? !metadata.required : true, + nullable, generated: metadata.primary ? 'increment' : undefined, ...metadata, type: this.columnTypeMapping[metadata.type], default: undefined, transformer: { // eslint-disable-next-line unicorn/no-null - to: (v: number | null) => v ? String(v) : null, - from: (v: string | null) => v ? Number(v) : undefined + to: (v: number | null) => v != null ? String(v) : null, + // eslint-disable-next-line unicorn/no-null + from: (v: string | null) => v != null ? Number(v) : undefined } }; } case 'string': { return { - nullable: typeof metadata.required === 'boolean' ? !metadata.required : true, + nullable, generated: metadata.primary ? 'uuid' : undefined, ...metadata, type: metadata.format === 'uuid' || metadata.primary ? 'uuid' : this.columnTypeMapping[metadata.type], diff --git a/src/data-source/repository.test.ts b/src/data-source/repository.test.ts new file mode 100644 index 0000000..8d5f696 --- /dev/null +++ b/src/data-source/repository.test.ts @@ -0,0 +1,82 @@ +import { beforeAll, afterAll, describe, it, expect } from '@jest/globals'; +import { PostgreSqlContainer, StartedPostgreSqlContainer } from '@testcontainers/postgresql'; + +import { DataSource } from './decorators'; +import { inject } from '../di'; +import { PostgresDataSource, PostgresOptions } from './data-sources'; +import { MigrationEntity } from './migration'; +import { POSTGRES_TEST_IMAGE } from '../__testing__'; +import { BaseEntity } from '../entity/base-entity.model'; +import { Newable, OmitStrict } from '../types'; +import { Repository } from './repository'; +import { Entity, Property } from '../entity'; + +@Entity() +class VisitStats extends BaseEntity { + @Property.number() + count!: number; + + @Property.number() + countFirstVisit!: number; + + @Property.string() + targetSite!: string; + + @Property.string({ required: false }) + referrer: string | undefined; + + @Property.string() + domain!: string; + + @Property.date() + date!: Date; +} + +@DataSource() +class TestDataSource extends PostgresDataSource { + options: PostgresOptions = { + host: 'localhost', + username: 'postgres', + password: 'password', + database: 'db', + synchronize: true + }; + entities: Newable[] = [MigrationEntity, VisitStats]; +} + +describe('repository', () => { + let container: StartedPostgreSqlContainer; + let ds: TestDataSource; + + beforeAll(async () => { + container = await new PostgreSqlContainer(POSTGRES_TEST_IMAGE) + .withDatabase('db') + .withUsername('postgres') + .withPassword('password') + .start(); + ds = inject(TestDataSource); + ds.options = { + ...ds.options, + port: container.getMappedPort(5432) + }; + await ds.init(); + }, 20000); + + afterAll(async () => { + await container.stop(); + }); + + it('create', async () => { + const repo: Repository = ds.getRepository(VisitStats); + const visitStats: OmitStrict = { + count: 1, + countFirstVisit: 0, + targetSite: '/test', + referrer: 'google.de', + date: new Date(), + domain: 'localhost' + }; + await repo.create(visitStats); + expect((await repo.findAll()).length).toBe(1); + }); +}); \ No newline at end of file diff --git a/src/validation/validation.service.ts b/src/validation/validation.service.ts index 6641f4f..4eef435 100644 --- a/src/validation/validation.service.ts +++ b/src/validation/validation.service.ts @@ -240,10 +240,11 @@ export class ValidationService implements ValidationServiceInterface { } const fullKey: string = parentKey ? `${parentKey}.${key}` : key; - if (property == undefined && (typeof metadata.required === 'boolean' ? metadata.required : metadata.required(entity))) { + const required: boolean = typeof metadata.required === 'boolean' ? metadata.required : metadata.required(entity); + if (property == undefined && required) { return [new IsRequiredValidationProblem(fullKey)]; } - if (property == undefined && !(typeof metadata.required === 'boolean' ? metadata.required : metadata.required(entity))) { + if (property == undefined && !required) { return []; } if (!Array.isArray(property)) { @@ -271,10 +272,11 @@ export class ValidationService implements ValidationServiceInterface { } const fullKey: string = parentKey ? `${parentKey}.${key}` : key; - if (property == undefined && (typeof metadata.required === 'boolean' ? metadata.required : metadata.required(entity))) { + const required: boolean = typeof metadata.required === 'boolean' ? metadata.required : metadata.required(entity); + if (property == undefined && required) { return [new IsRequiredValidationProblem(fullKey)]; } - if (property == undefined && !(typeof metadata.required === 'boolean' ? metadata.required : metadata.required(entity))) { + if (property == undefined && !required) { return []; } if (typeof property !== 'object') {