Добавлена обработка импорта статей, пока без загрузки обложки в хранилище S3

This commit is contained in:
anibilag 2025-02-25 13:41:20 +03:00
parent 9e6276ace9
commit 3b1b702b00
7 changed files with 98 additions and 213 deletions

View File

@ -3,6 +3,9 @@ import { prisma } from '../../../lib/prisma';
import { AuthRequest } from '../../../middleware/auth';
import { checkPermission } from '../../../utils/permissions';
import { logger } from '../../../config/logger';
import { Article } from "../../../types";
const DEFAULT_COVER_IMAGE = 'https://images.unsplash.com/photo-1460661419201-fd4cecdf8a8b?auto=format&fit=crop&q=80&w=2070';
export async function getArticle(req: Request, res: Response) : Promise<void> {
try {
@ -38,20 +41,20 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
const { title, excerpt, content, categoryId, cityId, coverImage, readTime } = req.body;
if (!req.user) {
logger.warn('Unauthorized article creation attempt');
res.status(401).json({ error: 'Not authenticated' });
logger.warn('Пользователь не вошел в систему');
res.status(401).json({ error: 'Пользователь не аутентифицирован' });
return
}
if (!categoryId || categoryId < 1 || categoryId > 8) {
logger.warn(`Invalid category ID: ${categoryId}`);
res.status(400).json({ error: 'Invalid category' });
logger.warn(`Недопустимый ID категории: ${categoryId}`);
res.status(400).json({ error: 'Недопустимая категория' });
return
}
if (!checkPermission(req.user, categoryId, 'create')) {
logger.warn(`Permission denied for user ${req.user.id} to create article in category ${categoryId}`);
res.status(403).json({ error: 'Permission denied' });
logger.warn(`Не разрешено для пользователя ${req.user.id} создавать статьи в категории ${categoryId}`);
res.status(403).json({ error: 'В разрешении отказано' });
return
}
@ -77,11 +80,11 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
}
});
logger.info(`Article created: ${article.id} by user ${req.user.id}`);
logger.info(`Создана статья: ${article.id} пользователем ${req.user.id}`);
res.status(201).json(article);
} catch (error) {
logger.error('Error creating article:', error);
res.status(500).json({ error: 'Server error' });
logger.error('Ошибка создания статьи:', error);
res.status(500).json({ error: 'Серверная ошибка' });
}
}
@ -104,7 +107,7 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
}
if (!checkPermission(req.user, categoryId, 'edit')) {
res.status(403).json({ error: 'Нет прав на выполнение этой операции' });
res.status(403).json({ error: 'В разрешении отказано' });
return
}
@ -132,15 +135,15 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
res.json(updatedArticle);
} catch (error) {
logger.error('Error updating article:', error);
res.status(500).json({ error: 'Server error' });
logger.error('Ошибка редактирования статьи:', error);
res.status(500).json({ error: 'Серверная ошибка' });
}
}
export async function deleteArticle(req: AuthRequest, res: Response) : Promise<void> {
try {
if (!req.user) {
res.status(401).json({ error: 'Not authenticated' });
res.status(401).json({ error: 'Пользователь не вошел в систему' });
return
}
@ -149,22 +152,75 @@ export async function deleteArticle(req: AuthRequest, res: Response) : Promise<v
});
if (!article) {
res.status(404).json({ error: 'Article not found' });
res.status(404).json({ error: 'Статья не найдена' });
return
}
// if (!checkPermission(req.user, article.category, 'delete')) {
// res.status(403).json({ error: 'Permission denied' });
// return
// }
if (!checkPermission(req.user, article.categoryId.toString(), 'delete')) {
res.status(403).json({ error: 'В разрешении отказано' });
return
}
await prisma.article.delete({
where: { id: req.params.id }
});
res.json({ message: 'Article deleted successfully' });
res.json({ message: 'Статья успешно удалена' });
} catch (error) {
logger.error('Error deleting article:', error);
res.status(500).json({ error: 'Server error' });
logger.error('Ошибка удаления статьи:', error);
res.status(500).json({ error: 'Серверная ошибка' });
}
}
}
export async function importArticles(req: AuthRequest, res: Response) : Promise<void> {
const articles: Article[] = req.body;
if (!Array.isArray(articles) || articles.length === 0) {
res.status(400).json({ message: 'Ожидается непустой массив статей в теле запроса' });
return
}
let importedCount = 0;
try {
for (const article of articles) {
try {
// Шаг 1: Создание статьи
const newArticle = await prisma.article.create({
data: {
title: article.title,
excerpt: article.excerpt,
content: article.content,
categoryId: article.categoryId,
cityId: article.cityId,
coverImage: DEFAULT_COVER_IMAGE,
readTime: article.readTime,
publishedAt: new Date(article.publishedAt),
likes: article.likes || 0,
dislikes: article.dislikes || 0,
author: {
connect: { id: article.author.id },
},
},
});
// Шаг 2: Обработка coverImage
importedCount++;
} catch (articleError) {
console.error(`Ошибка импорта статьи ${article.id}:`, articleError);
}
}
res.status(200).json({
message: `Импорт завершен. Успешно импортировано ${importedCount} из ${articles.length} статей.`,
});
} catch (err) {
console.error('Ошибка обработки запроса:', err);
res.status(500).json({ message: 'Ошибка сервера при импорте' });
} finally {
await prisma.$disconnect();
}
}

