Версия 1.0.8 - Если при импорте статьи изображение уже не существует (добавлена проверка), то подставлять URL для картинки Изображение утеряно.
This commit is contained in:
parent
5bae3e4f0e
commit
8c06cc9f04
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,7 +2,7 @@ node_modules
|
||||
dist
|
||||
|
||||
logs
|
||||
*.log
|
||||
|
||||
|
||||
.idea
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "russcult_server",
|
||||
"version": "1.0.8",
|
||||
"version": "1.0.9",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
|
||||
@ -12,6 +12,7 @@ import { AuthorRole } from "@prisma/client";
|
||||
|
||||
|
||||
const DEFAULT_COVER_IMAGE = '/images/cover-placeholder.webp';
|
||||
const FALLBACK_COVER_URL = '/images/lost-image.webp';
|
||||
|
||||
export async function getArticle(req: Request, res: Response) : Promise<void> {
|
||||
try {
|
||||
@ -368,6 +369,69 @@ export async function deleteArticle(req: AuthRequest, res: Response) : Promise<v
|
||||
}
|
||||
}
|
||||
|
||||
export async function imageExists(url: string): Promise<boolean> {
|
||||
// --- Попытка HEAD (не бросаем исключение при 4xx/5xx) ---
|
||||
try {
|
||||
const headRes = await axios.head(url, {
|
||||
timeout: 5000,
|
||||
validateStatus: () => true, // всегда возвращаем ответ, не бросаем
|
||||
});
|
||||
|
||||
const status = headRes.status;
|
||||
const contentType = headRes.headers['content-type'] ?? '';
|
||||
|
||||
// Если HEAD вернул успешный статус и content-type явно image -> ok
|
||||
if (status >= 200 && status < 300 && contentType.startsWith('image/')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Если HEAD вернул 2xx, но content-type нет/не image -> попробуем GET (возможно сервер не указывает CT в HEAD)
|
||||
const shouldTryGetOnHead =
|
||||
(status >= 200 && status < 300) ||
|
||||
status === 403 ||
|
||||
status === 404 ||
|
||||
status === 405 ||
|
||||
status === 501 ||
|
||||
!status;
|
||||
|
||||
if (!shouldTryGetOnHead) {
|
||||
// Например 500/502/401 и т.п. — считаем, что изображение недоступно
|
||||
return false;
|
||||
}
|
||||
} catch (headErr) {
|
||||
// В случаe сетевой ошибки или таймаута — попробуем GET ниже
|
||||
// (не выкидываем ошибку)
|
||||
}
|
||||
|
||||
// --- HEAD не дал уверенного ответа — пробуем GET (stream) ---
|
||||
try {
|
||||
const getRes = await axios.get(url, {
|
||||
responseType: 'stream',
|
||||
timeout: 7000,
|
||||
validateStatus: () => true, // чтобы не бросало исключение на 4xx/5xx
|
||||
});
|
||||
|
||||
const status = getRes.status;
|
||||
const contentType = (getRes.headers && getRes.headers['content-type']) || '';
|
||||
|
||||
// Если статус 2xx и content-type image -> ok
|
||||
const ok = status >= 200 && status < 300 && contentType.startsWith('image/');
|
||||
|
||||
// Закрываем поток — нам достаточно заголовков
|
||||
try {
|
||||
if (getRes.data && typeof getRes.data.destroy === 'function') {
|
||||
getRes.data.destroy();
|
||||
}
|
||||
} catch (_) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
return !!ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function importArticles(req: AuthRequest, res: Response) : Promise<void> {
|
||||
const articles: Article[] = req.body;
|
||||
|
||||
@ -448,38 +512,53 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
||||
const imageUrl = article.coverImage;
|
||||
|
||||
try {
|
||||
// Извлечение изображение в виде буфера массива
|
||||
const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer' });
|
||||
// Проверяем, существует ли картинка по URL
|
||||
const exists = await imageExists(imageUrl);
|
||||
|
||||
// Создание буфера из данных ответа
|
||||
const imageBuffer = Buffer.from(imageResponse.data, 'binary');
|
||||
if (exists) {
|
||||
// Извлечение изображение в виде буфера массива
|
||||
const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer' });
|
||||
|
||||
// Получить тип контента и расширение
|
||||
const contentType = imageResponse.headers['content-type'];
|
||||
const fileExtension = path.extname(new URL(imageUrl).pathname) ||
|
||||
(contentType.includes('image/') ? `.${contentType.split('/')[1]}` : '');
|
||||
// Создание буфера из данных ответа
|
||||
const imageBuffer = Buffer.from(imageResponse.data, 'binary');
|
||||
|
||||
// Генерация имени файла
|
||||
const fileName = `cover-${Date.now()}${fileExtension}`;
|
||||
const webpFileName = path.basename(fileName, path.extname(fileName)) + '.webp';
|
||||
// Получить тип контента и расширение
|
||||
const contentType = imageResponse.headers['content-type'];
|
||||
const fileExtension = path.extname(new URL(imageUrl).pathname) ||
|
||||
(contentType.includes('image/') ? `.${contentType.split('/')[1]}` : '');
|
||||
|
||||
// Загрузка буфера в S3
|
||||
const uploadedUrl = await uploadBufferToS3(
|
||||
imageBuffer,
|
||||
folder,
|
||||
webpFileName,
|
||||
contentType,
|
||||
'medium',
|
||||
80
|
||||
);
|
||||
// Генерация имени файла
|
||||
const fileName = `cover-${Date.now()}${fileExtension}`;
|
||||
const webpFileName = path.basename(fileName, path.extname(fileName)) + '.webp';
|
||||
|
||||
// Добавить обложку к статье
|
||||
await prisma.article.update({
|
||||
where: { id: newArticle.id },
|
||||
data: {
|
||||
coverImage: uploadedUrl
|
||||
},
|
||||
});
|
||||
// Загрузка буфера в S3
|
||||
const uploadedUrl = await uploadBufferToS3(
|
||||
imageBuffer,
|
||||
folder,
|
||||
webpFileName,
|
||||
contentType,
|
||||
'medium',
|
||||
80
|
||||
);
|
||||
|
||||
// Добавить обложку к статье
|
||||
await prisma.article.update({
|
||||
where: { id: newArticle.id },
|
||||
data: {
|
||||
coverImage: uploadedUrl
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.warn(`Изображение coverImage отсутствует, устанавливаю fallback: ${imageUrl}`);
|
||||
|
||||
await prisma.article.update({
|
||||
where: { id: newArticle.id },
|
||||
data: {
|
||||
coverImage: FALLBACK_COVER_URL
|
||||
},
|
||||
});
|
||||
}
|
||||
} catch (imageError) {
|
||||
console.error(`Ошибка загрузки coverImage для статьи ${article.id}:`, imageError);
|
||||
}
|
||||
@ -496,23 +575,31 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
||||
for (let i = 0; i < article.images.length; i++) {
|
||||
const imageUrl = article.images[i];
|
||||
try {
|
||||
const imageResponse = await axios.get(imageUrl, {responseType: 'arraybuffer'});
|
||||
const imageBuffer = Buffer.from(imageResponse.data, 'binary');
|
||||
const contentType = imageResponse.headers['content-type'];
|
||||
const fileExtension = path.extname(new URL(imageUrl).pathname) ||
|
||||
(contentType.includes('image/') ? `.${contentType.split('/')[1]}` : '');
|
||||
const fileName = `image-${Date.now()}-${i}${fileExtension}`;
|
||||
const webpFileName = path.basename(fileName, path.extname(fileName)) + '.webp';
|
||||
// Проверяем, существует ли картинка по URL
|
||||
const exists = await imageExists(imageUrl);
|
||||
|
||||
const uploadedUrl = await uploadBufferToS3(
|
||||
imageBuffer,
|
||||
folder,
|
||||
webpFileName,
|
||||
contentType,
|
||||
'medium',
|
||||
80
|
||||
);
|
||||
uploadedImageUrls.push(uploadedUrl);
|
||||
if (exists) {
|
||||
const imageResponse = await axios.get(imageUrl, {responseType: 'arraybuffer'});
|
||||
const imageBuffer = Buffer.from(imageResponse.data, 'binary');
|
||||
const contentType = imageResponse.headers['content-type'];
|
||||
const fileExtension = path.extname(new URL(imageUrl).pathname) ||
|
||||
(contentType.includes('image/') ? `.${contentType.split('/')[1]}` : '');
|
||||
const fileName = `image-${Date.now()}-${i}${fileExtension}`;
|
||||
const webpFileName = path.basename(fileName, path.extname(fileName)) + '.webp';
|
||||
|
||||
const uploadedUrl = await uploadBufferToS3(
|
||||
imageBuffer,
|
||||
folder,
|
||||
webpFileName,
|
||||
contentType,
|
||||
'medium',
|
||||
80
|
||||
);
|
||||
uploadedImageUrls.push(uploadedUrl);
|
||||
}
|
||||
else {
|
||||
uploadedImageUrls.push(FALLBACK_COVER_URL);
|
||||
}
|
||||
} catch (imageError) {
|
||||
console.error(`Ошибка загрузки изображения ${imageUrl} для статьи ${newArticle.id}:`, imageError);
|
||||
uploadedImageUrls.push(''); // Добавляем пустую строку, чтобы сохранить порядок
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user