diff --git a/libs/cms/src/lib/cms.contentful.service.ts b/libs/cms/src/lib/cms.contentful.service.ts index 55f256689fce..71bcfabb44cc 100644 --- a/libs/cms/src/lib/cms.contentful.service.ts +++ b/libs/cms/src/lib/cms.contentful.service.ts @@ -35,7 +35,6 @@ import { ContentfulRepository, localeMap } from './contentful.repository' import { GetAlertBannerInput } from './dto/getAlertBanner.input' import { AlertBanner, mapAlertBanner } from './models/alertBanner.model' import { mapUrl, Url } from './models/url.model' -import { mapTellUsAStory, TellUsAStory } from './models/tellUsAStory.model' import { GetSubpageHeaderInput } from './dto/getSubpageHeader.input' import { mapSubpageHeader, SubpageHeader } from './models/subpageHeader.model' import { @@ -82,6 +81,9 @@ import { GetGenericTagBySlugInput } from './dto/getGenericTagBySlug.input' import { GenericTag, mapGenericTag } from './models/genericTag.model' import { GetEmailSignupInput } from './dto/getEmailSignup.input' import { LifeEventPage, mapLifeEventPage } from './models/lifeEventPage.model' +import { mapManual } from './models/manual.model' +import { mapServiceWebPage } from './models/serviceWebPage.model' +import { mapEvent } from './models/event.model' const errorHandler = (name: string) => { return (error: Error) => { @@ -90,20 +92,42 @@ const errorHandler = (name: string) => { } } -const ArticleFields = [ - // we want to exclude relatedArticles because it's a self-referencing - // relation and selecting related articles to a depth of 10 would make the - // response huge - 'sys', - 'fields.slug', - 'fields.title', - 'fields.shortTitle', - 'fields.content', - 'fields.subgroup', - 'fields.group', - 'fields.category', - 'fields.subArticles', -].join(',') +const ArticleFields = ( + [ + // we want to exclude relatedArticles because it's a self-referencing + // relation and selecting related articles to a depth of 10 would make the + // response huge + 'sys', + 'fields.activeTranslations', + 'fields.alertBanner', + 'fields.category', + 'fields.content', + 'fields.contentStatus', + 'fields.featuredImage', + 'fields.group', + 'fields.importance', + 'fields.intro', + 'fields.organization', + 'fields.otherCategories', + 'fields.otherGroups', + 'fields.otherSubgroups', + 'fields.processEntry', + 'fields.processEntryButtonText', + 'fields.relatedContent', + 'fields.relatedOrganization', + 'fields.responsibleParty', + 'fields.shortTitle', + 'fields.showTableOfContents', + 'fields.signLanguageVideo', + 'fields.slug', + 'fields.stepper', + 'fields.subArticles', + 'fields.subgroup', + 'fields.title', + 'fields.userStories', + 'fields.keywords', + ] as ('sys' | `fields.${keyof types.IArticle['fields']}`)[] +).join(',') @Injectable() export class CmsContentfulService { @@ -378,8 +402,9 @@ export class CmsContentfulService { ): Promise { const params = { ['content_type']: 'organizationPage', - include: 10, + include: 5, 'fields.slug': slug, + limit: 1, } const result = await this.contentfulRepository @@ -399,10 +424,11 @@ export class CmsContentfulService { ): Promise { const params = { ['content_type']: 'organizationSubpage', - include: 10, + include: 5, 'fields.slug': slug, 'fields.organizationPage.sys.contentType.sys.id': 'organizationPage', 'fields.organizationPage.fields.slug': organizationSlug, + limit: 1, } const result = await this.contentfulRepository .getLocalizedEntries(lang, params) @@ -415,6 +441,23 @@ export class CmsContentfulService { ) } + async getServiceWebPage(slug: string, lang: string) { + const params = { + ['content_type']: 'serviceWebPage', + include: 5, + 'fields.slug': slug, + limit: 1, + } + const result = await this.contentfulRepository + .getLocalizedEntries(lang, params) + .catch(errorHandler('getServiceWebPage')) + + return ( + (result.items as types.IServiceWebPage[]).map(mapServiceWebPage)[0] ?? + null + ) + } + async getAuctions( lang: string, organization?: string, @@ -467,6 +510,7 @@ export class CmsContentfulService { const params = { ['content_type']: 'projectPage', 'fields.slug': slug, + limit: 1, } const result = await this.contentfulRepository @@ -483,7 +527,8 @@ export class CmsContentfulService { ['content_type']: 'article', 'fields.slug': slug, select: ArticleFields, - include: 10, + include: 5, + limit: 1, } const result = await this.contentfulRepository @@ -542,8 +587,9 @@ export class CmsContentfulService { async getNews(lang: string, slug: string): Promise { const params = { ['content_type']: 'news', - include: 10, + include: 5, 'fields.slug': slug, + limit: 1, } const result = await this.contentfulRepository @@ -553,6 +599,36 @@ export class CmsContentfulService { return (result.items as types.INews[]).map(mapNews)[0] ?? null } + async getSingleEvent(lang: string, slug: string) { + const params = { + ['content_type']: 'event', + include: 5, + 'fields.slug': slug, + limit: 1, + } + + const result = await this.contentfulRepository + .getLocalizedEntries(lang, params) + .catch(errorHandler('getSingleEvent')) + + return (result.items as types.IEvent[]).map(mapEvent)[0] ?? null + } + + async getSingleManual(lang: string, slug: string) { + const params = { + ['content_type']: 'manual', + include: 5, + 'fields.slug': slug, + limit: 1, + } + + const result = await this.contentfulRepository + .getLocalizedEntries(lang, params) + .catch(errorHandler('getSingleManual')) + + return (result.items as types.IManual[]).map(mapManual)[0] ?? null + } + async getContentSlug({ id, }: GetContentSlugInput): Promise { @@ -828,8 +904,9 @@ export class CmsContentfulService { const params = { ['content_type']: 'frontpage', 'fields.pageIdentifier': pageIdentifier, - include: 10, + include: 5, order: '-sys.createdAt', + limit: 1, } const result = await this.contentfulRepository diff --git a/libs/cms/src/lib/cms.resolver.ts b/libs/cms/src/lib/cms.resolver.ts index d6d2d2a821c0..52d9a8f7fd5d 100644 --- a/libs/cms/src/lib/cms.resolver.ts +++ b/libs/cms/src/lib/cms.resolver.ts @@ -246,10 +246,7 @@ export class CmsResolver { async getOrganizationPage( @Args('input') input: GetOrganizationPageInput, ): Promise { - return this.cmsElasticsearchService.getSingleDocumentTypeBySlug( - getElasticsearchIndex(input.lang), - { type: 'webOrganizationPage', slug: input.slug }, - ) + return this.cmsContentfulService.getOrganizationPage(input.slug, input.lang) } @CacheControl(defaultCache) @@ -257,9 +254,10 @@ export class CmsResolver { async getOrganizationSubpage( @Args('input') input: GetOrganizationSubpageInput, ): Promise { - return this.cmsElasticsearchService.getSingleOrganizationSubpage( - getElasticsearchIndex(input.lang), - { ...input }, + return this.cmsContentfulService.getOrganizationSubpage( + input.organizationSlug, + input.slug, + input.lang, ) } @@ -268,10 +266,7 @@ export class CmsResolver { async getServiceWebPage( @Args('input') input: GetServiceWebPageInput, ): Promise { - return this.cmsElasticsearchService.getSingleDocumentTypeBySlug( - getElasticsearchIndex(input.lang), - { type: 'webServiceWebPage', slug: input.slug }, - ) + return this.cmsContentfulService.getServiceWebPage(input.slug, input.lang) } @CacheControl(defaultCache) @@ -422,11 +417,10 @@ export class CmsResolver { async getSingleArticle( @Args('input') { lang, slug }: GetSingleArticleInput, ): Promise<(Partial
& { lang: Locale }) | null> { - const article: Article | null = - await this.cmsElasticsearchService.getSingleDocumentTypeBySlug
( - getElasticsearchIndex(lang), - { type: 'webArticle', slug }, - ) + const article: Article | null = await this.cmsContentfulService.getArticle( + slug, + lang, + ) if (!article) return null @@ -452,10 +446,7 @@ export class CmsResolver { getSingleNews( @Args('input') { lang, slug }: GetSingleNewsInput, ): Promise { - return this.cmsElasticsearchService.getSingleDocumentTypeBySlug( - getElasticsearchIndex(lang), - { type: 'webNews', slug }, - ) + return this.cmsContentfulService.getNews(lang, slug) } @CacheControl(defaultCache) @@ -463,10 +454,7 @@ export class CmsResolver { getSingleEvent( @Args('input') { lang, slug }: GetSingleEventInput, ): Promise { - return this.cmsElasticsearchService.getSingleDocumentTypeBySlug( - getElasticsearchIndex(lang), - { type: 'webEvent', slug }, - ) + return this.cmsContentfulService.getSingleEvent(lang, slug) } @CacheControl(defaultCache) @@ -620,10 +608,7 @@ export class CmsResolver { getSingleManual( @Args('input') input: GetSingleManualInput, ): Promise { - return this.cmsElasticsearchService.getSingleDocumentTypeBySlug( - getElasticsearchIndex(input.lang), - { type: 'webManual', slug: input.slug }, - ) + return this.cmsContentfulService.getSingleManual(input.lang, input.slug) } @CacheControl(defaultCache) diff --git a/libs/content-search-indexer/src/lib/cache-invalidation.service.ts b/libs/content-search-indexer/src/lib/cache-invalidation.service.ts deleted file mode 100644 index c71800383bcc..000000000000 --- a/libs/content-search-indexer/src/lib/cache-invalidation.service.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { Injectable } from '@nestjs/common' -import { logger } from '@island.is/logging' -import { - SubArticle, - ProjectPage, - OrganizationPage, - OrganizationSubpage, - News, - Manual, - ManualChapterItem, -} from '@island.is/cms' -import { ElasticsearchIndexLocale } from '@island.is/content-search-index-manager' -import { SearchableContentTypes } from '@island.is/shared/types' -import { MappedData } from '@island.is/content-search-indexer/types' -import { environment } from '../environments/environment' - -type GenerateInvalidationUrlsFunctionType = ( - baseUrl: string, - document: unknown, -) => string[] - -const PATHS: { - [key in SearchableContentTypes]?: Record< - ElasticsearchIndexLocale, - GenerateInvalidationUrlsFunctionType - > -} = { - webArticle: { - is: (baseUrl: string, article: { slug: string }) => [ - `${baseUrl}/${article.slug}`, - ], - en: (baseUrl: string, article: { slug: string }) => [ - `${baseUrl}/en/${article.slug}`, - ], - }, - webSubArticle: { - is: (baseUrl: string, subArticle: SubArticle) => [ - `${baseUrl}/${subArticle.slug}`, - ], - en: (baseUrl: string, subArticle: SubArticle) => [ - `${baseUrl}/en/${subArticle.slug}`, - ], - }, - webProjectPage: { - is: (baseUrl: string, projectPage: ProjectPage) => [ - `${baseUrl}/v/${projectPage.slug}`, - ], - en: (baseUrl: string, projectPage: ProjectPage) => [ - `${baseUrl}/en/p/${projectPage.slug}`, - ], - }, - webOrganizationPage: { - is: (baseUrl: string, organizationPage: OrganizationPage) => [ - `${baseUrl}/s/${organizationPage.slug}`, - ], - en: (baseUrl: string, organizationPage: OrganizationPage) => [ - `${baseUrl}/en/o/${organizationPage.slug}`, - ], - }, - webOrganizationSubpage: { - is: (baseUrl: string, subpage: OrganizationSubpage) => [ - `${baseUrl}/s/${subpage.organizationPage.slug}/${subpage.slug}`, - ], - en: (baseUrl: string, subpage: OrganizationSubpage) => [ - `${baseUrl}/en/o/${subpage.organizationPage.slug}/${subpage.slug}`, - ], - }, - webNews: { - is: (baseUrl: string, newsItem: News) => { - const urls = [`${baseUrl}/frett/${newsItem.slug}`] - if (newsItem.organization?.slug) { - urls.push(`${baseUrl}/s/${newsItem.organization.slug}/${newsItem.slug}`) - } - return urls - }, - en: (baseUrl: string, newsItem: News) => { - const urls = [`${baseUrl}/en/news/${newsItem.slug}`] - if (newsItem.organization?.slug) { - urls.push( - `${baseUrl}/en/o/news/${newsItem.organization.slug}/${newsItem.slug}`, - ) - } - return urls - }, - }, - webManual: { - is: (baseUrl: string, manual: Manual) => [ - `${baseUrl}/handbaekur/${manual.slug}`, - ], - en: (baseUrl: string, manual: Manual) => [ - `${baseUrl}/en/manuals/${manual.slug}`, - ], - }, - webManualChapterItem: { - is: (baseUrl: string, chapterItem: ManualChapterItem) => [ - `${baseUrl}/handbaekur/${chapterItem.manual.slug}/${chapterItem.manualChapter.slug}`, - ], - en: (baseUrl: string, chapterItem: ManualChapterItem) => [ - `${baseUrl}/en/manuals/${chapterItem.manual.slug}/${chapterItem.manualChapter.slug}`, - ], - }, -} - -const MAX_REQUEST_COUNT = 10 - -@Injectable() -export class CacheInvalidationService { - private getBaseUrl() { - let baseUrl = 'http://web.islandis.svc.cluster.local' - if (environment.runtimeEnvironment === 'local') { - baseUrl = 'http://localhost:4200' - } - return baseUrl - } - - async invalidateCache(items: MappedData[], locale: ElasticsearchIndexLocale) { - if ( - environment.runtimeEnvironment === 'local' && - !environment.forceCacheInvalidationAfterIndexing - ) { - return - } - - const baseUrl = this.getBaseUrl() - - const bypassSecret = environment.bypassCacheSecret - - const promises: { url: string; promise: Promise }[] = [] - let successfulCacheInvalidationCount = 0 - const failedCacheInvalidationReasons: { url: string; reason: unknown }[] = - [] - - const handleRequests = async () => { - const responses = await Promise.allSettled( - promises.map(({ promise }) => promise), - ) - for (let i = 0; i < responses.length; i += 1) { - const response = responses[i] - const url = promises[i].url - if (response.status === 'fulfilled') { - successfulCacheInvalidationCount += 1 - } else { - failedCacheInvalidationReasons.push({ - url, - reason: response.reason, - }) - } - } - promises.length = 0 - } - - const itemsThatCanBeCacheInvalidated = items.filter( - (item) => item.type in PATHS, - ) - - if (itemsThatCanBeCacheInvalidated.length > 0) { - logger.info( - `Invalidating cache for ${itemsThatCanBeCacheInvalidated.length} ${ - itemsThatCanBeCacheInvalidated.length === 1 ? 'document' : 'documents' - }...`, - ) - } - - for (const item of itemsThatCanBeCacheInvalidated) { - const generateInvalidationUrls = PATHS[item.type][ - locale - ] as GenerateInvalidationUrlsFunctionType - - let urls: string[] = [] - - try { - urls = generateInvalidationUrls(baseUrl, JSON.parse(item.response)) - } catch { - logger.warn( - `Generating invalidation url failed for document with id: ${item._id}`, - ) - continue - } - - for (const urlWithoutPostfix of urls) { - const url = `${urlWithoutPostfix}?bypass-cache=${bypassSecret}` - promises.push({ - url: urlWithoutPostfix, - promise: fetch(url, { - headers: { - 'Cache-Control': 'no-cache', - }, - }), - }) - if (promises.length > MAX_REQUEST_COUNT) { - await handleRequests() - } - } - } - - if (promises.length > 0) { - await handleRequests() - } - - if (successfulCacheInvalidationCount > 0) { - logger.info( - `Successfully invalidated cache for ${successfulCacheInvalidationCount} urls`, - ) - } - if (failedCacheInvalidationReasons.length > 0) { - logger.warn( - `Could not invalidate cache for ${failedCacheInvalidationReasons.length} urls`, - failedCacheInvalidationReasons, - ) - } - } -} diff --git a/libs/content-search-indexer/src/lib/indexing.module.ts b/libs/content-search-indexer/src/lib/indexing.module.ts index 7b3393c6d644..77b1934080c7 100644 --- a/libs/content-search-indexer/src/lib/indexing.module.ts +++ b/libs/content-search-indexer/src/lib/indexing.module.ts @@ -5,12 +5,11 @@ import { CmsSyncModule } from '@island.is/cms' import { IndexingController } from './indexing.controller' import { IndexingService } from './indexing.service' -import { CacheInvalidationService } from './cache-invalidation.service' @Module({ // add the importer nestjs module to the imports array to have access to it's service in the indexing service imports: [CmsSyncModule], controllers: [IndexingController], - providers: [CacheInvalidationService, IndexingService, ElasticService], + providers: [IndexingService, ElasticService], }) export class IndexingModule {} diff --git a/libs/content-search-indexer/src/lib/indexing.service.ts b/libs/content-search-indexer/src/lib/indexing.service.ts index 697b36537891..77d4aaecd2c4 100644 --- a/libs/content-search-indexer/src/lib/indexing.service.ts +++ b/libs/content-search-indexer/src/lib/indexing.service.ts @@ -14,7 +14,6 @@ import { getElasticsearchIndex, } from '@island.is/content-search-index-manager' import { environment } from '../environments/environment' -import { CacheInvalidationService } from './cache-invalidation.service' type SyncStatus = { running?: boolean @@ -28,7 +27,6 @@ export class IndexingService { constructor( private readonly elasticService: ElasticService, private readonly cmsSyncService: CmsSyncService, - private readonly cacheInvalidationService: CacheInvalidationService, ) { // add importer service to this array to make it import this.importers = [this.cmsSyncService] @@ -91,14 +89,6 @@ export class IndexingService { isIncrementalUpdate, ) - // Invalidate cached pages in the background if we are performing an incremental update - if (isIncrementalUpdate) { - this.cacheInvalidationService.invalidateCache( - elasticData.add, - options.locale, - ) - } - nextPageToken = importerResponseNextPageToken postSyncOptions = importerResponsePostSyncOptions } @@ -131,14 +121,6 @@ export class IndexingService { isIncrementalUpdate, ) - // Invalidate cached pages in the background if we are performing an incremental update - if (isIncrementalUpdate) { - this.cacheInvalidationService.invalidateCache( - elasticData.add, - options.locale, - ) - } - nextPageToken = importerResponseNextPageToken postSyncOptions = importerResponsePostSyncOptions }