Реализована работа с статьями - создание, редактирование.

This commit is contained in:
anibilag 2025-02-17 23:04:19 +03:00
parent c5cb7c2984
commit 9e6276ace9
9 changed files with 98 additions and 58 deletions

View File

@ -0,0 +1,10 @@
/*
Warnings:
- You are about to drop the column `city` on the `Article` table. All the data in the column will be lost.
- Added the required column `cityId` to the `Article` table without a default value. This is not possible if the table is not empty.
*/
-- AlterTable
ALTER TABLE "Article" DROP COLUMN "city",
ADD COLUMN "cityId" INTEGER NOT NULL;

View File

@ -0,0 +1,11 @@
/*
Warnings:
- You are about to drop the `Category` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropForeignKey
ALTER TABLE "Article" DROP CONSTRAINT "Article_categoryId_fkey";
-- DropTable
DROP TABLE "Category";

View File

@ -25,9 +25,8 @@ model Article {
title String
excerpt String
content String
category Category @relation(fields: [categoryId], references: [id])
categoryId Int
city String
cityId Int
coverImage String
readTime Int
likes Int @default(0)
@ -38,12 +37,6 @@ model Article {
gallery GalleryImage[]
}
model Category {
id Int @id
name String @unique
articles Article[]
}
model GalleryImage {
id String @id @default(uuid())
url String

View File

@ -9,11 +9,11 @@ export async function getArticle(req: Request, res: Response) : Promise<void> {
const article = await prisma.article.findUnique({
where: { id: req.params.id },
include: {
category: true,
author: {
select: {
id: true,
displayName: true,
avatarUrl: true,
email: true
}
}
@ -35,7 +35,7 @@ export async function getArticle(req: Request, res: Response) : Promise<void> {
export async function createArticle(req: AuthRequest, res: Response) : Promise<void> {
try {
const { title, excerpt, content, categoryId, city, coverImage, readTime } = req.body;
const { title, excerpt, content, categoryId, cityId, coverImage, readTime } = req.body;
if (!req.user) {
logger.warn('Unauthorized article creation attempt');
@ -43,18 +43,14 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
return
}
const category = await prisma.category.findUnique({
where: { id: categoryId }
});
if (!category) {
if (!categoryId || categoryId < 1 || categoryId > 8) {
logger.warn(`Invalid category ID: ${categoryId}`);
res.status(400).json({ error: 'Invalid category' });
return
}
if (!checkPermission(req.user, categoryId, 'create')) {
logger.warn(`Permission denied for user ${req.user.id} to create article in category ${category.name}`);
logger.warn(`Permission denied for user ${req.user.id} to create article in category ${categoryId}`);
res.status(403).json({ error: 'Permission denied' });
return
}
@ -65,13 +61,12 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
excerpt,
content,
categoryId,
city,
cityId,
coverImage,
readTime,
authorId: req.user.id
},
include: {
category: true,
author: {
select: {
id: true,
@ -92,7 +87,7 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
export async function updateArticle(req: AuthRequest, res: Response) : Promise<void> {
try {
const { title, excerpt, content, category, city, coverImage, readTime } = req.body;
const { title, excerpt, content, categoryId, cityId, coverImage, readTime } = req.body;
if (!req.user) {
res.status(401).json({ error: 'Пользователь не вошел в систему' });
@ -100,8 +95,7 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
}
const article = await prisma.article.findUnique({
where: { id: req.params.id },
include: { category: true }
where: { id: req.params.id }
});
if (!article) {
@ -109,7 +103,7 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
return
}
if (!checkPermission(req.user, category, 'edit')) {
if (!checkPermission(req.user, categoryId, 'edit')) {
res.status(403).json({ error: 'Нет прав на выполнение этой операции' });
return
}
@ -120,13 +114,12 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
title,
excerpt,
content,
categoryId: parseInt(category),
city,
categoryId: Number(categoryId),
cityId,
coverImage,
readTime
},
include: {
category: true,
author: {
select: {
id: true,
@ -152,8 +145,7 @@ export async function deleteArticle(req: AuthRequest, res: Response) : Promise<v
}
const article = await prisma.article.findUnique({
where: { id: req.params.id },
include: { category: true }
where: { id: req.params.id }
});
if (!article) {

View File

@ -1,19 +1,20 @@
import { Request, Response } from 'express';
import { prisma } from '../../../lib/prisma';
import { Prisma } from '@prisma/client';
import { CategoryMap } from '../../../types';
export async function listArticles(req: Request, res: Response) {
try {
const { page = 1, category, city } = req.query;
const { page = 1, categoryId, cityId } = req.query;
const perPage = 6;
const categoryId = CategoryMap[category as string];
// Преобразование и проверка параметров
const catId = Number(categoryId);
const citId = Number(cityId);
// Проверка и преобразование параметров в строковые значения
// Проверка и преобразование параметров
const where: Prisma.ArticleWhereInput = {
...(categoryId && { categoryId: categoryId }),
...(city && { city: city as string }),
...(categoryId && !Number.isNaN(catId) && catId !== 0 && { categoryId: catId }),
...(cityId && !Number.isNaN(citId) && citId !== 0 && { cityId: citId }),
};
// Рассчитываем пропуск записей для пагинации
@ -28,7 +29,8 @@ export async function listArticles(req: Request, res: Response) {
select: {
id: true,
displayName: true,
email: true,
avatarUrl: true,
email: true
},
},
},

View File

@ -28,6 +28,7 @@ export async function searchArticles(req: Request, res: Response) {
select: {
id: true,
displayName: true,
avatarUrl: true,
email: true,
},
},

View File

@ -1,16 +1,28 @@
import { CategoryName, City } from './index';
import {CategoryName, CategoryIds} from './index';
/*
export interface UserPermissions {
categories: {
[key in CategoryName]?: {
[key in CategoryName]: {
create: boolean;
edit: boolean;
delete: boolean;
};
};
cities: City[];
cities: number[];
isAdmin: boolean;
}
*/
export interface UserPermissions {
categories: {
[key: string]: { edit: boolean; create: boolean; delete: boolean };
};
cities: number[];
isAdmin: boolean;
}
export type PermissionAction = 'edit' | 'create' | 'delete';
export interface User {
id: string;

View File

@ -3,9 +3,8 @@ export interface Article {
title: string;
excerpt: string;
content: string;
category: Category;
categoryId: number;
city: City;
cityId: number;
author: Author;
coverImage: string;
gallery?: GalleryImage[];
@ -30,14 +29,17 @@ export interface Author {
bio: string;
}
export interface Category {
id: number;
name: CategoryName;
}
// export interface Category {
// id: number;
// name: CategoryName;
// }
export type CategoryName = 'Film' | 'Theater' | 'Music' | 'Sports' | 'Art' | 'Legends' | 'Anniversaries' | 'Memory';
export type CategoryIds = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8';
export type City = 'New York' | 'London';
/*
export const CategoryMap: Record<string, number> = {
'Film': 1,
'Theater': 2,
@ -48,3 +50,4 @@ export const CategoryMap: Record<string, number> = {
'Anniversaries': 7,
'Memory': 8,
};
*/

View File

@ -1,30 +1,46 @@
import { Category, City } from '../types';
import { User } from '../types/auth';
import {PermissionAction, User} from '../types/auth';
/*
export const checkPermission = (
user: User,
category: Category,
action: 'create' | 'edit' | 'delete'
categoryId: string,
action: PermissionAction
): boolean => {
if (user.permissions.isAdmin) return true;
return !!user.permissions.categories[category.name]?.[action];
return !!user.permissions.categories?[categoryId]?.[action];
};
*/
export const checkPermission = (
user: User,
categoryId: string,
action: PermissionAction
): boolean => {
// Если пользователь админ — ему разрешено всё
if (user.permissions.isAdmin) return true;
// Проверяем, есть ли такая категория в permissions
const categoryPermissions = user.permissions.categories[categoryId];
// Если категория отсутствует или в ней нет действия — запрет
return categoryPermissions ? categoryPermissions[action] : false;
};
export const checkCityAccess = (user: User, city: City): boolean => {
export const checkCityAccess = (user: User, cityId: number): boolean => {
if (user.permissions.isAdmin) return true;
return user.permissions.cities.includes(city);
return user.permissions.cities.includes(cityId);
};
export const getDefaultPermissions = () => ({
categories: {
Film: { create: false, edit: false, delete: false },
Theater: { create: false, edit: false, delete: false },
Music: { create: false, edit: false, delete: false },
Sports: { create: false, edit: false, delete: false },
Art: { create: false, edit: false, delete: false },
Legends: { create: false, edit: false, delete: false },
Anniversaries: { create: false, edit: false, delete: false },
Memory: { create: false, edit: false, delete: false }
1: { create: false, edit: false, delete: false },
2: { create: false, edit: false, delete: false },
3: { create: false, edit: false, delete: false },
4: { create: false, edit: false, delete: false },
5: { create: false, edit: false, delete: false },
6: { create: false, edit: false, delete: false },
7: { create: false, edit: false, delete: false },
8: { create: false, edit: false, delete: false }
},
cities: [],
isAdmin: false