This commit is contained in:
Flatlogic Bot 2026-03-04 23:10:55 +00:00
parent c56b38d9e4
commit 2fdc2b3497
3 changed files with 133 additions and 62 deletions

View File

@ -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;

View File

@ -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();

View File

@ -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;
}
}