Добавлена обработка импорта статей, пока без загрузки обложки в хранилище 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 { AuthRequest } from '../../../middleware/auth';
|
||||||
import { checkPermission } from '../../../utils/permissions';
|
import { checkPermission } from '../../../utils/permissions';
|
||||||
import { logger } from '../../../config/logger';
|
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> {
|
export async function getArticle(req: Request, res: Response) : Promise<void> {
|
||||||
try {
|
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;
|
const { title, excerpt, content, categoryId, cityId, coverImage, readTime } = req.body;
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
logger.warn('Unauthorized article creation attempt');
|
logger.warn('Пользователь не вошел в систему');
|
||||||
res.status(401).json({ error: 'Not authenticated' });
|
res.status(401).json({ error: 'Пользователь не аутентифицирован' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!categoryId || categoryId < 1 || categoryId > 8) {
|
if (!categoryId || categoryId < 1 || categoryId > 8) {
|
||||||
logger.warn(`Invalid category ID: ${categoryId}`);
|
logger.warn(`Недопустимый ID категории: ${categoryId}`);
|
||||||
res.status(400).json({ error: 'Invalid category' });
|
res.status(400).json({ error: 'Недопустимая категория' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkPermission(req.user, categoryId, 'create')) {
|
if (!checkPermission(req.user, categoryId, 'create')) {
|
||||||
logger.warn(`Permission denied for user ${req.user.id} to create article in category ${categoryId}`);
|
logger.warn(`Не разрешено для пользователя ${req.user.id} создавать статьи в категории ${categoryId}`);
|
||||||
res.status(403).json({ error: 'Permission denied' });
|
res.status(403).json({ error: 'В разрешении отказано' });
|
||||||
return
|
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);
|
res.status(201).json(article);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error creating article:', error);
|
logger.error('Ошибка создания статьи:', error);
|
||||||
res.status(500).json({ error: 'Server 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')) {
|
if (!checkPermission(req.user, categoryId, 'edit')) {
|
||||||
res.status(403).json({ error: 'Нет прав на выполнение этой операции' });
|
res.status(403).json({ error: 'В разрешении отказано' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,15 +135,15 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
|
|
||||||
res.json(updatedArticle);
|
res.json(updatedArticle);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error updating article:', error);
|
logger.error('Ошибка редактирования статьи:', error);
|
||||||
res.status(500).json({ error: 'Server error' });
|
res.status(500).json({ error: 'Серверная ошибка' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteArticle(req: AuthRequest, res: Response) : Promise<void> {
|
export async function deleteArticle(req: AuthRequest, res: Response) : Promise<void> {
|
||||||
try {
|
try {
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
res.status(401).json({ error: 'Not authenticated' });
|
res.status(401).json({ error: 'Пользователь не вошел в систему' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,22 +152,75 @@ export async function deleteArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!article) {
|
if (!article) {
|
||||||
res.status(404).json({ error: 'Article not found' });
|
res.status(404).json({ error: 'Статья не найдена' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (!checkPermission(req.user, article.category, 'delete')) {
|
if (!checkPermission(req.user, article.categoryId.toString(), 'delete')) {
|
||||||
// res.status(403).json({ error: 'Permission denied' });
|
res.status(403).json({ error: 'В разрешении отказано' });
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
|
|
||||||
await prisma.article.delete({
|
await prisma.article.delete({
|
||||||
where: { id: req.params.id }
|
where: { id: req.params.id }
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ message: 'Article deleted successfully' });
|
res.json({ message: 'Статья успешно удалена' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error deleting article:', error);
|
logger.error('Ошибка удаления статьи:', error);
|
||||||
res.status(500).json({ error: 'Server 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 '../../../lib/prisma';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
|
||||||
export async function listArticles(req: Request, res: Response) {
|
export async function listArticles(req: Request, res: Response) {
|
||||||
try {
|
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 perPage = 6;
|
||||||
|
|
||||||
// Преобразование и проверка параметров
|
// Преобразование и проверка categoryId и cityId
|
||||||
const catId = Number(categoryId);
|
const catId = Number(req.query.categoryId);
|
||||||
const citId = Number(cityId);
|
const citId = Number(req.query.cityId);
|
||||||
|
|
||||||
// Проверка и преобразование параметров
|
|
||||||
const where: Prisma.ArticleWhereInput = {
|
const where: Prisma.ArticleWhereInput = {
|
||||||
...(categoryId && !Number.isNaN(catId) && catId !== 0 && { categoryId: catId }),
|
...(Number.isInteger(catId) && catId > 0 ? { categoryId: catId } : {}),
|
||||||
...(cityId && !Number.isNaN(citId) && citId !== 0 && { cityId: citId }),
|
...(Number.isInteger(citId) && citId > 0 ? { cityId: citId } : {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Рассчитываем пропуск записей для пагинации
|
// Рассчитываем пропуск записей
|
||||||
const skip = (Number(page) - 1) * perPage;
|
const skip = (page - 1) * perPage;
|
||||||
|
|
||||||
// Выполняем два параллельных запроса: получение статей и подсчёт общего количества
|
// Выполняем два параллельных запроса: получение статей и подсчёт общего количества
|
||||||
const [articles, total] = await Promise.all([
|
const [articles, total] = await Promise.all([
|
||||||
@ -45,7 +47,7 @@ export async function listArticles(req: Request, res: Response) {
|
|||||||
res.json({
|
res.json({
|
||||||
articles,
|
articles,
|
||||||
totalPages: Math.ceil(total / perPage),
|
totalPages: Math.ceil(total / perPage),
|
||||||
currentPage: Number(page),
|
currentPage: page,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Логируем ошибку и отправляем ответ с кодом 500
|
// Логируем ошибку и отправляем ответ с кодом 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 { auth } from '../../middleware/auth';
|
||||||
import { searchArticles } from './controllers/search';
|
import { searchArticles } from './controllers/search';
|
||||||
import { listArticles } from './controllers/list';
|
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();
|
const router = express.Router();
|
||||||
|
|
||||||
// Search and list routes
|
// Поиск и список routes
|
||||||
router.get('/search', searchArticles);
|
router.get('/search', searchArticles);
|
||||||
router.get('/', listArticles);
|
router.get('/', listArticles);
|
||||||
|
|
||||||
|
// Импорт route
|
||||||
|
// Настройка middleware для обработки JSON
|
||||||
|
router.use(express.json({ limit: '10mb' }));
|
||||||
|
router.post('/import', auth, importArticles);
|
||||||
|
|
||||||
// CRUD routes
|
// CRUD routes
|
||||||
router.get('/:id', getArticle);
|
router.get('/:id', getArticle);
|
||||||
router.post('/', auth, createArticle);
|
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 express from 'express';
|
||||||
import { auth } from '../../middleware/auth';
|
import { auth } from '../../middleware/auth';
|
||||||
import {createS3Client, getMulterS3Config, uploadToS3} from '../../services/s3Service';
|
import { createS3Client, getMulterS3Config, uploadToS3 } from '../../services/s3Service';
|
||||||
import { logger } from '../../config/logger';
|
import { logger } from '../../config/logger';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user