UNPKG

@paperbits/common

Version:
381 lines (293 loc) 14.9 kB
import * as Utils from "../utils"; import * as Objects from "../objects"; import * as Constants from "../constants"; import { PageContract, PageMetadata, PageLocalizedContract, IPageService } from "."; import { IObjectStorage, Operator, Query, Page } from "../persistence"; import { IBlockService } from "../blocks"; import { Contract } from "../contract"; import { ILocaleService } from "../localization"; import { Logger } from "../logging"; const documentsPath = "files"; const templateBlockKey = "blocks/new-page-template"; export class PageService implements IPageService { protected pagesPath: string = "pages"; constructor( private readonly objectStorage: IObjectStorage, private readonly blockService: IBlockService, private readonly localeService: ILocaleService, private readonly logger: Logger ) { } /** * Copies limited number of metadata properties. */ private copyMetadata(sourceMetadata: PageMetadata, targetMetadata: PageMetadata): PageMetadata { if (!sourceMetadata) { throw new Error(`Parameter "sourceMetadata" not specified.`); } if (!targetMetadata) { throw new Error(`Parameter "targetMetadata" not specified.`); } targetMetadata.title = sourceMetadata.title; targetMetadata.description = sourceMetadata.description; targetMetadata.keywords = sourceMetadata.keywords; targetMetadata.permalink = sourceMetadata.permalink; targetMetadata.jsonLd = sourceMetadata.jsonLd; targetMetadata.socialShareData = sourceMetadata.socialShareData; return targetMetadata; } private syncLocalePermalinks(defaultLocaleCode: string, page: PageLocalizedContract): void { const defaultLocale = page.locales[defaultLocaleCode]; if (!defaultLocale) { throw new Error(`Page contract doesn't contain the default locale.`); } Object.keys(page.locales).forEach(locale => { if (locale === defaultLocaleCode) { return; // skipping update for default locale } page.locales[locale].permalink = defaultLocale.permalink; }); } private localizedContractToContract(defaultLocale: string, currentLocale: string, requestedLocale: string, localizedPageContract: PageLocalizedContract): PageContract { const locales = localizedPageContract[Constants.localePrefix]; const pageMetadata = (requestedLocale ? locales[requestedLocale] : locales[currentLocale]) || this.copyMetadata(locales[defaultLocale], {}); if (!pageMetadata) { return null; } const pageContract: any = { key: localizedPageContract.key, ...pageMetadata }; return pageContract; } public async getPageByPermalink(permalink: string, requestedLocale: string = null): Promise<PageContract> { if (!permalink) { throw new Error(`Parameter "permalink" not specified.`); } const defaultLocale = await this.localeService.getDefaultLocaleCode(); const currentLocale = await this.localeService.getCurrentLocaleCode(); const permalinkProperty = `${Constants.localePrefix}/${defaultLocale}/permalink`; // We use permalink from default locale only (for now). const query = Query .from<PageContract>() .where(permalinkProperty, Operator.equals, permalink); try { const pageOfObjects = await this.objectStorage.searchObjects<PageLocalizedContract>(this.pagesPath, query); const result = pageOfObjects.value; if (!result) { return null; } const pages: any[] = Object.values(result); if (pages.length === 0) { return null; } const firstPage = pages[0]; return this.localizedContractToContract(defaultLocale, currentLocale, requestedLocale, firstPage); } catch (error) { throw new Error(`Unable to search pages: ${error.stack || error.message}`); } } public async getPageByKey(key: string, requestedLocale?: string): Promise<PageContract> { if (!key) { throw new Error(`Parameter "key" not specified.`); } const pageContract = await this.objectStorage.getObject<PageLocalizedContract>(key); if (!pageContract) { return null; } const defaultLocale = await this.localeService.getDefaultLocaleCode(); const currentLocale = await this.localeService.getCurrentLocaleCode(); return this.localizedContractToContract(defaultLocale, currentLocale, requestedLocale, pageContract); } private convertPage(localizedPage: Page<PageLocalizedContract>, defaultLocale: string, searchLocale: string, requestedLocale: string): Page<PageContract> { const resultPage: Page<PageContract> = { value: localizedPage.value.map(x => this.localizedContractToContract(defaultLocale, searchLocale, requestedLocale, x)), takeNext: async (): Promise<Page<PageContract>> => { const nextLocalizedPage = await localizedPage.takeNext(); return this.convertPage(nextLocalizedPage, defaultLocale, searchLocale, requestedLocale); } }; if (!localizedPage.takeNext) { resultPage.takeNext = null; } return resultPage; } public async search(query: Query<PageContract>, requestedLocale?: string): Promise<Page<PageContract>> { if (!query) { throw new Error(`Parameter "query" not specified.`); } const defaultLocale = await this.localeService.getDefaultLocaleCode(); const currentLocale = await this.localeService.getCurrentLocaleCode(); const searchLocale = requestedLocale || currentLocale; const localizedQuery = Utils.localizeQuery(query, searchLocale); try { const pageOfResults = await this.objectStorage.searchObjects<PageLocalizedContract>(this.pagesPath, localizedQuery); return this.convertPage(pageOfResults, defaultLocale, searchLocale, requestedLocale); } catch (error) { throw new Error(`Unable to search pages: ${error.stack || error.message}`); } } public async deletePage(page: PageContract): Promise<void> { if (!page) { throw new Error(`Parameter "page" not specified.`); } try { const localizedPageContract = await this.objectStorage.getObject<PageLocalizedContract>(page.key); if (localizedPageContract.locales) { const contentKeys = Object .values(localizedPageContract.locales) .map(locale => locale.contentKey) .filter(locale => !!locale); for (const contentKey of contentKeys) { await this.objectStorage.deleteObject(contentKey); } } await this.objectStorage.deleteObject(page.key); this.logger.trackEvent("PageDeleted", { key: page.key }); } catch (error) { throw new Error(`Unable to delete page ${page.title}: ${error.stack || error.message}`); } } public async createPage(permalink: string, title: string, description: string, keywords: string): Promise<PageContract> { const locale = await this.localeService.getDefaultLocaleCode(); const identifier = Utils.guid(); const pageKey = `${this.pagesPath}/${identifier}`; const contentKey = `${documentsPath}/${identifier}`; const localizedPage: PageLocalizedContract = { key: pageKey, locales: { [locale]: { title: title, description: description, keywords: keywords, permalink: permalink, contentKey: contentKey } } }; await this.objectStorage.addObject<PageLocalizedContract>(pageKey, localizedPage); const template = await this.blockService.getBlockContent(templateBlockKey); template["key"] = contentKey; // rewriting own key await this.objectStorage.addObject<Contract>(contentKey, template); this.logger.trackEvent("PageAdded", { key: pageKey, contentKey: contentKey }); const pageContent: PageContract = { key: pageKey, title: title, description: description, keywords: keywords, permalink: permalink, contentKey: contentKey }; return pageContent; } public async copyPage(key: string): Promise<PageContract> { const originalPage = await this.objectStorage.getObject<PageLocalizedContract>(key); const identifier = Utils.guid(); const targetKey = `${this.pagesPath}/${identifier}`; const targetPage: PageLocalizedContract = { key: targetKey, locales: {} }; const sourceLocales = Object.keys(originalPage.locales); for (const locale of sourceLocales) { const sourceMetadata = originalPage.locales[locale]; const sourceContentKey = sourceMetadata.contentKey; const sourcePageContent = await this.objectStorage.getObject<Contract>(sourceContentKey); const targetIdentifier = Utils.guid(); const targetContentKey = `${documentsPath}/${targetIdentifier}`; const targetMetadata: PageMetadata = { title: sourceMetadata.title + " (copy)", description: sourceMetadata.description, keywords: sourceMetadata.keywords, contentKey: targetContentKey, permalink: sourceMetadata.permalink + "-copy", jsonLd: sourceMetadata.jsonLd, socialShareData: sourceMetadata.socialShareData }; targetPage.locales[locale] = targetMetadata; const targetPageContent = Objects.clone<Contract>(sourcePageContent); targetPageContent["key"] = targetContentKey; await this.objectStorage.addObject<Contract>(targetContentKey, targetPageContent); this.logger.trackEvent("PageCopied", { key: targetKey, sourcePageKey: key }); } await this.objectStorage.addObject<PageLocalizedContract>(targetKey, targetPage); return this.getPageByKey(targetKey); } public async updatePage(page: PageContract, requestedLocale?: string): Promise<void> { if (!page) { throw new Error(`Parameter "page" not specified.`); } if (!requestedLocale) { requestedLocale = await this.localeService.getCurrentLocaleCode(); } const pageContract = await this.objectStorage.getObject<PageLocalizedContract>(page.key); if (!pageContract) { throw new Error(`Could not update page. Page with key "${page.key}" doesn't exist.`); } const existingLocaleMetadata = pageContract.locales[requestedLocale] || <PageMetadata>{}; pageContract.locales[requestedLocale] = this.copyMetadata(page, existingLocaleMetadata); const defaultLocale = await this.localeService.getDefaultLocaleCode(); this.syncLocalePermalinks(defaultLocale, pageContract); await this.objectStorage.updateObject<PageLocalizedContract>(page.key, pageContract); } public async getPageContent(pageKey: string, requestedLocale?: string): Promise<Contract> { if (!pageKey) { throw new Error(`Parameter "pageKey" not specified.`); } if (!requestedLocale) { requestedLocale = await this.localeService.getCurrentLocaleCode(); } const defaultLocale = await this.localeService.getDefaultLocaleCode(); const localizedPageContract = await this.objectStorage.getObject<PageLocalizedContract>(pageKey); let pageMetadata = localizedPageContract.locales[requestedLocale]; if (!pageMetadata) { pageMetadata = localizedPageContract.locales[defaultLocale]; } let pageContent: Contract; if (pageMetadata.contentKey) { pageContent = await this.objectStorage.getObject<Contract>(pageMetadata.contentKey); } else { const pageDefaultLocaleMetadata = localizedPageContract.locales[defaultLocale]; pageContent = await this.objectStorage.getObject<Contract>(pageDefaultLocaleMetadata.contentKey); } return pageContent; } public async updatePageContent(pageKey: string, content: Contract, requestedLocale?: string, changeDescription?: string): Promise<void> { if (!pageKey) { throw new Error(`Parameter "pageKey" not specified.`); } if (!content) { throw new Error(`Parameter "content" not specified.`); } const localizedPageContract = await this.objectStorage.getObject<PageLocalizedContract>(pageKey); if (!localizedPageContract) { throw new Error(`Page with key "${pageKey}" not found.`); } if (!requestedLocale) { requestedLocale = await this.localeService.getCurrentLocaleCode(); } let pageMetadata = localizedPageContract.locales[requestedLocale]; if (!pageMetadata) { const defaultLocale = await this.localeService.getDefaultLocaleCode(); const defaultPageMetadata = localizedPageContract.locales[defaultLocale]; const identifier = Utils.guid(); pageMetadata = this.copyMetadata(defaultPageMetadata, { contentKey: `${documentsPath}/${identifier}` }); localizedPageContract.locales[requestedLocale] = pageMetadata; await this.objectStorage.updateObject(pageKey, localizedPageContract); } else if (!pageMetadata.contentKey) { const identifier = Utils.guid(); pageMetadata.contentKey = `${documentsPath}/${identifier}`; await this.objectStorage.updateObject(pageKey, pageMetadata); } await this.objectStorage.updateObject(pageMetadata.contentKey, content, changeDescription); } }