Работа с галереей и сохранением в S3.

This commit is contained in:
anibilag 2025-03-25 22:34:46 +03:00
parent ec10d76705
commit 584b2bc614
6 changed files with 75 additions and 53 deletions

View File

@ -336,7 +336,6 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
}
}
importedCount++;
} catch (articleError) {
console.error(`Ошибка импорта статьи ${article.id}:`, articleError);

View File

@ -21,8 +21,8 @@ export async function createGalleryImage(req: AuthRequest, res: Response) {
res.status(201).json(image);
} catch (error) {
logger.error('Error creating gallery image:', error);
res.status(500).json({ error: 'Failed to create gallery image' });
logger.error('Ошибка создания изображения в галерее:', error);
res.status(500).json({ error: 'Сбой создания изображения в галерее' });
}
}

View File

@ -17,7 +17,7 @@ export const handleFileUpload = async (req: Request, res: Response): Promise<voi
const quality = Number(req.body.quality) || 80;
try {
// Upload file to S3
// Загрузка файлв в S3
const fileUrl = await uploadFile(folder, file, resolutionId, quality);
res.json({ message: 'Файл успешно загружен', fileUrl });
@ -28,7 +28,7 @@ export const handleFileUpload = async (req: Request, res: Response): Promise<voi
}
};
// Handle URL to image upload
// Обработка загрузки файла в S3 по URL изображения
export const handleUrlToImageUpload = async (req: Request, res: Response): Promise<void> => {
const { imageUrl, folder, resolutionId, quality, filename } = req.body;
@ -38,11 +38,11 @@ export const handleUrlToImageUpload = async (req: Request, res: Response): Promi
}
try {
// Fetch the image
// Получение изображения по URL в буфер
const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer' });
const imageBuffer = Buffer.from(imageResponse.data, 'binary');
// Upload to S3
// загрузка в S3
const fileUrl = await uploadBufferToS3(
imageBuffer,
folder || 'images',

View File

@ -1,16 +1,16 @@
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import {User, UserPermissions} from '../types/auth';
import { User, UserPermissions } from '../types/auth';
import { logger } from '../config/logger';
import {JsonValue} from "@prisma/client/runtime/library";
import { JsonValue } from "@prisma/client/runtime/library";
const prisma = new PrismaClient();
export const authService = {
login: async (email: string, password: string) => {
try {
logger.info(`Login attempt for user: ${email}`);
logger.info(`Попытка входа пользователя: ${email}`);
const user : {
email: string;
@ -32,14 +32,14 @@ export const authService = {
});
if (!user) {
logger.warn(`Login failed: User not found - ${email}`);
throw new Error('Invalid credentials');
logger.warn(`Сбой входа: Пользователь не найден - ${email}`);
throw new Error('Недопустивые данные пользователя');
}
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
logger.warn(`Login failed: Invalid password for user - ${email}`);
throw new Error('Invalid credentials');
logger.warn(`Сбой входа: Неправильный пароль пользователя - ${email}`);
throw new Error('Недопустивые данные пользователя');
}
const token = await authService.generateToken(user.id);
@ -47,14 +47,14 @@ export const authService = {
const permissions = user.permissions as unknown;
logger.info(`User logged in successfully: ${email}`);
logger.info(`Пользователь успешно зашел: ${email}`);
return {
user: {...userWithoutPassword,
permissions: permissions as UserPermissions } as User,
token
};
} catch (error) {
logger.error('Login error:', error);
logger.error('Ошибка входа:', error);
throw error;
}
},
@ -66,10 +66,10 @@ export const authService = {
process.env.JWT_SECRET || '',
{ expiresIn: '24h' }
);
logger.debug(`Generated token for user: ${userId}`);
logger.debug(`Создан токен для пользователя: ${userId}`);
return token;
} catch (error) {
logger.error('Token generation error:', error);
logger.error('Ошибка создания токена:', error);
throw error;
}
},
@ -82,7 +82,7 @@ export const authService = {
permissions: any;
}) => {
try {
logger.info(`Creating new user: ${userData.email}`);
logger.info(`Создание нового пользователя: ${userData.email}`);
const hashedPassword = await bcrypt.hash(userData.password, 10);
const user : {
@ -105,14 +105,14 @@ export const authService = {
}
});
logger.info(`User created successfully: ${userData.email}`);
logger.info(`Пользователь успешно создан: ${userData.email}`);
const permissions = user.permissions as unknown;
return {...user,
permissions: permissions as UserPermissions } as User
} catch (error) {
logger.error('User creation error:', error);
logger.error('Ошибка создания пользователя:', error);
throw error;
}
}

View File

