Skip to content

Commit

Permalink
Push Neo4j specific implementation from ProductService to `ProductR…
Browse files Browse the repository at this point in the history
…epository`
  • Loading branch information
bryanjnelson committed Nov 15, 2024
1 parent ba7cf7d commit 0326f2a
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 225 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ export class FixNaNTotalVerseEquivalentsMigration extends BaseMigration {
.map('id')
.run();

const products = await this.productService.readManyUnsecured(
const products = await this.productService.readMany(
ids,
this.fakeAdminSession,
);

for (const p of products) {
const correctTotalVerseEquivalent = getTotalVerseEquivalents(
...p.scriptureReferences,
...p.scriptureReferences.value,
);

if (p.__typename === 'DirectScriptureProduct') {
Expand Down
223 changes: 190 additions & 33 deletions src/components/product/product.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ import {
relation,
} from 'cypher-query-builder';
import { DateTime } from 'luxon';
import { Except, Merge } from 'type-fest';
import { Merge } from 'type-fest';
import {
getDbClassLabels,
ID,
NotFoundException,
Range,
ServerException,
Session,
UnsecuredDto,
} from '~/common';
import { CommonRepository, DbTypeOf, OnIndex } from '~/core/database';
import { DbChanges, getChanges } from '~/core/database/changes';
import { getChanges } from '~/core/database/changes';
import {
ACTIVE,
collect,
Expand All @@ -34,14 +36,20 @@ import {
paginate,
sorting,
} from '~/core/database/query';
import { ScriptureReferenceRepository } from '../scripture';
import { ResourceResolver } from '../../core';
import { BaseNode } from '../../core/database/results';
import {
ScriptureReferenceRepository,
ScriptureReferenceService,
} from '../scripture';
import {
ScriptureRange,
ScriptureRangeInput,
UnspecifiedScripturePortion,
UnspecifiedScripturePortionInput,
} from '../scripture/dto';
import {
AnyProduct,
ApproachToMethodologies,
CreateDerivativeScriptureProduct,
CreateDirectScriptureProduct,
Expand All @@ -57,7 +65,9 @@ import {
ProductFilters,
ProductListInput,
ProgressMeasurement,
UpdateDerivativeScriptureProduct,
UpdateDirectScriptureProduct,
UpdateOtherProduct,
} from './dto';

export type HydratedProductRow = Merge<
Expand All @@ -76,10 +86,32 @@ export type HydratedProductRow = Merge<

@Injectable()
export class ProductRepository extends CommonRepository {
constructor(private readonly scriptureRefs: ScriptureReferenceRepository) {
constructor(
private readonly scriptureRefs: ScriptureReferenceRepository,
private readonly scriptureRefService: ScriptureReferenceService,
private readonly resourceResolver: ResourceResolver,
) {
super();
}

async readOne(id: ID, session: Session) {
const query = this.db
.query()
.matchNode('node', 'Product', { id })
.apply(this.hydrate(session));
const result = await query.first();
if (!result) {
throw new NotFoundException('Could not find Product');
}

return result.dto;
}

async readOneUnsecured(id: ID, session: Session) {
const result = await this.readOne(id, session);
return this.mapDbRowToDto(result);
}

async readMany(ids: readonly ID[], session: Session) {
const query = this.db
.query()
Expand All @@ -90,6 +122,14 @@ export class ProductRepository extends CommonRepository {
return await query.run();
}

async readManyUnsecured(
ids: readonly ID[],
session: Session,
): Promise<ReadonlyArray<UnsecuredDto<AnyProduct>>> {
const rows = await this.readMany(ids, session);
return rows.map((row) => this.mapDbRowToDto(row));
}

async listIdsAndScriptureRefs(engagementId: ID) {
return await this.db
.query()
Expand Down Expand Up @@ -232,8 +272,8 @@ export class ProductRepository extends CommonRepository {
)
.return<{ dto: HydratedProductRow }>(
merge('props', {
engagement: 'engagement.id',
project: 'project.id',
engagement: 'engagement { .id }',
project: 'project { .id }',
produces: 'produces',
unspecifiedScripture:
'unspecifiedScripture { .book, .totalVerses }',
Expand All @@ -244,37 +284,58 @@ export class ProductRepository extends CommonRepository {

getActualDirectChanges = getChanges(DirectScriptureProduct);

async updateProperties(
object: DirectScriptureProduct,
changes: DbChanges<DirectScriptureProduct>,
async updateDirectProperties(
changes: UpdateDirectScriptureProduct,
session: Session,
) {
return await this.db.updateProperties({
const { id, scriptureReferences, unspecifiedScripture, ...simpleChanges } =
changes;

await this.scriptureRefService.update(id, scriptureReferences);

if (unspecifiedScripture !== undefined) {
await this.updateUnspecifiedScripture(id, unspecifiedScripture);
}

await this.db.updateProperties({
type: DirectScriptureProduct,
object,
changes,
object: { id },
changes: simpleChanges,
});

return this.mapDbRowToDto(await this.readOne(id, session));
}

getActualDerivativeChanges = getChanges(DerivativeScriptureProduct);
getActualOtherChanges = getChanges(OtherProduct);

async findProducible(produces: ID | undefined) {
return await this.db
const result = await this.db
.query()
.match([
node('producible', 'Producible', {
id: produces,
}),
])
.return('producible')
.return<{ producible: BaseNode }>('node')
.first();

if (!result) {
throw new NotFoundException(
'Could not find producible node',
'product.produces',
);
}

return result;
}

async create(
input: (CreateDerivativeScriptureProduct | CreateDirectScriptureProduct) & {
totalVerses: number;
totalVerseEquivalents: number;
},
session: Session,
) {
const isDerivative = 'produces' in input;
const Product = isDerivative
Expand Down Expand Up @@ -361,10 +422,11 @@ export class ProductRepository extends CommonRepository {
if (!result) {
throw new ServerException('Failed to create product');
}
return result.id;

return this.mapDbRowToDto(await this.readOne(result.id, session));
}

async createOther(input: CreateOtherProduct) {
async createOther(input: CreateOtherProduct, session: Session) {
const initialProps = {
mediums: input.mediums ?? [],
purposes: input.purposes ?? [],
Expand Down Expand Up @@ -397,17 +459,15 @@ export class ProductRepository extends CommonRepository {
if (!result) {
throw new ServerException('Failed to create product');
}
return result.id;

return this.mapDbRowToDto(await this.readOne(result.id, session));
}

async updateProducible(
input: Except<UpdateDirectScriptureProduct, 'scriptureReferences'>,
produces: ID,
) {
async updateProducible(id: ID, produces: ID) {
await this.db
.query()
.match([
node('product', 'Product', { id: input.id }),
node('product', 'Product', id),
relation('out', 'rel', 'produces', ACTIVE),
node('', 'Producible'),
])
Expand All @@ -419,7 +479,7 @@ export class ProductRepository extends CommonRepository {

await this.db
.query()
.match([node('product', 'Product', { id: input.id })])
.match([node('product', 'Product', id)])
.match([
node('producible', 'Producible', {
id: produces,
Expand Down Expand Up @@ -467,22 +527,40 @@ export class ProductRepository extends CommonRepository {
}

async updateDerivativeProperties(
object: DerivativeScriptureProduct,
changes: DbChanges<DerivativeScriptureProduct>,
changes: UpdateDerivativeScriptureProduct,
session: Session,
) {
return await this.db.updateProperties({
const { id, produces, scriptureReferencesOverride, ...simpleChanges } =
changes;

if (produces) {
await this.findProducible(produces);
await this.updateProducible(id, produces);
}

await this.scriptureRefService.update(id, scriptureReferencesOverride, {
isOverriding: true,
});

await this.db.updateProperties({
type: DerivativeScriptureProduct,
object,
changes,
object: { id },
changes: simpleChanges,
});

return this.mapDbRowToDto(await this.readOne(id, session));
}

async updateOther(object: OtherProduct, changes: DbChanges<OtherProduct>) {
return await this.db.updateProperties({
async updateOther(changes: UpdateOtherProduct, session: Session) {
const { id, ...simpleChanges } = changes;

await this.db.updateProperties({
type: OtherProduct,
object,
changes,
object: { id },
changes: simpleChanges,
});

return this.mapDbRowToDto(await this.readOne(id, session));
}

async list(input: ProductListInput, session: Session) {
Expand Down Expand Up @@ -514,7 +592,12 @@ export class ProductRepository extends CommonRepository {
.apply(sorting(Product, input))
.apply(paginate(input, this.hydrate(session)))
.first();
return result!; // result from paginate() will always have 1 row.

return {
// result from paginate() will always have 1 row
...result!,
items: result!.items.map((row) => this.mapDbRowToDto(row)),
};
}

async mergeCompletionDescription(
Expand All @@ -539,6 +622,17 @@ export class ProductRepository extends CommonRepository {
.run();
}

async delete(object: AnyProduct) {
try {
await this.deleteNode(object);
} catch (exception) {
throw new ServerException(
`Failed to delete product ${object.id}`,
exception,
);
}
}

async suggestCompletionDescriptions({
query: queryInput,
methodology,
Expand Down Expand Up @@ -566,6 +660,69 @@ export class ProductRepository extends CommonRepository {
return result!;
}

mapDbRowToDto(row: HydratedProductRow): UnsecuredDto<AnyProduct> {
const {
isOverriding,
produces: rawProducible,
title,
description,
...rawProps
} = row;
const props = {
...rawProps,
mediums: rawProps.mediums ?? [],
purposes: rawProps.purposes ?? [],
steps: rawProps.steps ?? [],
scriptureReferences: this.scriptureRefService.parseList(
rawProps.scriptureReferences,
),
};

if (title) {
const dto: UnsecuredDto<OtherProduct> = {
...props,
title,
description,
__typename: 'OtherProduct',
};
return dto;
}

if (!rawProducible) {
const dto: UnsecuredDto<DirectScriptureProduct> = {
...props,
totalVerses: props.totalVerses ?? 0,
totalVerseEquivalents: props.totalVerseEquivalents ?? 0,
__typename: 'DirectScriptureProduct',
};
return dto;
}

const producible = {
...rawProducible,
scriptureReferences: this.scriptureRefService.parseList(
rawProducible.scriptureReferences,
),
};

const producibleType = this.resourceResolver.resolveType(
producible.__typename,
) as ProducibleType;

const dto: UnsecuredDto<DerivativeScriptureProduct> = {
...props,
produces: { ...producible, __typename: producibleType },
scriptureReferences: !isOverriding
? producible.scriptureReferences
: props.scriptureReferences,
scriptureReferencesOverride: !isOverriding
? null
: props.scriptureReferences,
__typename: 'DerivativeScriptureProduct',
};
return dto;
}

@OnIndex('schema')
private async createCompletionDescriptionIndex() {
await this.db
Expand Down
Loading

0 comments on commit 0326f2a

Please sign in to comment.