Добавлена обработка импорта статей, пока без загрузки обложки в хранилище S3
This commit is contained in:
parent
9e6276ace9
commit
3b1b702b00
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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' });
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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' });
|
||||
}
|
||||
}
|
@ -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' });
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user