feat: enhance Unsplash integration with pagination, random images, and portrait orientation

- Added pagination support to search endpoint with page parameter and orientation control (defaults to portrait)
- Implemented new /random endpoint for fetching random portrait images with configurable count
- Enhanced UnsplashPickerComponent with infinite scroll, loading states, and automatic random image loading on open
- Added debounced search input, scroll detection for pagination, and improved error
This commit is contained in:
2025-11-17 15:38:07 -05:00
parent 5e8cddf92e
commit 332f586d7b
11 changed files with 827 additions and 54 deletions
+44 -2
View File
@@ -12,14 +12,18 @@ router.get('/search', async (req, res) => {
}
const q = String(req.query.q || '').trim();
const perPage = Math.min(50, Math.max(1, Number(req.query.perPage || 24)));
const page = Math.max(1, Number(req.query.page || 1));
if (!q) return res.json({ results: [] });
const url = new URL('https://api.unsplash.com/search/photos');
url.searchParams.set('query', q);
url.searchParams.set('per_page', String(perPage));
url.searchParams.set('page', String(page));
url.searchParams.set('client_id', ACCESS_KEY);
// Prefer landscape/small for editor usage
url.searchParams.set('orientation', 'landscape');
// Force portrait orientation for better display in editor
const orientation = String(req.query.orientation || 'portrait');
url.searchParams.set('orientation', orientation);
console.log('[Unsplash] Search with orientation:', orientation);
const upstream = await fetch(url.toString(), { headers: { 'Accept-Version': 'v1' } });
if (!upstream.ok) {
@@ -35,6 +39,7 @@ router.get('/search', async (req, res) => {
links: r.links,
user: r.user ? { name: r.user.name } : undefined,
})) : [];
console.log(`[Unsplash] Search returned ${results.length} results for orientation="${orientation}"`);
return res.json({ results });
} catch (e) {
console.error('[Unsplash] proxy error', e);
@@ -42,4 +47,41 @@ router.get('/search', async (req, res) => {
}
});
router.get('/random', async (req, res) => {
try {
const ACCESS_KEY = process.env.UNSPLASH_ACCESS_KEY;
if (!ACCESS_KEY) {
return res.status(501).json({ error: 'unsplash_disabled' });
}
const perPage = Math.min(50, Math.max(1, Number(req.query.perPage || 24)));
const orientation = String(req.query.orientation || 'portrait');
console.log(`[Unsplash] Random request with orientation="${orientation}", count=${perPage}`);
const url = new URL('https://api.unsplash.com/photos/random');
url.searchParams.set('count', String(perPage));
url.searchParams.set('client_id', ACCESS_KEY);
url.searchParams.set('orientation', orientation);
const upstream = await fetch(url.toString(), { headers: { 'Accept-Version': 'v1' } });
if (!upstream.ok) {
const text = await upstream.text().catch(() => '');
return res.status(502).json({ error: 'unsplash_upstream_error', status: upstream.status, message: text });
}
const json = await upstream.json();
const arr = Array.isArray(json) ? json : (json ? [json] : []);
const results = arr.map((r) => ({
id: r.id,
alt_description: r.alt_description || null,
urls: r.urls,
links: r.links,
user: r.user ? { name: r.user.name } : undefined,
}));
console.log(`[Unsplash] Random returned ${results.length} results for orientation="${orientation}"`);
return res.json({ results });
} catch (e) {
console.error('[Unsplash] random proxy error', e);
return res.status(500).json({ error: 'internal_error' });
}
});
export default router;