Oo
This commit is contained in:
parent
c56b38d9e4
commit
2fdc2b3497
@ -157,7 +157,9 @@ if (fs.existsSync(publicDir)) {
|
||||
});
|
||||
}
|
||||
|
||||
const PORT = process.env.NODE_ENV === 'dev_stage' ? 3000 : 8080;
|
||||
const PORT = Number(
|
||||
process.env.PORT || (process.env.NODE_ENV === 'dev_stage' ? 3000 : 8080),
|
||||
);
|
||||
|
||||
db.sequelize.sync().then(function () {
|
||||
app.listen(PORT, () => {
|
||||
@ -165,4 +167,4 @@ db.sequelize.sync().then(function () {
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = app;
|
||||
module.exports = app;
|
||||
|
||||
@ -4,6 +4,47 @@ const { pexelsKey, pexelsQuery } = require('../config');
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
const KEY = pexelsKey;
|
||||
const REQUEST_TIMEOUT_MS = 4500;
|
||||
|
||||
const FALLBACK_IMAGE = {
|
||||
src: {
|
||||
original:
|
||||
'https://images.pexels.com/photos/8199252/pexels-photo-8199252.jpeg',
|
||||
},
|
||||
photographer: 'Yan Krukau',
|
||||
photographer_url: 'https://www.pexels.com/@yankrukov',
|
||||
};
|
||||
|
||||
const FALLBACK_VIDEO = {
|
||||
video_files: [],
|
||||
user: {
|
||||
name: 'Pexels',
|
||||
url: 'https://www.pexels.com/',
|
||||
},
|
||||
};
|
||||
|
||||
async function fetchJsonWithTimeout(url, headers) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { headers, signal: controller.signal });
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contentType = response.headers.get('content-type') || '';
|
||||
if (!contentType.includes('application/json')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
return null;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
|
||||
router.get('/image', async (req, res) => {
|
||||
const headers = {
|
||||
@ -14,13 +55,10 @@ router.get('/image', async (req, res) => {
|
||||
const perPage = 1;
|
||||
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
res.status(200).json(data.photos[0]);
|
||||
} catch (error) {
|
||||
res.status(200).json({ error: 'Failed to fetch image' });
|
||||
}
|
||||
const data = await fetchJsonWithTimeout(url, headers);
|
||||
const image = data && Array.isArray(data.photos) ? data.photos[0] : null;
|
||||
|
||||
res.status(200).json(image || FALLBACK_IMAGE);
|
||||
});
|
||||
|
||||
router.get('/video', async (req, res) => {
|
||||
@ -32,13 +70,10 @@ router.get('/video', async (req, res) => {
|
||||
const perPage = 1;
|
||||
const url = `https://api.pexels.com/videos/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
|
||||
|
||||
try {
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
res.status(200).json(data.videos[0]);
|
||||
} catch (error) {
|
||||
res.status(200).json({ error: 'Failed to fetch video' });
|
||||
}
|
||||
const data = await fetchJsonWithTimeout(url, headers);
|
||||
const video = data && Array.isArray(data.videos) ? data.videos[0] : null;
|
||||
|
||||
res.status(200).json(video || FALLBACK_VIDEO);
|
||||
});
|
||||
|
||||
router.get('/multiple-images', async (req, res) => {
|
||||
@ -52,11 +87,6 @@ router.get('/multiple-images', async (req, res) => {
|
||||
const orientation = 'square';
|
||||
const perPage = 1;
|
||||
|
||||
const fallbackImage = {
|
||||
src: 'https://images.pexels.com/photos/8199252/pexels-photo-8199252.jpeg',
|
||||
photographer: 'Yan Krukau',
|
||||
photographer_url: 'https://www.pexels.com/@yankrukov',
|
||||
};
|
||||
const fetchFallbackImage = async () => {
|
||||
try {
|
||||
const response = await fetch('https://picsum.photos/600');
|
||||
@ -66,13 +96,19 @@ router.get('/multiple-images', async (req, res) => {
|
||||
photographer_url: 'https://picsum.photos/',
|
||||
};
|
||||
} catch (error) {
|
||||
return fallbackImage;
|
||||
return {
|
||||
src: FALLBACK_IMAGE.src.original,
|
||||
photographer: FALLBACK_IMAGE.photographer,
|
||||
photographer_url: FALLBACK_IMAGE.photographer_url,
|
||||
};
|
||||
}
|
||||
};
|
||||
const fetchImage = async (query) => {
|
||||
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
|
||||
const response = await fetch(url, { headers });
|
||||
const data = await response.json();
|
||||
const data = await fetchJsonWithTimeout(url, headers);
|
||||
if (!data || !Array.isArray(data.photos)) {
|
||||
return null;
|
||||
}
|
||||
return data.photos[0] || null;
|
||||
};
|
||||
|
||||
@ -83,9 +119,10 @@ router.get('/multiple-images', async (req, res) => {
|
||||
if (result.status === 'fulfilled' && result.value) {
|
||||
const image = result.value;
|
||||
return {
|
||||
src: image.src?.original || fallbackImage.src,
|
||||
photographer: image.photographer || fallbackImage.photographer,
|
||||
photographer_url: image.photographer_url || fallbackImage.photographer_url,
|
||||
src: image.src?.original || FALLBACK_IMAGE.src.original,
|
||||
photographer: image.photographer || FALLBACK_IMAGE.photographer,
|
||||
photographer_url:
|
||||
image.photographer_url || FALLBACK_IMAGE.photographer_url,
|
||||
};
|
||||
} else {
|
||||
const fallback = await fetchFallbackImage();
|
||||
|
||||
@ -1,23 +1,51 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export async function getPexelsImage() {
|
||||
const FALLBACK_IMAGE = {
|
||||
src: {
|
||||
original:
|
||||
'https://images.pexels.com/photos/8199252/pexels-photo-8199252.jpeg',
|
||||
},
|
||||
photographer: 'Yan Krukau',
|
||||
photographer_url: 'https://www.pexels.com/@yankrukov',
|
||||
};
|
||||
|
||||
const FALLBACK_VIDEO = {
|
||||
video_files: [],
|
||||
user: {
|
||||
name: 'Pexels',
|
||||
url: 'https://www.pexels.com/',
|
||||
},
|
||||
};
|
||||
|
||||
async function safeGet(url: string, params?: Record<string, string>) {
|
||||
try {
|
||||
const response = await axios.get('pexels/image');
|
||||
return response.data;
|
||||
const response = await axios.get(url, {
|
||||
params,
|
||||
timeout: 7000,
|
||||
// Keep UI stable even if upstream/proxy returns 5xx.
|
||||
validateStatus: () => true,
|
||||
});
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
console.warn(`[pexels] ${url} returned ${response.status}`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching image:', error);
|
||||
console.warn(`[pexels] ${url} unavailable`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPexelsImage() {
|
||||
const data = await safeGet('pexels/image');
|
||||
return data || FALLBACK_IMAGE;
|
||||
}
|
||||
|
||||
export async function getPexelsVideo() {
|
||||
try {
|
||||
const response = await axios.get('pexels/video');
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Error fetching video:', error);
|
||||
return null;
|
||||
}
|
||||
const data = await safeGet('pexels/video');
|
||||
return data || FALLBACK_VIDEO;
|
||||
}
|
||||
|
||||
|
||||
@ -29,48 +57,52 @@ export async function getMultiplePexelsImages(
|
||||
const normalizeQuery = (query) =>
|
||||
query.trim().toLowerCase().replace(/\s+/g, '');
|
||||
|
||||
if (typeof window === 'undefined') {
|
||||
return queries.map(() => FALLBACK_IMAGE);
|
||||
}
|
||||
|
||||
while (localStorageLock) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
}
|
||||
localStorageLock = true;
|
||||
|
||||
const cachedImages = JSON.parse(localStorage.getItem('pexelsImagesCache')) || {};
|
||||
try {
|
||||
const cachedImages =
|
||||
JSON.parse(localStorage.getItem('pexelsImagesCache')) || {};
|
||||
|
||||
const isImageCached = (query) => {
|
||||
const normalizedQuery = normalizeQuery(query);
|
||||
const cached = cachedImages[normalizedQuery];
|
||||
const isCached =
|
||||
cached && cached.src && cached.photographer && cached.photographer_url;
|
||||
return isCached;
|
||||
};
|
||||
|
||||
const isImageCached = (query) => {
|
||||
const normalizedQuery = normalizeQuery(query);
|
||||
const cached = cachedImages[normalizedQuery];
|
||||
const isCached = cached && cached.src && cached.photographer && cached.photographer_url;
|
||||
return isCached;
|
||||
};
|
||||
const missingQueries = queries.filter((query) => !isImageCached(query));
|
||||
|
||||
const missingQueries = queries.filter((query) => !isImageCached(query));
|
||||
if (missingQueries.length > 0) {
|
||||
const queryString = missingQueries.join(',');
|
||||
|
||||
if (missingQueries.length > 0) {
|
||||
const queryString = missingQueries.join(',');
|
||||
|
||||
try {
|
||||
const response = await axios.get('pexels/multiple-images', {
|
||||
params: { queries: queryString },
|
||||
const response = await safeGet('pexels/multiple-images', {
|
||||
queries: queryString,
|
||||
});
|
||||
|
||||
missingQueries.forEach((query, index) => {
|
||||
const normalizedQuery = normalizeQuery(query);
|
||||
if (!cachedImages[normalizedQuery]) {
|
||||
cachedImages[normalizedQuery] = response.data[index];
|
||||
cachedImages[normalizedQuery] = response?.[index] || FALLBACK_IMAGE;
|
||||
}
|
||||
});
|
||||
|
||||
localStorage.setItem('pexelsImagesCache', JSON.stringify(cachedImages));
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
const result = queries.map((query) => cachedImages[normalizeQuery(query)]);
|
||||
const result = queries.map(
|
||||
(query) => cachedImages[normalizeQuery(query)] || FALLBACK_IMAGE,
|
||||
);
|
||||
|
||||
localStorageLock = false;
|
||||
|
||||
return result;
|
||||
return result;
|
||||
} finally {
|
||||
localStorageLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user