@ -1,8 +1,9 @@
import {PrismaClient} from '@prisma/client';
import {logger} from '../config/logger';
import { PrismaClient } from '@prisma/client';
import { logger } from '../config/logger';
const prisma = new PrismaClient();
export const galleryService = {
createImage: async (data: {
url: string;
@ -16,13 +17,25 @@ export const galleryService = {
order?: number;
}) => {
try {
const image = await prisma.galleryImage.create({
data
// Находим максимальное значение order для данного articleId
const maxOrder = await prisma.galleryImage.aggregate({
where: { articleId: data.articleId },
_max: { order: true },
});
logger.info(`Created gallery image: ${image.id}`);
// Устанавливаем order как максимальное значение + 1, или 0 если записей еще нет
const newOrder = maxOrder._max.order !== null ? maxOrder._max.order + 1 : 0;
const image = await prisma.galleryImage.create({
data: {
...data,
order: newOrder
}
});
logger.info(`Создано изображение в галерее: ${image.id}`);
return image;
} catch (error) {
logger.error('Error creating gallery image:', error);
logger.error('Сбой создания изображения в галерее:', error);
throw error;
}
},
@ -40,10 +53,10 @@ export const galleryService = {
where: { id },
data
});
logger.info(`Updated gallery image: ${id}`);
logger.info(`Изменено изображение в галерее: ${id}`);
return image;
} catch (error) {
logger.error(`Error updating gallery image ${id}:`, error);
logger.error(`Сбой изменения изображения в галерее: ${id}:`, error);
throw error;
}
},
@ -53,9 +66,9 @@ export const galleryService = {
await prisma.galleryImage.delete({
where: { id }
});
logger.info(`Deleted gallery image: ${id}`);
logger.info(`Удалено изображение из галереи: ${id}`);
} catch (error) {
logger.error(`Error deleting gallery image ${id}:`, error);
logger.error(`Сбой удаления изображения из галереи ${id}:`, error);
throw error;
}
},
@ -70,9 +83,9 @@ export const galleryService = {
})
)
);
logger.info(`Reordered gallery images for article: ${articleId}`);
logger.info(`Изменен порядок расположения изображений в галерее для статьи: ${articleId}`);
} catch (error) {
logger.error(`Error reordering gallery images for article ${articleId}:`, error);
logger.error(`Сбой изменения порядка расположения изображений в галерее для статьи ${articleId}:`, error);
throw error;
}
},
@ -84,7 +97,7 @@ export const galleryService = {
orderBy: {order: 'asc'}
});
} catch (error) {
logger.error(`Error fetching gallery for article ${articleId}:`, error);
logger.error(`Ошибка получения изображений галереи для статьи: ${articleId}:`, error);
throw error;
}
}

View File

@ -23,7 +23,7 @@ export const s3Client = new S3Client({
},
});
// Export bucket name
// Наименование бакета
export const bucketName = BUCKET_NAME;
// Функция для изменения размеров и оптимизации изображения
@ -52,7 +52,7 @@ const getMulterS3Config = (
}).single('file'); // Обрабатываем один файл
};
// Default multer upload middleware using the default S3 client
// Middleware по умолчанию, использующее дефолтовый S3 клиент
export const multerUpload = getMulterS3Config(s3Client, bucketName);
// Функция для загрузки файла в S3 после оптимизации
@ -102,14 +102,14 @@ export const uploadToS3 = async (
};
/**
* Function to upload a buffer to S3
* @param buffer - The buffer to upload
* @param folder - The folder path in S3
* @param filename - Optional filename (if not provided, a UUID will be generated)
* @param mimetype - The MIME type of the buffer content
* @param resolutionId - Optional resolution identifier
* @param quality - Optional quality setting for image optimization
* @returns URL of the uploaded file
* Функция для загрузки буфера в S3
* @param buffer - Буфер для загрузки
* @param folder - Путь к папке в S3
* @param filename - Опциональное имя файла (если пусто, то будет сгенерирован UUID как имя)
* @param mimetype - MIME тип контента буфера
* @param resolutionId - Опциональный идентификатор разрешения изображения
* @param quality - Отцтональное значение качества при оптимизации
* @returns URL загрущенного файла
*/
export const uploadBufferToS3 = async (
buffer: Buffer,
@ -120,26 +120,36 @@ export const uploadBufferToS3 = async (
quality: number = 80
): Promise<string> => {
try {
// Generate filename if not provided
// Генерация имени файла, если оно не задано
const finalFilename = filename || `${uuidv4()}${mimetype ? getExtensionFromMimeType(mimetype) : '.bin'}`;
// Create the S3 key (path)
// Создание ключа S3 (путь в бакете)
const key = folder ? `${folder}/${finalFilename}` : finalFilename;
// Here you could add image optimization for the buffer if needed
// For example, using Sharp library to resize/compress the image
const selectedResolution = imageResolutions.find(r => r.id === resolutionId);
if (!selectedResolution) {
throw new Error('Недопустимое разрешение изображения');
}
// Upload buffer to S3
// Оптимизируем изображение
const optimizedBuffer = await resizeAndOptimizeImage(
buffer,
selectedResolution.width,
selectedResolution.height,
quality
);
// Загрузка буфера в S3
await s3Client.send(
new PutObjectCommand({
Bucket: bucketName,
Key: key,
Body: buffer,
Body: optimizedBuffer,
ContentType: mimetype,
})
);
// Return the file URL
// Возвращаем URL файла
const fileUrl = `https://${bucketName}.s3.regru.cloud/${finalFilename}`;
logger.info(`Buffer successfully uploaded to S3: ${key}`);
return fileUrl;
@ -150,7 +160,7 @@ export const uploadBufferToS3 = async (
}
};
// Helper function to get file extension from MIME type
// Функция получения пасширения файла из MIME типа
function getExtensionFromMimeType(mimetype: string): string {
const mimeToExt: Record<string, string> = {
'image/jpeg': '.jpg',
@ -167,7 +177,7 @@ function getExtensionFromMimeType(mimetype: string): string {
return mimeToExt[mimetype] || '';
}
// Simplified version of uploadToS3 that uses the default client and bucket
// Упрощенная версия uploadToS3 которая использует клиента и бакет по умолчанию
export const uploadFile = async (
folder: string,
file: Express.MulterS3.File,