-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(core): Resolve all LocaleString fields in GraphQL API
Relates to #763
- Loading branch information
1 parent
179679f
commit 3ddadc0
Showing
12 changed files
with
182 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
packages/core/src/api/resolvers/entity/country-entity.resolver.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Parent, ResolveField, Resolver } from '@nestjs/graphql'; | ||
|
||
import { Country } from '../../../entity/country/country.entity'; | ||
import { LocaleStringHydrator } from '../../../service/helpers/locale-string-hydrator/locale-string-hydrator'; | ||
import { RequestContext } from '../../common/request-context'; | ||
import { Ctx } from '../../decorators/request-context.decorator'; | ||
|
||
@Resolver('Country') | ||
export class CountryEntityResolver { | ||
constructor(private localeStringHydrator: LocaleStringHydrator) {} | ||
|
||
@ResolveField() | ||
name(@Ctx() ctx: RequestContext, @Parent() country: Country): Promise<string> { | ||
return this.localeStringHydrator.hydrateLocaleStringField(ctx, country, 'name'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
packages/core/src/service/helpers/locale-string-hydrator/locale-string-hydrator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
|
||
import { RequestContext } from '../../../api/common/request-context'; | ||
import { RequestContextCacheService } from '../../../cache/request-context-cache.service'; | ||
import { Translatable, TranslatableKeys, Translated } from '../../../common/types/locale-types'; | ||
import { VendureEntity } from '../../../entity/base/base.entity'; | ||
import { ProductVariant } from '../../../entity/product-variant/product-variant.entity'; | ||
import { TransactionalConnection } from '../../transaction/transactional-connection'; | ||
import { translateDeep } from '../utils/translate-entity'; | ||
|
||
/** | ||
* This helper class is to be used in GraphQL entity resolvers, to resolve fields which depend on being | ||
* translated (i.e. the corresponding entity field is of type `LocaleString`). | ||
*/ | ||
@Injectable() | ||
export class LocaleStringHydrator { | ||
constructor( | ||
private connection: TransactionalConnection, | ||
private requestCache: RequestContextCacheService, | ||
) {} | ||
|
||
async hydrateLocaleStringField<T extends VendureEntity & Translatable>( | ||
ctx: RequestContext, | ||
entity: T, | ||
fieldName: TranslatableKeys<T>, | ||
): Promise<string> { | ||
if (entity[fieldName]) { | ||
// Already hydrated, so return the value | ||
return entity[fieldName] as any; | ||
} | ||
await this.hydrateLocaleStrings(ctx, entity); | ||
return entity[fieldName] as any; | ||
} | ||
|
||
/** | ||
* Takes a translatable entity and populates all the LocaleString fields | ||
* by fetching the translations from the database (they will be eagerly loaded). | ||
* | ||
* This method includes a caching optimization to prevent multiple DB calls when many | ||
* translatable fields are needed on the same entity in a resolver. | ||
*/ | ||
private async hydrateLocaleStrings<T extends VendureEntity & Translatable>( | ||
ctx: RequestContext, | ||
entity: T, | ||
): Promise<Translated<T>> { | ||
const entityType = entity.constructor.name; | ||
if (!entity.translations?.length) { | ||
const cacheKey = `hydrate-${entityType}-${entity.id}`; | ||
let dbCallPromise = this.requestCache.get<Promise<T | undefined>>(ctx, cacheKey); | ||
|
||
if (!dbCallPromise) { | ||
dbCallPromise = this.connection.getRepository<T>(ctx, entityType).findOne(entity.id); | ||
this.requestCache.set(ctx, cacheKey, dbCallPromise); | ||
} | ||
|
||
await dbCallPromise.then(withTranslations => { | ||
// tslint:disable-next-line:no-non-null-assertion | ||
entity.translations = withTranslations!.translations; | ||
}); | ||
} | ||
if (entity.translations.length) { | ||
const translated = translateDeep(entity, ctx.languageCode); | ||
for (const localeStringProp of Object.keys(entity.translations[0])) { | ||
if (localeStringProp === 'base' || localeStringProp === 'languageCode') { | ||
continue; | ||
} | ||
if (localeStringProp === 'customFields') { | ||
(entity as any)[localeStringProp] = Object.assign( | ||
(entity as any)[localeStringProp], | ||
(translated as any)[localeStringProp], | ||
); | ||
} else { | ||
(entity as any)[localeStringProp] = (translated as any)[localeStringProp]; | ||
} | ||
} | ||
} | ||
return entity as Translated<T>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters