diff --git a/server/src/controllers/items-controller.ts b/server/src/controllers/items-controller.ts index 8cce246..a114538 100644 --- a/server/src/controllers/items-controller.ts +++ b/server/src/controllers/items-controller.ts @@ -6,6 +6,11 @@ import { listItems as listItemsRepo, updateItem as updateItemRepo } from '../db/items-repository.js'; +import { + createUrl, + findUrlByValue, + updateUrlBodyText +} from '../db/urls-repository.js'; import { findImageById } from '../db/images-repository.js'; import { HttpError } from '../errors/http-error.js'; import { resolveUrlId } from './url-helpers.js'; @@ -17,6 +22,7 @@ import { parseItemUpdatePayload } from '../validation/items.js'; import { readUrlMarkdown } from '../services/url-reader-service.js'; +import { normalizeUrl } from '../utils/url-normalizer.js'; export const listItems = async (req: Request, res: Response) => { const projectId = parseUuid(req.params.projectId, 'projectId'); @@ -162,17 +168,34 @@ export const importItemFromUrl = async (req: Request, res: Response) => { // If the URL constructor fails here, readUrlMarkdown will surface the error. } + const normalizedDbUrl = normalizeUrl(url); + let urlRecord = await findUrlByValue(normalizedDbUrl); let markdown: string; - try { - markdown = await readUrlMarkdown(normalizedUrl); - } catch (error) { - if (error instanceof HttpError) { - throw error; + + const existingBody = urlRecord?.bodyText; + if (existingBody && existingBody.trim().length > 0) { + markdown = existingBody; + } else { + try { + markdown = await readUrlMarkdown(normalizedUrl); + } catch (error) { + if (error instanceof HttpError) { + throw error; + } + throw new HttpError( + 422, + 'We could not read that URL. Please enter the item details manually.' + ); + } + + if (urlRecord) { + const updated = await updateUrlBodyText(urlRecord.id, markdown); + if (updated) { + urlRecord = updated; + } + } else { + urlRecord = await createUrl({ url: normalizedDbUrl, bodyText: markdown }); } - throw new HttpError( - 422, - 'We could not read that URL. Please enter the item details manually.' - ); } try { diff --git a/server/src/db/urls-repository.ts b/server/src/db/urls-repository.ts index 7906a62..bdc286e 100644 --- a/server/src/db/urls-repository.ts +++ b/server/src/db/urls-repository.ts @@ -90,3 +90,25 @@ export const createUrl = async (params: CreateUrlParams): Promise => return mapUrlRow(result.rows[0]); }; + +export const updateUrlBodyText = async ( + id: string, + bodyText: string +): Promise => { + const result = await query( + ` + UPDATE urls + SET body_text = $2, + updated_at = NOW() + WHERE id = $1 + RETURNING id, url, body_text, attributes, has_price, created_at, updated_at + `, + [id, bodyText] + ); + + if (result.rows.length === 0) { + return null; + } + + return mapUrlRow(result.rows[0]); +}; diff --git a/server/src/services/url-reader-service.ts b/server/src/services/url-reader-service.ts index d1b1fbb..84ae842 100644 --- a/server/src/services/url-reader-service.ts +++ b/server/src/services/url-reader-service.ts @@ -40,7 +40,7 @@ export const readUrlMarkdown = async (rawUrl: string): Promise => { Authorization: `Bearer ${env.urlReader.apiKey}` } }); - } catch (error) { + } catch { throw new HttpError( 502, 'We could not reach the URL reader service. Please try again later.'