Skip to content

Commit

Permalink
feat(specification): persistence layer - migration
Browse files Browse the repository at this point in the history
  • Loading branch information
kgajowy committed Aug 10, 2021
1 parent 915bdb2 commit a2578fa
Show file tree
Hide file tree
Showing 8 changed files with 361 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class ScenarioSpecification1628585506694 implements MigrationInterface {
name = 'ScenarioSpecification1628585506694';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "specification" ("id" uuid NOT NULL, "scenario_id" uuid NOT NULL, "draft" boolean NOT NULL, CONSTRAINT "PK_01b2d90197e187e3187b2d888be" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "specification_feature" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "specification_feature_config_id" uuid NOT NULL, "feature_id" uuid NOT NULL, "calculated" boolean NOT NULL, CONSTRAINT "PK_fe6096ea6f25997b62b8a5c5f99" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TYPE "specification_feature_config_operation_enum" AS ENUM('split', 'stratification', 'copy')`,
);
await queryRunner.query(
`CREATE TABLE "specification_feature_config" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "specification_id" uuid NOT NULL, "base_feature_id" uuid NOT NULL, "against_feature_id" uuid, "operation" "specification_feature_config_operation_enum" NOT NULL, "features_determined" boolean NOT NULL, CONSTRAINT "PK_df1e25cf3972f83b1ff5bcb0337" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "specification" ADD CONSTRAINT "FK_2cecab1b81e5fa64f2fdbbaec76" FOREIGN KEY ("scenario_id") REFERENCES "scenarios"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "specification_feature" ADD CONSTRAINT "FK_ba3687896f22d3f57b3b570368e" FOREIGN KEY ("specification_feature_config_id") REFERENCES "specification_feature_config"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "specification_feature_config" ADD CONSTRAINT "FK_1f1f9830fbe8a94ffd5c86d0bca" FOREIGN KEY ("specification_id") REFERENCES "specification"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "specification_feature_config" DROP CONSTRAINT "FK_1f1f9830fbe8a94ffd5c86d0bca"`,
);
await queryRunner.query(
`ALTER TABLE "specification_feature" DROP CONSTRAINT "FK_ba3687896f22d3f57b3b570368e"`,
);
await queryRunner.query(
`ALTER TABLE "specification" DROP CONSTRAINT "FK_2cecab1b81e5fa64f2fdbbaec76"`,
);
await queryRunner.query(`DROP TABLE "specification_feature_config"`);
await queryRunner.query(
`DROP TYPE "specification_feature_config_operation_enum"`,
);
await queryRunner.query(`DROP TABLE "specification_feature"`);
await queryRunner.query(`DROP TABLE "specification"`);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,23 @@ export class SpecificationFeatureConfigApiEntity {

@OneToMany(
() => SpecificationFeatureApiEntity,
(specificationFeature) => specificationFeature.specificationFeatureConfigId,
(specificationFeature) => specificationFeature.specificationFeatureConfig,
{
cascade: true,
eager: true,
},
)
features?: SpecificationFeatureApiEntity[];

@Column({
type: `uuid`,
name: `base_feature_id`,
})
baseFeatureId!: string;

@Column({
type: `uuid`,
name: `against_feature_id`,
nullable: true,
})
againstFeatureId?: string | null;
Expand All @@ -55,6 +61,7 @@ export class SpecificationFeatureConfigApiEntity {

@Column({
type: `boolean`,
name: `features_determined`,
})
featuresDetermined!: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class SpecificationFeatureApiEntity {

@Column({
type: `uuid`,
name: `feature_id`,
})
featureId!: string;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,25 @@ import {
JoinColumn,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
PrimaryColumn,
} from 'typeorm';
import { Scenario } from '@marxan-api/modules/scenarios/scenario.api.entity';
import { SpecificationFeatureConfigApiEntity } from './specification-feature-config.api.entity';

@Entity(`specification`)
export class SpecificationApiEntity {
@PrimaryGeneratedColumn(`uuid`)
@PrimaryColumn({
type: `uuid`,
})
id!: string;

@OneToMany(
() => SpecificationFeatureConfigApiEntity,
(specificationFeatures) => specificationFeatures.specification,
(specificationFeaturesConfig) => specificationFeaturesConfig.specification,
{
cascade: true,
eager: true,
},
)
specificationFeaturesConfiguration?: SpecificationFeatureConfigApiEntity[];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,99 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Repository } from 'typeorm';
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm';

import { DbConnections } from '@marxan-api/ormconfig.connections';

import { FeatureConfigInput, Specification } from '../domain';
import { SpecificationRepository } from '../application/specification.repository';

import { SpecificationApiEntity } from './specification.api.entity';
import { SpecificationFeatureConfigApiEntity } from './specification-feature-config.api.entity';
import { SpecificationFeatureApiEntity } from './specification-feature.api.entity';

@Injectable()
export class DbSpecificationRepository implements SpecificationRepository {
constructor(
@InjectRepository(SpecificationApiEntity)
private readonly specificationRepo: Repository<SpecificationApiEntity>,
@InjectRepository(SpecificationFeatureConfigApiEntity)
private readonly specificationFeaturesRepo: Repository<SpecificationFeatureConfigApiEntity>,
private readonly specificationFeatureConfigRepo: Repository<SpecificationFeatureConfigApiEntity>,
@InjectRepository(SpecificationFeatureApiEntity)
private readonly specificationFeatureRepo: Repository<SpecificationFeatureApiEntity>,
@InjectEntityManager(DbConnections.default)
private readonly entityManager: EntityManager,
) {}

async findAllRelatedToFeatureConfig(
configuration: FeatureConfigInput,
): Promise<Specification[]> {
const specifications = await this.specificationRepo.find({
where: {
// seems like nested where does not fully work
// https://github.com/typeorm/typeorm/issues/2707
let builder = this.specificationRepo
.createQueryBuilder('spec')
.select('spec.id')
.leftJoin(
'specification_feature_config',
'config',
'config.specification_id = spec.id',
)
.where(`config.base_feature_id = :baseFeatureId`, {
baseFeatureId: configuration.baseFeatureId,
againstFeatureId: configuration.againstFeatureId
? configuration.againstFeatureId
: IsNull(),
})
.andWhere(`config.operation = :operation`, {
operation: configuration.operation,
},
loadEagerRelations: true,
});
});

if (configuration.againstFeatureId) {
builder = builder.andWhere(
`config.against_feature_id = :againstFeatureId`,
{
againstFeatureId: configuration.againstFeatureId,
},
);
} else {
builder.andWhere(`config.against_feature_id is null`);
}
const specIds = await builder.getRawMany<{
spec_id: string;
}>();
const specifications = await this.specificationRepo.findByIds(
specIds.map((row) => row.spec_id),
);

return specifications.map((specification) =>
this.#serialize(specification),
);
}

findAllRelatedToFeatures(features: string[]): Promise<Specification[]> {
return Promise.resolve([]);
async findAllRelatedToFeatures(features: string[]): Promise<Specification[]> {
const specIds = await this.specificationRepo
.createQueryBuilder('spec')
.select('spec.id')
.leftJoin(
`specification_feature_config`,
`config`,
`config.specification_id = spec.id`,
)
.leftJoin(
`specification_feature`,
`features`,
`config.id = features.specification_feature_config_id`,
)
.where(`features.feature_id::text IN(:featureIds)`, {
featureIds: features.join(','),
})
.getRawMany<{
spec_id: string;
}>();

const specifications = await this.specificationRepo.findByIds(
specIds.map((row) => row.spec_id),
);

return specifications.map((specification) =>
this.#serialize(specification),
);
}

async getById(id: string): Promise<Specification | undefined> {
Expand All @@ -53,14 +110,47 @@ export class DbSpecificationRepository implements SpecificationRepository {
return;
}

save(specification: Specification): Promise<void> {
return Promise.resolve(undefined);
async save(specification: Specification): Promise<void> {
const snapshot = specification.toSnapshot();
await this.specificationRepo.save(
this.specificationRepo.create({
id: snapshot.id,
draft: snapshot.draft,
scenarioId: snapshot.scenarioId,
specificationFeaturesConfiguration: this.specificationFeatureConfigRepo.create(
snapshot.config.map((configuration) => ({
againstFeatureId: configuration.againstFeatureId,
baseFeatureId: configuration.baseFeatureId,
operation: configuration.operation,
features: configuration.resultFeatures.map((feature) =>
this.specificationFeatureRepo.create({
calculated: feature.calculated,
featureId: feature.id,
}),
),
featuresDetermined: configuration.featuresDetermined,
specificationId: snapshot.id,
})),
),
}),
);
return;
}

transaction(
code: (repo: SpecificationRepository) => Promise<Specification[]>,
): Promise<Specification[]> {
return Promise.resolve([]);
return this.entityManager.transaction((transactionEntityManager) => {
const transactionalRepository = new DbSpecificationRepository(
transactionEntityManager.getRepository(SpecificationApiEntity),
transactionEntityManager.getRepository(
SpecificationFeatureConfigApiEntity,
),
transactionEntityManager.getRepository(SpecificationFeatureApiEntity),
transactionEntityManager,
);
return code(transactionalRepository);
});
}

#serialize = (specification: SpecificationApiEntity): Specification => {
Expand All @@ -76,7 +166,11 @@ export class DbSpecificationRepository implements SpecificationRepository {
baseFeatureId: specificationFeature.baseFeatureId,
operation: specificationFeature.operation,
featuresDetermined: specificationFeature.featuresDetermined,
resultFeatures: [],
resultFeatures:
specificationFeature.features?.map((feature) => ({
id: feature.featureId,
calculated: feature.calculated,
})) ?? [],
}),
) ?? [],
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export abstract class SpecificationRepository {
): Promise<Specification[]>;

abstract findAllRelatedToFeatureConfig(
configurations: FeatureConfigInput,
configuration: FeatureConfigInput,
): Promise<Specification[]>;

abstract transaction(
Expand Down
Loading

0 comments on commit a2578fa

Please sign in to comment.