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 () {
|
db.sequelize.sync().then(function () {
|
||||||
app.listen(PORT, () => {
|
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 fetch = require('node-fetch');
|
||||||
|
|
||||||
const KEY = pexelsKey;
|
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) => {
|
router.get('/image', async (req, res) => {
|
||||||
const headers = {
|
const headers = {
|
||||||
@ -14,13 +55,10 @@ router.get('/image', async (req, res) => {
|
|||||||
const perPage = 1;
|
const perPage = 1;
|
||||||
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
|
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
|
||||||
|
|
||||||
try {
|
const data = await fetchJsonWithTimeout(url, headers);
|
||||||
const response = await fetch(url, { headers });
|
const image = data && Array.isArray(data.photos) ? data.photos[0] : null;
|
||||||
const data = await response.json();
|
|
||||||
res.status(200).json(data.photos[0]);
|
res.status(200).json(image || FALLBACK_IMAGE);
|
||||||
} catch (error) {
|
|
||||||
res.status(200).json({ error: 'Failed to fetch image' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/video', async (req, res) => {
|
router.get('/video', async (req, res) => {
|
||||||
@ -32,13 +70,10 @@ router.get('/video', async (req, res) => {
|
|||||||
const perPage = 1;
|
const perPage = 1;
|
||||||
const url = `https://api.pexels.com/videos/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
|
const url = `https://api.pexels.com/videos/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
|
||||||
|
|
||||||
try {
|
const data = await fetchJsonWithTimeout(url, headers);
|
||||||
const response = await fetch(url, { headers });
|
const video = data && Array.isArray(data.videos) ? data.videos[0] : null;
|
||||||
const data = await response.json();
|
|
||||||
res.status(200).json(data.videos[0]);
|
res.status(200).json(video || FALLBACK_VIDEO);
|
||||||
} catch (error) {
|
|
||||||
res.status(200).json({ error: 'Failed to fetch video' });
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/multiple-images', async (req, res) => {
|
router.get('/multiple-images', async (req, res) => {
|
||||||
@ -52,11 +87,6 @@ router.get('/multiple-images', async (req, res) => {
|
|||||||
const orientation = 'square';
|
const orientation = 'square';
|
||||||
const perPage = 1;
|
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 () => {
|
const fetchFallbackImage = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://picsum.photos/600');
|
const response = await fetch('https://picsum.photos/600');
|
||||||
@ -66,13 +96,19 @@ router.get('/multiple-images', async (req, res) => {
|
|||||||
photographer_url: 'https://picsum.photos/',
|
photographer_url: 'https://picsum.photos/',
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} 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 fetchImage = async (query) => {
|
||||||
const url = `https://api.pexels.com/v1/search?query=${query}&orientation=${orientation}&per_page=${perPage}&page=1`;
|
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 fetchJsonWithTimeout(url, headers);
|
||||||
const data = await response.json();
|
if (!data || !Array.isArray(data.photos)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return data.photos[0] || null;
|
return data.photos[0] || null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,9 +119,10 @@ router.get('/multiple-images', async (req, res) => {
|
|||||||
if (result.status === 'fulfilled' && result.value) {
|
if (result.status === 'fulfilled' && result.value) {
|
||||||
const image = result.value;
|
const image = result.value;
|
||||||
return {
|
return {
|
||||||
src: image.src?.original || fallbackImage.src,
|
src: image.src?.original || FALLBACK_IMAGE.src.original,
|
||||||
photographer: image.photographer || fallbackImage.photographer,
|
photographer: image.photographer || FALLBACK_IMAGE.photographer,
|
||||||
photographer_url: image.photographer_url || fallbackImage.photographer_url,
|
photographer_url:
|
||||||
|
image.photographer_url || FALLBACK_IMAGE.photographer_url,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const fallback = await fetchFallbackImage();
|
const fallback = await fetchFallbackImage();
|
||||||
|
|||||||
@ -1,23 +1,51 @@
|
|||||||
import axios from 'axios';
|
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 {
|
try {
|
||||||
const response = await axios.get('pexels/image');
|
const response = await axios.get(url, {
|
||||||
return response.data;
|
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) {
|
} catch (error) {
|
||||||
console.error('Error fetching image:', error);
|
console.warn(`[pexels] ${url} unavailable`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPexelsImage() {
|
||||||
|
const data = await safeGet('pexels/image');
|
||||||
|
return data || FALLBACK_IMAGE;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPexelsVideo() {
|
export async function getPexelsVideo() {
|
||||||
try {
|
const data = await safeGet('pexels/video');
|
||||||
const response = await axios.get('pexels/video');
|
return data || FALLBACK_VIDEO;
|
||||||
return response.data;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching video:', error);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -29,48 +57,52 @@ export async function getMultiplePexelsImages(
|
|||||||
const normalizeQuery = (query) =>
|
const normalizeQuery = (query) =>
|
||||||
query.trim().toLowerCase().replace(/\s+/g, '');
|
query.trim().toLowerCase().replace(/\s+/g, '');
|
||||||
|
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return queries.map(() => FALLBACK_IMAGE);
|
||||||
|
}
|
||||||
|
|
||||||
while (localStorageLock) {
|
while (localStorageLock) {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
}
|
}
|
||||||
localStorageLock = true;
|
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 missingQueries = queries.filter((query) => !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));
|
if (missingQueries.length > 0) {
|
||||||
|
const queryString = missingQueries.join(',');
|
||||||
|
|
||||||
if (missingQueries.length > 0) {
|
const response = await safeGet('pexels/multiple-images', {
|
||||||
const queryString = missingQueries.join(',');
|
queries: queryString,
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.get('pexels/multiple-images', {
|
|
||||||
params: { queries: queryString },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
missingQueries.forEach((query, index) => {
|
missingQueries.forEach((query, index) => {
|
||||||
const normalizedQuery = normalizeQuery(query);
|
const normalizedQuery = normalizeQuery(query);
|
||||||
if (!cachedImages[normalizedQuery]) {
|
if (!cachedImages[normalizedQuery]) {
|
||||||
cachedImages[normalizedQuery] = response.data[index];
|
cachedImages[normalizedQuery] = response?.[index] || FALLBACK_IMAGE;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
localStorage.setItem('pexelsImagesCache', JSON.stringify(cachedImages));
|
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;
|
||||||
|
} finally {
|
||||||
return result;
|
localStorageLock = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user