Реализована работа с статьями - создание, редактирование.
This commit is contained in:
parent
c5cb7c2984
commit
9e6276ace9
@ -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;
|
@ -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";
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -28,6 +28,7 @@ export async function searchArticles(req: Request, res: Response) {
|
||||
select: {
|
||||
id: true,
|
||||
displayName: true,
|
||||
avatarUrl: true,
|
||||
email: true,
|
||||
},
|
||||
},
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
*/
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user