From 584b2bc614a0dbf4102af13ebb545afedfda6fa9 Mon Sep 17 00:00:00 2001 From: anibilag Date: Tue, 25 Mar 2025 22:34:46 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81?= =?UTF-8?q?=20=D0=B3=D0=B0=D0=BB=D0=B5=D1=80=D0=B5=D0=B5=D0=B9=20=D0=B8=20?= =?UTF-8?q?=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=B2=20S3.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/articles/controllers/crud.ts | 1 - src/routes/gallery/controllers/crud.ts | 4 +-- src/routes/images/controllers/upload.ts | 8 ++--- src/services/authService.ts | 28 +++++++-------- src/services/galleryService.ts | 39 +++++++++++++------- src/services/s3Service.ts | 48 +++++++++++++++---------- 6 files changed, 75 insertions(+), 53 deletions(-) diff --git a/src/routes/articles/controllers/crud.ts b/src/routes/articles/controllers/crud.ts index f3f1822..8ba2ab9 100644 --- a/src/routes/articles/controllers/crud.ts +++ b/src/routes/articles/controllers/crud.ts @@ -336,7 +336,6 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise< } } - importedCount++; } catch (articleError) { console.error(`Ошибка импорта статьи ${article.id}:`, articleError); diff --git a/src/routes/gallery/controllers/crud.ts b/src/routes/gallery/controllers/crud.ts index 4d83d6a..6d24345 100644 --- a/src/routes/gallery/controllers/crud.ts +++ b/src/routes/gallery/controllers/crud.ts @@ -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: 'Сбой создания изображения в галерее' }); } } diff --git a/src/routes/images/controllers/upload.ts b/src/routes/images/controllers/upload.ts index 219b386..0e44e3f 100644 --- a/src/routes/images/controllers/upload.ts +++ b/src/routes/images/controllers/upload.ts @@ -17,7 +17,7 @@ export const handleFileUpload = async (req: Request, res: Response): Promise => { 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', diff --git a/src/services/authService.ts b/src/services/authService.ts index 5d58ce2..a67639c 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -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; } } diff --git a/src/services/galleryService.ts b/src/services/galleryService.ts index 574be82..96f94e0 100644 --- a/src/services/galleryService.ts +++ b/src/services/galleryService.ts @@ -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; } } diff --git a/src/services/s3Service.ts b/src/services/s3Service.ts index a0c5d08..b6b7af5 100644 --- a/src/services/s3Service.ts +++ b/src/services/s3Service.ts @@ -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 => { 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 = { '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,