mirror of
https://github.com/sakaljurgis/best-choice.git
synced 2026-07-08 21:47:40 +00:00
add item by url 1st step
This commit is contained in:
@@ -11,6 +11,7 @@ import { resolveUrlId } from './url-helpers.js';
|
||||
import { parsePaginationParams, parseUuid } from '../validation/common.js';
|
||||
import {
|
||||
parseItemCreatePayload,
|
||||
parseItemImportPayload,
|
||||
parseItemStatusFilter,
|
||||
parseItemUpdatePayload
|
||||
} from '../validation/items.js';
|
||||
@@ -120,3 +121,64 @@ export const deleteItem = async (req: Request, res: Response) => {
|
||||
|
||||
res.status(204).send();
|
||||
};
|
||||
|
||||
export const importItemFromUrl = async (req: Request, res: Response) => {
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 1000);
|
||||
})
|
||||
const projectId = parseUuid(req.params.projectId, 'projectId');
|
||||
// projectId is validated but not otherwise used for the mock response.
|
||||
void projectId;
|
||||
|
||||
const { url } = parseItemImportPayload(req.body);
|
||||
const loweredUrl = url.toLowerCase();
|
||||
|
||||
if (loweredUrl.includes('fail') || loweredUrl.includes('error')) {
|
||||
throw new HttpError(
|
||||
422,
|
||||
'We could not read that URL. Please enter the item details manually.'
|
||||
);
|
||||
}
|
||||
|
||||
let hostname: string | null = null;
|
||||
let lastPathSegment: string | null = null;
|
||||
try {
|
||||
const parsed = new URL(url);
|
||||
hostname = parsed.hostname.replace(/^www\./, '');
|
||||
const pathSegments = parsed.pathname.split('/').filter(Boolean);
|
||||
if (pathSegments.length) {
|
||||
lastPathSegment = pathSegments[pathSegments.length - 1];
|
||||
}
|
||||
} catch {
|
||||
// non-standard URLs fall back to generic mock content
|
||||
}
|
||||
|
||||
const manufacturerBase = hostname ? hostname.split('.')[0] : 'imported';
|
||||
const manufacturer =
|
||||
manufacturerBase.charAt(0).toUpperCase() + manufacturerBase.slice(1);
|
||||
|
||||
const cleanedSegment =
|
||||
lastPathSegment?.replace(/[-_]/g, ' ').replace(/\s+/g, ' ').trim() ?? 'Item';
|
||||
const model = cleanedSegment.length ? cleanedSegment : 'Imported Item';
|
||||
|
||||
const attributes: Record<string, unknown> = {
|
||||
originUrl: url,
|
||||
importedAt: new Date().toISOString(),
|
||||
condition: 'New',
|
||||
availability: 'In stock',
|
||||
dpi: 1200,
|
||||
};
|
||||
|
||||
if (hostname) {
|
||||
attributes.domain = hostname;
|
||||
}
|
||||
|
||||
res.json({
|
||||
data: {
|
||||
manufacturer,
|
||||
model,
|
||||
note: null,
|
||||
attributes
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createItem,
|
||||
deleteItem,
|
||||
getItem,
|
||||
importItemFromUrl,
|
||||
listItems,
|
||||
updateItem
|
||||
} from '../controllers/items-controller.js';
|
||||
@@ -33,6 +34,7 @@ apiRouter.delete('/projects/:projectId', asyncHandler(deleteProject));
|
||||
|
||||
// Project items
|
||||
apiRouter.get('/projects/:projectId/items', asyncHandler(listItems));
|
||||
apiRouter.post('/projects/:projectId/items/import', asyncHandler(importItemFromUrl));
|
||||
apiRouter.post('/projects/:projectId/items', asyncHandler(createItem));
|
||||
|
||||
// Items
|
||||
|
||||
@@ -183,3 +183,21 @@ export const parseItemStatusFilter = (
|
||||
}
|
||||
return value as 'active' | 'rejected';
|
||||
};
|
||||
|
||||
export interface ItemImportPayload {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const parseItemImportPayload = (payload: unknown): ItemImportPayload => {
|
||||
if (payload === null || typeof payload !== 'object') {
|
||||
throw new HttpError(400, 'Body must be an object');
|
||||
}
|
||||
|
||||
const { url } = payload as Record<string, unknown>;
|
||||
|
||||
if (typeof url !== 'string' || url.trim().length === 0) {
|
||||
throw new HttpError(400, 'url must be a non-empty string');
|
||||
}
|
||||
|
||||
return { url: url.trim() };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user