import { URL } from 'url'; import { EntityRepository, getManager, Transaction, TransactionManager, FindCondition, } from 'typeorm'; import { Page } from '@/entities'; import logger from '@/logger'; import { makeValidURL, queryObjToQueryString, transformURL } from './utils'; @EntityRepository(Page) export default class PageRepository { @Transaction() async getByUrl( url: string, { productFirebaseId }: { productFirebaseId?: string } = {}, @TransactionManager() manager = getManager() ): Promise { const transformedURL = transformURL(url); const { hostname, pathname, search, searchParams } = new URL( makeValidURL(transformedURL) ); const [page, ...rest] = await manager.find(Page, { where: { ...(productFirebaseId ? { productFirebaseId } : {}), // NOTE: in this instance, we are ok with allowing undiscoverable // pages. // discoverable: true, url: `${hostname}${pathname}`, ...(search && search.length > 0 ? { urlQuery: queryObjToQueryString( Array.from(searchParams.entries()).reduce( (prev, [key, value]) => { return { ...prev, [key]: value }; }, {} ) ), } : {}), }, relations: ['product'], // max only need 2 since we throw if there is more than one take: 2, }); if (rest.length > 0 && search.length === 0) { throw new Error( 'Too many pages match this URL, please be more specific.' ); } if (!page) { throw new Error('Could not find page with that URL'); } return page; } /** * Gets all pages by URLs returned in the order of the urls that were * passed in, or null of no matching page is found. */ @Transaction() async getByUrls( urls: string[], @TransactionManager() manager = getManager() ): Promise<(Page | null)[]> { const transformedURLs = urls.map((original) => { const url = new URL(makeValidURL(transformURL(original))); const search = url.search; const urlQuery = search && search.length > 0 ? queryObjToQueryString( Array.from(url.searchParams.entries()).reduce( (prev, [key, value]) => { return { ...prev, [key]: value }; }, {} ) ) : undefined; return { original, url: `${url.hostname}${url.pathname}`, urlQuery, search, }; }); const where: FindCondition[] = transformedURLs.map( ({ url, search, urlQuery }) => ({ url, ...(search && search.length > 0 ? { urlQuery, } : {}), }) ); const pages = await manager.find(Page, { where, relations: ['product'], }); return transformedURLs.map(({ original, url, urlQuery, search }) => { const [page, ...rest] = pages.filter( (page) => page.url === url && (!urlQuery || page.urlQuery === urlQuery) ); if (rest.length > 0 && search.length === 0) { logger.warn( `Too many pages match URL: ${original}, please be more specific.` ); return null; } if (!page) { logger.warn(`Could not find page with URL: ${original}`); return null; } return page; }); } }