Update Next.js configuration for S3 support and enhance admin dashboard functionality - Added S3 hostname to next.config.js for image uploads - Updated package.json and package-lock.json with AWS SDK dependencies - Improved admin layout with S3 status component and enhanced dashboard statistics loading logic - Refactored news loading in NewsBlock component to handle errors gracefully.
This commit is contained in:
36
app/api/health/route.ts
Normal file
36
app/api/health/route.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Проверяем подключение к базе данных
|
||||
await prisma.$connect();
|
||||
|
||||
// Проверяем, что можем выполнить запрос
|
||||
const newsCount = await prisma.news.count();
|
||||
const userCount = await prisma.user.count();
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
status: 'healthy',
|
||||
database: 'connected',
|
||||
data: {
|
||||
newsCount,
|
||||
userCount,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Health check failed:', error);
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
status: 'unhealthy',
|
||||
database: 'disconnected',
|
||||
error: error instanceof Error ? error.message : 'Unknown error'
|
||||
}, { status: 500 });
|
||||
} finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
}
|
@ -13,7 +13,8 @@ export async function GET(request: NextRequest) {
|
||||
const search = searchParams.get('search');
|
||||
const slug = searchParams.get('slug');
|
||||
const featured = searchParams.get('featured') === 'true';
|
||||
const published = searchParams.get('published') !== 'false';
|
||||
const publishedParam = searchParams.get('published');
|
||||
const published = publishedParam === 'all' ? undefined : publishedParam !== 'false';
|
||||
const sortBy = searchParams.get('sortBy') || 'publishedAt';
|
||||
const sortOrder = searchParams.get('sortOrder') || 'desc';
|
||||
|
||||
|
82
app/api/test-s3/route.ts
Normal file
82
app/api/test-s3/route.ts
Normal file
@ -0,0 +1,82 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { S3Client, PutObjectCommand, DeleteObjectCommand } from '@aws-sdk/client-s3';
|
||||
|
||||
export async function GET() {
|
||||
const s3Client = new S3Client({
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
region: process.env.S3_REGION || 'ru-1',
|
||||
credentials: {
|
||||
accessKeyId: process.env.S3_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
forcePathStyle: true,
|
||||
});
|
||||
|
||||
const bucketName = process.env.S3_BUCKET_NAME!;
|
||||
const testKey = `test/health-check-${Date.now()}.txt`;
|
||||
|
||||
try {
|
||||
// Шаг 1: Проверяем переменные окружения
|
||||
if (!process.env.S3_ENDPOINT) {
|
||||
throw new Error('S3_ENDPOINT не настроен');
|
||||
}
|
||||
if (!process.env.S3_BUCKET_NAME) {
|
||||
throw new Error('S3_BUCKET_NAME не настроен');
|
||||
}
|
||||
if (!process.env.S3_ACCESS_KEY_ID) {
|
||||
throw new Error('S3_ACCESS_KEY_ID не настроен');
|
||||
}
|
||||
if (!process.env.S3_SECRET_ACCESS_KEY) {
|
||||
throw new Error('S3_SECRET_ACCESS_KEY не настроен');
|
||||
}
|
||||
|
||||
// Шаг 2: Пробуем загрузить тестовый файл
|
||||
const testData = Buffer.from('S3 health check test');
|
||||
const putCommand = new PutObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: testKey,
|
||||
Body: testData,
|
||||
ContentType: 'text/plain',
|
||||
});
|
||||
|
||||
const uploadResult = await s3Client.send(putCommand);
|
||||
|
||||
// Шаг 3: Пробуем удалить тестовый файл
|
||||
const deleteCommand = new DeleteObjectCommand({
|
||||
Bucket: bucketName,
|
||||
Key: testKey,
|
||||
});
|
||||
|
||||
await s3Client.send(deleteCommand);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'S3 подключение работает нормально',
|
||||
details: {
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
bucket: bucketName,
|
||||
region: process.env.S3_REGION,
|
||||
uploadResult: {
|
||||
httpStatusCode: uploadResult.$metadata.httpStatusCode,
|
||||
etag: uploadResult.ETag,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('S3 Health Check Error:', error);
|
||||
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Неизвестная ошибка',
|
||||
details: {
|
||||
endpoint: process.env.S3_ENDPOINT,
|
||||
bucket: bucketName,
|
||||
region: process.env.S3_REGION,
|
||||
errorName: error instanceof Error ? error.name : 'Unknown',
|
||||
errorCode: (error as any)?.code,
|
||||
errorStatusCode: (error as any)?.$metadata?.httpStatusCode,
|
||||
}
|
||||
}, { status: 500 });
|
||||
}
|
||||
}
|
122
app/api/upload/route.ts
Normal file
122
app/api/upload/route.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { uploadFileToS3, deleteFileFromS3, extractKeyFromUrl } from '@/lib/s3';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
console.log('API Upload: Получен запрос POST');
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const file = formData.get('file') as File;
|
||||
const folder = formData.get('folder') as string || 'uploads';
|
||||
const oldUrl = formData.get('oldUrl') as string;
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Файл не найден' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем размер файла (максимум 10MB)
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
if (file.size > maxSize) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Файл слишком большой. Максимальный размер: 10MB' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Проверяем тип файла
|
||||
const allowedTypes = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
'image/svg+xml',
|
||||
'application/pdf',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'text/plain', // Для тестирования S3Status
|
||||
];
|
||||
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Неподдерживаемый тип файла' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Если есть старый файл, удаляем его
|
||||
if (oldUrl) {
|
||||
const oldKey = extractKeyFromUrl(oldUrl);
|
||||
if (oldKey) {
|
||||
try {
|
||||
await deleteFileFromS3(oldKey);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при удалении старого файла:', error);
|
||||
// Не прерываем процесс, если не удалось удалить старый файл
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Конвертируем файл в Buffer
|
||||
const bytes = await file.arrayBuffer();
|
||||
const buffer = Buffer.from(bytes);
|
||||
|
||||
// Загружаем файл в S3
|
||||
const result = await uploadFileToS3(
|
||||
buffer,
|
||||
file.type,
|
||||
folder,
|
||||
file.name
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: result,
|
||||
message: 'Файл успешно загружен'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке файла:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка при загрузке файла' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url);
|
||||
const url = searchParams.get('url');
|
||||
|
||||
if (!url) {
|
||||
return NextResponse.json(
|
||||
{ error: 'URL файла не указан' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const key = extractKeyFromUrl(url);
|
||||
if (!key) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Некорректный URL файла' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
await deleteFileFromS3(key);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Файл успешно удален'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Ошибка при удалении файла:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Ошибка при удалении файла' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user