feat: migrate stock photo integration from Unsplash to Pexels
- Replaced Unsplash API integration with Pexels API for stock photo search and import - Updated API response transformation to match frontend expectations with Pexels data structure - Removed Unsplash-specific download tracking and attribution requirements - Modified environment configuration to use PEXELS_API_KEY instead of UNSPLASH_ACCESS_KEY - Updated UI text and attribution links to reference Pexels instead of Unsplash - Simplifie
This commit is contained in:
parent
02878439af
commit
0c2813bea6
@ -21,9 +21,6 @@ type StockPhoto = {
|
||||
html: string;
|
||||
};
|
||||
};
|
||||
links: {
|
||||
download_location: string;
|
||||
};
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
@ -198,7 +195,6 @@ export default function MediaLibrary({
|
||||
body: JSON.stringify({
|
||||
photoId: photo.id,
|
||||
downloadUrl: photo.urls.regular,
|
||||
downloadLocation: photo.links.download_location,
|
||||
photographer: photo.user.name,
|
||||
photographerUrl: photo.user.links.html,
|
||||
}),
|
||||
@ -298,7 +294,7 @@ export default function MediaLibrary({
|
||||
|
||||
{!stockLoading && stockPhotos.length === 0 && !stockQuery && (
|
||||
<Typography variant="body2" sx={{ textAlign: 'center', py: 4, color: 'text.secondary' }}>
|
||||
Enter a search term to find free stock photos from Unsplash.
|
||||
Enter a search term to find free stock photos from Pexels.
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
@ -309,7 +305,7 @@ export default function MediaLibrary({
|
||||
<img src={photo.urls.small} alt={photo.alt_description || 'Stock photo'} style={{ maxWidth: '100%', maxHeight: '150px', objectFit: 'contain', display: 'block' }} />
|
||||
</Box>
|
||||
<Typography variant="caption" sx={{ display: 'block', mb: 0.5 }}>
|
||||
Photo by <a href={photo.user.links.html + '?utm_source=voxblog&utm_medium=referral'} target="_blank" rel="noopener noreferrer" style={{ color: 'inherit' }}>{photo.user.name}</a>
|
||||
Photo by <a href={photo.user.links.html} target="_blank" rel="noopener noreferrer" style={{ color: 'inherit' }}>{photo.user.name}</a>
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', color: 'text.secondary', mb: 1 }}>
|
||||
{photo.width} × {photo.height}
|
||||
|
||||
@ -4,11 +4,11 @@ import crypto from 'crypto';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
const UNSPLASH_ACCESS_KEY = process.env.UNSPLASH_ACCESS_KEY;
|
||||
const PEXELS_API_KEY = process.env.PEXELS_API_KEY;
|
||||
|
||||
/**
|
||||
* GET /api/stock-photos/search
|
||||
* Search Unsplash for stock photos
|
||||
* Search Pexels for stock photos
|
||||
*/
|
||||
router.get('/search', async (req, res) => {
|
||||
try {
|
||||
@ -18,21 +18,47 @@ router.get('/search', async (req, res) => {
|
||||
return res.status(400).json({ error: 'query parameter is required' });
|
||||
}
|
||||
|
||||
if (!UNSPLASH_ACCESS_KEY) {
|
||||
return res.status(500).json({ error: 'Unsplash API key not configured' });
|
||||
if (!PEXELS_API_KEY) {
|
||||
return res.status(500).json({ error: 'Pexels API key not configured' });
|
||||
}
|
||||
|
||||
const unsplashUrl = `https://api.unsplash.com/search/photos?query=${encodeURIComponent(query)}&per_page=${per_page}&client_id=${UNSPLASH_ACCESS_KEY}`;
|
||||
const pexelsUrl = `https://api.pexels.com/v1/search?query=${encodeURIComponent(query)}&per_page=${per_page}`;
|
||||
|
||||
const response = await fetch(unsplashUrl);
|
||||
const response = await fetch(pexelsUrl, {
|
||||
headers: {
|
||||
'Authorization': PEXELS_API_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Unsplash API error: ${response.status}`);
|
||||
throw new Error(`Pexels API error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
res.json(data);
|
||||
// Transform Pexels response to match expected format
|
||||
const transformedData = {
|
||||
results: data.photos?.map((photo: any) => ({
|
||||
id: String(photo.id),
|
||||
urls: {
|
||||
small: photo.src.small,
|
||||
regular: photo.src.large,
|
||||
full: photo.src.original,
|
||||
},
|
||||
alt_description: photo.alt || null,
|
||||
user: {
|
||||
name: photo.photographer,
|
||||
links: {
|
||||
html: photo.photographer_url,
|
||||
},
|
||||
},
|
||||
width: photo.width,
|
||||
height: photo.height,
|
||||
})) || [],
|
||||
total: data.total_results || 0,
|
||||
};
|
||||
|
||||
res.json(transformedData);
|
||||
} catch (err: any) {
|
||||
console.error('[Stock Photos] Search error:', err);
|
||||
res.status(500).json({
|
||||
@ -48,31 +74,18 @@ router.get('/search', async (req, res) => {
|
||||
*/
|
||||
router.post('/import', async (req, res) => {
|
||||
try {
|
||||
const { photoId, downloadUrl, downloadLocation, photographer, photographerUrl } = req.body;
|
||||
const { photoId, downloadUrl, photographer, photographerUrl } = req.body;
|
||||
|
||||
if (!photoId || !downloadUrl) {
|
||||
return res.status(400).json({ error: 'photoId and downloadUrl are required' });
|
||||
}
|
||||
|
||||
if (!UNSPLASH_ACCESS_KEY) {
|
||||
return res.status(500).json({ error: 'Unsplash API key not configured' });
|
||||
if (!PEXELS_API_KEY) {
|
||||
return res.status(500).json({ error: 'Pexels API key not configured' });
|
||||
}
|
||||
|
||||
console.log('[Stock Photos] Importing photo:', photoId);
|
||||
|
||||
// Trigger download tracking (required by Unsplash API guidelines)
|
||||
if (downloadLocation) {
|
||||
try {
|
||||
await fetch(downloadLocation, {
|
||||
headers: {
|
||||
'Authorization': `Client-ID ${UNSPLASH_ACCESS_KEY}`,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('[Stock Photos] Failed to track download:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Download the image
|
||||
const imageResponse = await fetch(downloadUrl);
|
||||
|
||||
@ -93,7 +106,7 @@ router.post('/import', async (req, res) => {
|
||||
// Generate unique filename
|
||||
const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
||||
const randomId = crypto.randomBytes(8).toString('hex');
|
||||
const filename = `unsplash-${photoId}-${randomId}.${ext}`;
|
||||
const filename = `pexels-${photoId}-${randomId}.${ext}`;
|
||||
const key = `images/${timestamp}/${filename}`;
|
||||
|
||||
// Upload to S3
|
||||
|
||||
@ -49,6 +49,9 @@ services:
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
||||
S3_ENDPOINT: ${S3_ENDPOINT}
|
||||
INFISICAL_TOKEN: ${INFISICAL_TOKEN}
|
||||
INFISICAL_SITE_URL: ${INFISICAL_SITE_URL}
|
||||
PEXELS_API_KEY: ${PEXELS_API_KEY}
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
|
||||
Loading…
Reference in New Issue
Block a user