Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/components/product/dto/product.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
import { type SetDbType } from '~/core/database';
import { type SetChangeType } from '~/core/database/changes';
import { e } from '~/core/gel';
import { RegisterResource } from '~/core/resources';
import { type LinkTo, RegisterResource } from '~/core/resources';
import { type DbScriptureReferences } from '../../scripture';
import {
type ScriptureRangeInput,
Expand Down Expand Up @@ -55,8 +55,8 @@ export class Product extends Producible {
static readonly Parent = () =>
import('../../engagement/dto').then((m) => m.LanguageEngagement);

readonly engagement: ID;
readonly project: ID;
readonly engagement: LinkTo<'LanguageEngagement'>;
readonly project: LinkTo<'Project'>;

@Field()
@DbLabel('ProductMedium')
Expand Down Expand Up @@ -266,6 +266,12 @@ declare module '../dto/producible.dto' {
}
}

export const ProductConcretes = {
DirectScriptureProduct,
DerivativeScriptureProduct,
OtherProduct,
};

declare module '~/core/resources/map' {
interface ResourceMap {
Product: typeof Product;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ export class FixNaNTotalVerseEquivalentsMigration extends BaseMigration {
.map('id')
.run();

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

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

if (p.__typename === 'DirectScriptureProduct') {
Expand Down
228 changes: 228 additions & 0 deletions src/components/product/product.gel.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { Injectable, type Type } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { LazyGetter } from 'lazy-get-decorator';
import { type ID, type PublicOf } from '../../common';
import { grabInstances } from '../../common/instance-maps';
import { e, RepoFor } from '../../core/gel';
import {
ProductConcretes as ConcreteTypes,
type CreateDerivativeScriptureProduct,
type CreateDirectScriptureProduct,
type CreateOtherProduct,
Product,
} from './dto';
import { type ProductRepository } from './product.repository';

// scriptureReferencesOverride, scriptureReferences

const baseHydrate = e.shape(e.Product, (product) => ({
...product['*'],
__typename: product.__type__.name,
project: {
id: true,
status: true,
type: true,
},
engagement: {
id: true,
status: true,
},
parent: e.select({
identity: product.engagement.id,
labels: e.array_agg(e.set(product.engagement.__type__.name.slice(9, null))),
properties: e.select({
id: product.engagement.id,
createdAt: product.engagement.createdAt,
}),
}),
pnpIndex: true,
scriptureReferences: product.scripture,
}));

const directScriptureExtraHydrate = {
totalVerses: true,
totalVerseEquivalents: true,
} as const;

const derivativeScriptureExtraHydrate = {
scripture: true,
composite: true,
totalVerses: true,
totalVerseEquivalents: true,
} as const;

const otherExtraHydrate = {
title: true,
description: true,
} as const;

const directScriptureProductHydrate = e.shape(
e.DirectScriptureProduct,
(dsp) => ({
...baseHydrate(dsp),
__typename: dsp.__type__.name,
unspecifiedScripture: {
book: true,
totalVerses: true,
},
//TODO - remove after migration
unspecifiedScripturePortion: {
book: true,
totalVerses: true,
},
...directScriptureExtraHydrate,
}),
);

const derivativeScriptureProductHydrate = e.shape(
e.DerivativeScriptureProduct,
(dsp) => ({
...baseHydrate(dsp),
__typename: dsp.__type__.name,
scriptureReferencesOverride: dsp.scriptureOverride,
produces: {
scriptureReferences: e.select([dsp.produces.scripture]),
createdAt: dsp.produces.createdAt,
id: dsp.produces.id,
},
...derivativeScriptureExtraHydrate,
}),
);

const otherProductHydrate = e.shape(e.OtherProduct, (op) => ({
...baseHydrate(op),
__typename: op.__type__.name,
scriptureReferencesOverride: false, //TODO - remove after migration
...otherExtraHydrate,
}));

const hydrate = e.shape(e.Product, (product) => ({
...baseHydrate(product),
...e.is(e.DirectScriptureProduct, directScriptureExtraHydrate),
...e.is(e.DerivativeScriptureProduct, derivativeScriptureExtraHydrate),
...e.is(e.OtherProduct, otherExtraHydrate),
}));

export const ConcreteRepos = {
DirectScriptureProduct: class DirectScriptureProductRepository extends RepoFor(
ConcreteTypes.DirectScriptureProduct,
{
hydrate: directScriptureProductHydrate,
omit: ['create'],
},
) {
async create(input: CreateDirectScriptureProduct) {
const engagement = e.cast(
e.LanguageEngagement,
e.uuid(input.engagementId),
);
return await this.defaults.create({
...input,
projectContext: engagement.projectContext,
});
}
},

DerivativeScriptureProduct: class DerivativeScriptureProductRepository extends RepoFor(
ConcreteTypes.DerivativeScriptureProduct,
{
hydrate: derivativeScriptureProductHydrate,
omit: ['create'],
},
) {
async create(input: CreateDerivativeScriptureProduct) {
const engagement = e.cast(
e.LanguageEngagement,
e.uuid(input.engagementId),
);
return await this.defaults.create({
...input,
projectContext: engagement.projectContext,
});
}
},

OtherProduct: class OtherProductRepository extends RepoFor(
ConcreteTypes.OtherProduct,
{
hydrate: otherProductHydrate,
omit: ['create'],
},
) {
async create(input: CreateOtherProduct) {
const engagement = e.cast(
e.LanguageEngagement,
e.uuid(input.engagementId),
);
return await this.defaults.create({
...input,
projectContext: engagement.projectContext,
});
}
},
} satisfies Record<keyof typeof ConcreteTypes, Type>;

@Injectable()
export class ProductGelRepository
extends RepoFor(Product, {
hydrate,
omit: ['create'],
})
implements PublicOf<ProductRepository>
{
constructor(private readonly moduleRef: ModuleRef) {
super();
}

@LazyGetter() protected get concretes() {
return grabInstances(this.moduleRef, ConcreteRepos);
}

async createDerivative(
input: CreateDerivativeScriptureProduct & {
totalVerses: number;
totalVerseEquivalents: number;
},
) {
return await this.concretes.DerivativeScriptureProduct.create(input);
}

async createDirect(
input: CreateDirectScriptureProduct & {
totalVerses: number;
totalVerseEquivalents: number;
},
) {
return await this.concretes.DirectScriptureProduct.create(input);
}

async createOther(input: CreateOtherProduct) {
return await this.concretes.OtherProduct.create(input);
}

async listIdsAndScriptureRefs(engagementId: ID) {
const engagement = e.cast(e.LanguageEngagement, e.uuid(engagementId));
const query = e.select(e.DirectScriptureProduct, (dsp) => ({
id: true,
pnpIndex: true,
scriptureRanges: dsp.scripture,
unspecifiedScripture: dsp.unspecifiedScripture,
filter: e.op(dsp.engagement, '=', engagement),
}));

return await this.db.run(query);
}

async listIdsWithPnpIndexes(engagementId: ID, _type?: string) {
const engagement = e.cast(e.LanguageEngagement, e.uuid(engagementId));

const query = e.select(e.Product, (p) => ({
id: true,
pnpIndex: p.pnpIndex,
...e.is(e.DirectScriptureProduct, {}),
filter: e.op(p.engagement, '=', engagement),
}));

return await this.db.run(query);
}
}
Loading
Loading