View File

@ -2,23 +2,25 @@ import { Request, Response } from 'express';
import { prisma } from '../../../lib/prisma';
import { Prisma } from '@prisma/client';
export async function listArticles(req: Request, res: Response) {
try {
const { page = 1, categoryId, cityId } = req.query;
let page = Number(req.query.page);
if (!Number.isInteger(page) || page < 1) page = 1; // Гарантируем, что `page` - корректное число
const perPage = 6;
// Преобразование и проверка параметров
const catId = Number(categoryId);
const citId = Number(cityId);
// Преобразование и проверка categoryId и cityId
const catId = Number(req.query.categoryId);
const citId = Number(req.query.cityId);
// Проверка и преобразование параметров
const where: Prisma.ArticleWhereInput = {
...(categoryId && !Number.isNaN(catId) && catId !== 0 && { categoryId: catId }),
...(cityId && !Number.isNaN(citId) && citId !== 0 && { cityId: citId }),
...(Number.isInteger(catId) && catId > 0 ? { categoryId: catId } : {}),
...(Number.isInteger(citId) && citId > 0 ? { cityId: citId } : {}),
};
// Рассчитываем пропуск записей для пагинации
const skip = (Number(page) - 1) * perPage;
// Рассчитываем пропуск записей
const skip = (page - 1) * perPage;
// Выполняем два параллельных запроса: получение статей и подсчёт общего количества
const [articles, total] = await Promise.all([
@ -45,7 +47,7 @@ export async function listArticles(req: Request, res: Response) {
res.json({
articles,
totalPages: Math.ceil(total / perPage),
currentPage: Number(page),
currentPage: page,
});
} catch (error) {
// Логируем ошибку и отправляем ответ с кодом 500

View File

@ -1,93 +0,0 @@
import { Request, Response } from 'express';
import { prisma } from '../../lib/prisma';
export async function getArticle(req: Request, res: Response) {
try {
const article = await prisma.article.findUnique({
where: { id: req.params.id },
include: {
author: {
select: {
id: true,
displayName: true,
email: true
}
}
}
});
if (!article) {
return res.status(404).json({ error: 'Article not found' });
}
res.json(article);
} catch {
res.status(500).json({ error: 'Server error' });
}
}
export async function createArticle(req: Request, res: Response) {
try {
const { title, excerpt, content, category, city, coverImage, readTime } = req.body;
const article = await prisma.article.create({
data: {
title,
excerpt,
content,
category,
city,
coverImage,
readTime,
authorId: req.user!.id
},
include: {
author: {
select: {
id: true,
displayName: true,
email: true
}
}
}
});
res.status(201).json(article);
} catch {
res.status(500).json({ error: 'Server error' });
}
}
export async function updateArticle(req: Request, res: Response) {
try {
const article = await prisma.article.update({
where: { id: req.params.id },
data: req.body,
include: {
author: {
select: {
id: true,
displayName: true,
email: true
}
}
}
});
res.json(article);
} catch {
res.status(500).json({ error: 'Server error' });
}
}
export async function deleteArticle(req: Request, res: Response) {
try {
await prisma.article.delete({
where: { id: req.params.id }
});
res.json({ message: 'Article deleted successfully' });
} catch {
res.status(500).json({ error: 'Server error' });
}
}

View File

@ -2,14 +2,19 @@ import express from 'express';
import { auth } from '../../middleware/auth';
import { searchArticles } from './controllers/search';
import { listArticles } from './controllers/list';
import { getArticle, createArticle, updateArticle, deleteArticle } from './controllers/crud';
import { getArticle, createArticle, updateArticle, deleteArticle, importArticles } from './controllers/crud';
const router = express.Router();
// Search and list routes
// Поиск и список routes
router.get('/search', searchArticles);
router.get('/', listArticles);
// Импорт route
// Настройка middleware для обработки JSON
router.use(express.json({ limit: '10mb' }));
router.post('/import', auth, importArticles);
// CRUD routes
router.get('/:id', getArticle);
router.post('/', auth, createArticle);

View File

@ -1,41 +0,0 @@
import { Request, Response } from 'express';
import { prisma } from '../../lib/prisma';
export async function listArticles(req: Request, res: Response) {
try {
const { page = 1, category, city } = req.query;
const perPage = 6;
const where = {
...(category && { category: category as string }),
...(city && { city: city as string })
};
const [articles, total] = await Promise.all([
prisma.article.findMany({
where,
include: {
author: {
select: {
id: true,
displayName: true,
email: true
}
}
},
skip: ((page as number) - 1) * perPage,
take: perPage,
orderBy: { publishedAt: 'desc' }
}),
prisma.article.count({ where })
]);
res.json({
articles,
totalPages: Math.ceil(total / perPage),
currentPage: parseInt(page as string)
});
} catch {
res.status(500).json({ error: 'Server error' });
}
}

View File

@ -1,44 +0,0 @@
import { Request, Response } from 'express';
import { prisma } from '../../lib/prisma';
export async function searchArticles(req: Request, res: Response) {
try {
const { q, page = 1, limit = 9 } = req.query;
const skip = ((page as number) - 1) * (limit as number);
const where = {
OR: [
{ title: { contains: q as string, mode: 'insensitive' } },
{ excerpt: { contains: q as string, mode: 'insensitive' } },
{ content: { contains: q as string, mode: 'insensitive' } },
]
};
const [articles, total] = await Promise.all([
prisma.article.findMany({
where,
include: {
author: {
select: {
id: true,
displayName: true,
email: true
}
}
},
skip,
take: parseInt(limit as string),
orderBy: { publishedAt: 'desc' }
}),
prisma.article.count({ where })
]);
res.json({
articles,
totalPages: Math.ceil(total / (limit as number)),
currentPage: parseInt(page as string)
});
} catch {
res.status(500).json({ error: 'Server error' });
}
}

View File

@ -1,6 +1,6 @@
import express from 'express';
import { auth } from '../../middleware/auth';
import {createS3Client, getMulterS3Config, uploadToS3} from '../../services/s3Service';
import { createS3Client, getMulterS3Config, uploadToS3 } from '../../services/s3Service';
import { logger } from '../../config/logger';
const router = express.Router();