Переработана схема привязки авторов к статьям, авторы трех типов.
This commit is contained in:
parent
35b470c498
commit
810599c718
@ -0,0 +1,5 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "AuthorRole" AS ENUM ('WRITER', 'PHOTOGRAPHER', 'EDITOR', 'TRANSLATOR');
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Author" ADD COLUMN "roles" "AuthorRole"[] DEFAULT ARRAY[]::"AuthorRole"[];
|
@ -0,0 +1,17 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Article" DROP CONSTRAINT "Article_authorId_fkey";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "ArticleAuthor" (
|
||||||
|
"articleId" TEXT NOT NULL,
|
||||||
|
"authorId" TEXT NOT NULL,
|
||||||
|
"role" "AuthorRole" NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "ArticleAuthor_pkey" PRIMARY KEY ("articleId","authorId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "ArticleAuthor" ADD CONSTRAINT "ArticleAuthor_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "ArticleAuthor" ADD CONSTRAINT "ArticleAuthor_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "Author"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `authorId` on the `Article` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Article" DROP COLUMN "authorId";
|
@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The primary key for the `ArticleAuthor` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "ArticleAuthor" DROP CONSTRAINT "ArticleAuthor_pkey",
|
||||||
|
ADD CONSTRAINT "ArticleAuthor_pkey" PRIMARY KEY ("articleId", "authorId", "role");
|
@ -30,27 +30,27 @@ enum AuthorRole {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Author {
|
model Author {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
displayName String
|
displayName String
|
||||||
bio String?
|
bio String?
|
||||||
avatarUrl String
|
avatarUrl String
|
||||||
order Int @default(0)
|
order Int @default(0)
|
||||||
okUrl String?
|
okUrl String?
|
||||||
vkUrl String?
|
vkUrl String?
|
||||||
websiteUrl String?
|
websiteUrl String?
|
||||||
email String?
|
email String?
|
||||||
userId String? @unique
|
userId String? @unique
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
articles Article[]
|
isActive Boolean @default(true)
|
||||||
isActive Boolean @default(true)
|
roles AuthorRole[] @default([])
|
||||||
roles AuthorRole[] @default([])
|
articles ArticleAuthor[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model Article {
|
model Article {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
importId Int @default(0)
|
importId Int @default(0)
|
||||||
title String
|
title String
|
||||||
excerpt String
|
excerpt String
|
||||||
content String
|
content String
|
||||||
@ -58,13 +58,23 @@ model Article {
|
|||||||
cityId Int
|
cityId Int
|
||||||
coverImage String
|
coverImage String
|
||||||
readTime Int
|
readTime Int
|
||||||
likes Int @default(0)
|
likes Int @default(0)
|
||||||
dislikes Int @default(0)
|
dislikes Int @default(0)
|
||||||
publishedAt DateTime @default(now())
|
publishedAt DateTime @default(now())
|
||||||
author Author @relation(fields: [authorId], references: [id])
|
|
||||||
authorId String
|
|
||||||
gallery GalleryImage[]
|
gallery GalleryImage[]
|
||||||
isActive Boolean @default(false)
|
isActive Boolean @default(false)
|
||||||
|
authors ArticleAuthor[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model ArticleAuthor {
|
||||||
|
articleId String
|
||||||
|
authorId String
|
||||||
|
role AuthorRole
|
||||||
|
|
||||||
|
article Article @relation(fields: [articleId], references: [id], onDelete: Cascade)
|
||||||
|
author Author @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@id([articleId, authorId, role])
|
||||||
}
|
}
|
||||||
|
|
||||||
model GalleryImage {
|
model GalleryImage {
|
||||||
|
@ -7,7 +7,9 @@ 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";
|
import { Article } from "../../../types";
|
||||||
import { bucketName, uploadBufferToS3 } from "../../../services/s3Service";
|
import { uploadBufferToS3 } from "../../../services/s3Service";
|
||||||
|
import { AuthorRole } from "@prisma/client";
|
||||||
|
|
||||||
|
|
||||||
const DEFAULT_COVER_IMAGE = '/images/cover-placeholder.webp';
|
const DEFAULT_COVER_IMAGE = '/images/cover-placeholder.webp';
|
||||||
|
|
||||||
@ -16,13 +18,18 @@ export async function getArticle(req: Request, res: Response) : Promise<void> {
|
|||||||
const article = await prisma.article.findUnique({
|
const article = await prisma.article.findUnique({
|
||||||
where: { id: req.params.id },
|
where: { id: req.params.id },
|
||||||
include: {
|
include: {
|
||||||
author: {
|
authors: {
|
||||||
select: {
|
include: {
|
||||||
id: true,
|
author: {
|
||||||
displayName: true,
|
select: {
|
||||||
avatarUrl: true,
|
id: true,
|
||||||
email: true
|
displayName: true,
|
||||||
}
|
avatarUrl: true,
|
||||||
|
email: true,
|
||||||
|
order: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
gallery: {
|
gallery: {
|
||||||
orderBy: {order: "asc"}, // Сортировка изображений по порядку
|
orderBy: {order: "asc"}, // Сортировка изображений по порядку
|
||||||
@ -95,6 +102,7 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// создаём статью и добавляем автора с ролью WRITER
|
||||||
const article = await prisma.article.create({
|
const article = await prisma.article.create({
|
||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
@ -104,17 +112,29 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
cityId,
|
cityId,
|
||||||
coverImage,
|
coverImage,
|
||||||
readTime,
|
readTime,
|
||||||
authorId: author.id
|
publishedAt: new Date(),
|
||||||
|
authors: {
|
||||||
|
create: [
|
||||||
|
{
|
||||||
|
author: { connect: { id: author.id } },
|
||||||
|
role: AuthorRole.WRITER, // 👈 по умолчанию WRITER
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
author: {
|
authors: {
|
||||||
select: {
|
include: {
|
||||||
id: true,
|
author: {
|
||||||
displayName: true,
|
select: {
|
||||||
email: true
|
id: true,
|
||||||
}
|
displayName: true,
|
||||||
}
|
email: true,
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(`Создана статья: ${article.id} пользователем ${req.user.id}`);
|
logger.info(`Создана статья: ${article.id} пользователем ${req.user.id}`);
|
||||||
@ -127,21 +147,21 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
|
|
||||||
export async function updateArticle(req: AuthRequest, res: Response) : Promise<void> {
|
export async function updateArticle(req: AuthRequest, res: Response) : Promise<void> {
|
||||||
try {
|
try {
|
||||||
const { title, excerpt, content, categoryId, cityId, coverImage, readTime, author } = req.body;
|
const { title, excerpt, content, categoryId, cityId, coverImage, readTime, authors } = req.body;
|
||||||
|
|
||||||
if (!req.user) {
|
if (!req.user) {
|
||||||
res.status(401).json({ error: 'Пользователь не вошел в систему' });
|
res.status(401).json({ error: 'Пользователь не вошел в систему' });
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const article = await prisma.article.findUnique({
|
const existingArticle = await prisma.article.findUnique({
|
||||||
where: { id: req.params.id },
|
where: { id: req.params.id },
|
||||||
select: { authorId: true } // Берём только автора
|
select: { id: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!article) {
|
if (!existingArticle) {
|
||||||
res.status(404).json({ error: 'Статья не найдена' });
|
res.status(404).json({ error: 'Статья не найдена' });
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkPermission(req.user, categoryId, 'edit')) {
|
if (!checkPermission(req.user, categoryId, 'edit')) {
|
||||||
@ -159,22 +179,34 @@ export async function updateArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
readTime
|
readTime
|
||||||
};
|
};
|
||||||
|
|
||||||
if (author?.id && author.id !== article.authorId) {
|
// Обновляем список авторов, если он передан
|
||||||
updateData.author = { connect: { id: author.id } };
|
if (Array.isArray(authors)) {
|
||||||
|
updateData.authors = {
|
||||||
|
// Удалим всех существующих авторов и добавим заново
|
||||||
|
deleteMany: {},
|
||||||
|
create: authors.map((a ) => ({
|
||||||
|
author: { connect: { id: a.author?.id } },
|
||||||
|
role: a.role as AuthorRole
|
||||||
|
})),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedArticle = await prisma.article.update({
|
const updatedArticle = await prisma.article.update({
|
||||||
where: { id: req.params.id },
|
where: { id: req.params.id },
|
||||||
data: updateData,
|
data: updateData,
|
||||||
include: {
|
include: {
|
||||||
author: {
|
authors: {
|
||||||
select: {
|
include: {
|
||||||
id: true,
|
author: {
|
||||||
displayName: true,
|
select: {
|
||||||
email: true
|
id: true,
|
||||||
}
|
displayName: true,
|
||||||
}
|
email: true,
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json(updatedArticle);
|
res.json(updatedArticle);
|
||||||
@ -208,13 +240,17 @@ export async function activeArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
isActive: isActive
|
isActive: isActive
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
author: {
|
authors: {
|
||||||
select: {
|
include: {
|
||||||
id: true,
|
author: {
|
||||||
displayName: true,
|
select: {
|
||||||
email: true
|
id: true,
|
||||||
}
|
displayName: true,
|
||||||
}
|
email: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -242,13 +278,17 @@ export async function reactArticle(req: AuthRequest, res: Response) : Promise<vo
|
|||||||
dislikes: newDisLikes
|
dislikes: newDisLikes
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
author: {
|
authors: {
|
||||||
select: {
|
include: {
|
||||||
id: true,
|
author: {
|
||||||
displayName: true,
|
select: {
|
||||||
email: true
|
id: true,
|
||||||
}
|
displayName: true,
|
||||||
}
|
email: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -315,7 +355,7 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
|||||||
|
|
||||||
let importedCount = 0;
|
let importedCount = 0;
|
||||||
|
|
||||||
// Получение списка всех авторов для находдения автора по имени
|
// Получение списка всех авторов для нахождения автора по имени
|
||||||
const allAuthors = await prisma.author.findMany({
|
const allAuthors = await prisma.author.findMany({
|
||||||
select: { id: true, displayName: true },
|
select: { id: true, displayName: true },
|
||||||
});
|
});
|
||||||
@ -326,7 +366,34 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
|||||||
for (const article of articles) {
|
for (const article of articles) {
|
||||||
try {
|
try {
|
||||||
// Шаг 1: Создание статьи
|
// Шаг 1: Создание статьи
|
||||||
const authorId = authorMap.get(article.authorName) || article.author.id;
|
const authorsToConnect = [];
|
||||||
|
|
||||||
|
// Добавление писателя
|
||||||
|
const writerId = authorMap.get(article.authorName);
|
||||||
|
if (writerId) {
|
||||||
|
authorsToConnect.push({
|
||||||
|
author: { connect: { id: writerId } },
|
||||||
|
role: AuthorRole.WRITER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавление соавтора
|
||||||
|
const coWriterId = authorMap.get(article.coAuthorName);
|
||||||
|
if (coWriterId) {
|
||||||
|
authorsToConnect.push({
|
||||||
|
author: { connect: { id: coWriterId } },
|
||||||
|
role: AuthorRole.WRITER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавление фотографа
|
||||||
|
const photographerId = authorMap.get(article.photographerName);
|
||||||
|
if (photographerId) {
|
||||||
|
authorsToConnect.push({
|
||||||
|
author: { connect: { id: photographerId } },
|
||||||
|
role: AuthorRole.PHOTOGRAPHER,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const newArticle = await prisma.article.create({
|
const newArticle = await prisma.article.create({
|
||||||
data: {
|
data: {
|
||||||
@ -341,8 +408,8 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
|||||||
publishedAt: new Date(article.publishedAt),
|
publishedAt: new Date(article.publishedAt),
|
||||||
likes: article.likes || 0,
|
likes: article.likes || 0,
|
||||||
dislikes: article.dislikes || 0,
|
dislikes: article.dislikes || 0,
|
||||||
author: {
|
authors: {
|
||||||
connect: { id: authorId },
|
create: authorsToConnect,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -34,12 +34,17 @@ export async function listArticles(req: Request, res: Response) {
|
|||||||
prisma.article.findMany({
|
prisma.article.findMany({
|
||||||
where,
|
where,
|
||||||
include: {
|
include: {
|
||||||
author: {
|
authors: {
|
||||||
select: {
|
include: {
|
||||||
id: true,
|
author: {
|
||||||
displayName: true,
|
select: {
|
||||||
avatarUrl: true,
|
id: true,
|
||||||
email: true
|
displayName: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
email: true,
|
||||||
|
order: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { prisma } from '../../../lib/prisma';
|
import { prisma } from '../../../lib/prisma';
|
||||||
import { Prisma } from '@prisma/client';
|
import { AuthorRole, Prisma } from '@prisma/client';
|
||||||
|
|
||||||
export async function searchArticles(req: Request, res: Response) {
|
export async function searchArticles(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const { q, author, page = 1, limit = 9 } = req.query;
|
const { q, author, role, page = 1, limit = 9 } = req.query;
|
||||||
const skip = ((Number(page) || 1) - 1) * (Number(limit) || 9);
|
const skip = ((Number(page) || 1) - 1) * (Number(limit) || 9);
|
||||||
|
|
||||||
// Формируем where-условие
|
// Формируем where-условие
|
||||||
@ -19,7 +19,16 @@ export async function searchArticles(req: Request, res: Response) {
|
|||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
...(typeof author === 'string' && author.trim()
|
...(typeof author === 'string' && author.trim()
|
||||||
? { authorId: author }
|
? {
|
||||||
|
authors: {
|
||||||
|
some: {
|
||||||
|
authorId: author,
|
||||||
|
...(typeof role === 'string' && role.trim()
|
||||||
|
? { role: role as AuthorRole } // 👈 проверяем и добавляем роль
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,12 +37,17 @@ export async function searchArticles(req: Request, res: Response) {
|
|||||||
prisma.article.findMany({
|
prisma.article.findMany({
|
||||||
where,
|
where,
|
||||||
include: {
|
include: {
|
||||||
author: {
|
authors: {
|
||||||
select: {
|
include: {
|
||||||
id: true,
|
author: {
|
||||||
displayName: true,
|
select: {
|
||||||
avatarUrl: true,
|
id: true,
|
||||||
email: true,
|
displayName: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
email: true,
|
||||||
|
order: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -49,6 +63,7 @@ export async function searchArticles(req: Request, res: Response) {
|
|||||||
articles,
|
articles,
|
||||||
totalPages: Math.ceil(total / (Number(limit) || 9)),
|
totalPages: Math.ceil(total / (Number(limit) || 9)),
|
||||||
currentPage: Number(page) || 1,
|
currentPage: Number(page) || 1,
|
||||||
|
total: total,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка поиска по статьям:', error);
|
console.error('Ошибка поиска по статьям:', error);
|
||||||
|
@ -3,11 +3,13 @@ import { AuthRequest } from '../../../middleware/auth';
|
|||||||
import { authorService } from '../../../services/authorService';
|
import { authorService } from '../../../services/authorService';
|
||||||
import { logger } from "../../../config/logger";
|
import { logger } from "../../../config/logger";
|
||||||
import { prisma } from "../../../lib/prisma";
|
import { prisma } from "../../../lib/prisma";
|
||||||
|
import { AuthorRole } from "@prisma/client";
|
||||||
|
|
||||||
|
|
||||||
export async function getAuthors(req: AuthRequest, res: Response): Promise<void> {
|
export async function getAuthors(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const authors = await authorService.getAuthors();
|
const { role, page } = req.query;
|
||||||
|
const authors = await authorService.getAuthors(Number(page), role as string);
|
||||||
res.json(authors);
|
res.json(authors);
|
||||||
} catch {
|
} catch {
|
||||||
res.status(500).json({ error: 'Server error' });
|
res.status(500).json({ error: 'Server error' });
|
||||||
@ -22,7 +24,14 @@ export async function createAuthor(req: AuthRequest, res: Response): Promise<voi
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { email, displayName, bio, avatarUrl, order } = req.body;
|
const { email, displayName, bio, avatarUrl, okUrl, vkUrl, websiteUrl, roles } = req.body;
|
||||||
|
|
||||||
|
// Проверим, что пришедшие роли валидны
|
||||||
|
const validRoles = Array.isArray(roles)
|
||||||
|
? roles.filter((role: any): role is AuthorRole =>
|
||||||
|
Object.values(AuthorRole).includes(role as AuthorRole)
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
const lastAuthor = await prisma.author.findFirst({
|
const lastAuthor = await prisma.author.findFirst({
|
||||||
orderBy: {
|
orderBy: {
|
||||||
@ -38,7 +47,11 @@ export async function createAuthor(req: AuthRequest, res: Response): Promise<voi
|
|||||||
displayName,
|
displayName,
|
||||||
bio,
|
bio,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
order: nextOrder
|
order: nextOrder,
|
||||||
|
okUrl,
|
||||||
|
vkUrl,
|
||||||
|
websiteUrl,
|
||||||
|
roles: validRoles,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@ -47,6 +60,10 @@ export async function createAuthor(req: AuthRequest, res: Response): Promise<voi
|
|||||||
bio: true,
|
bio: true,
|
||||||
avatarUrl: true,
|
avatarUrl: true,
|
||||||
order: true,
|
order: true,
|
||||||
|
okUrl: true,
|
||||||
|
vkUrl: true,
|
||||||
|
websiteUrl: true,
|
||||||
|
roles: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -67,7 +84,14 @@ export async function updateAuthor(req: AuthRequest, res: Response): Promise<voi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { email, displayName, bio, avatarUrl, order, okUrl, vkUrl, websiteUrl } = req.body;
|
const { email, displayName, bio, avatarUrl, order, okUrl, vkUrl, websiteUrl, roles } = req.body;
|
||||||
|
|
||||||
|
// Проверим, что пришедшие роли валидны
|
||||||
|
const validRoles = Array.isArray(roles)
|
||||||
|
? roles.filter((role: any): role is AuthorRole =>
|
||||||
|
Object.values(AuthorRole).includes(role as AuthorRole)
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
// Подготовка данных для изменения
|
// Подготовка данных для изменения
|
||||||
const updateData: any = {
|
const updateData: any = {
|
||||||
@ -79,6 +103,7 @@ export async function updateAuthor(req: AuthRequest, res: Response): Promise<voi
|
|||||||
okUrl,
|
okUrl,
|
||||||
vkUrl,
|
vkUrl,
|
||||||
websiteUrl,
|
websiteUrl,
|
||||||
|
roles: validRoles,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Обновление данных автора
|
// Обновление данных автора
|
||||||
@ -95,6 +120,7 @@ export async function updateAuthor(req: AuthRequest, res: Response): Promise<voi
|
|||||||
okUrl: true,
|
okUrl: true,
|
||||||
vkUrl: true,
|
vkUrl: true,
|
||||||
websiteUrl: true,
|
websiteUrl: true,
|
||||||
|
roles: true,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,7 +6,8 @@ import { AuthRequest } from "../../../middleware/auth";
|
|||||||
// Изменить количество лайков
|
// Изменить количество лайков
|
||||||
export async function updateLikes(req: AuthRequest, res: Response): Promise<void> {
|
export async function updateLikes(req: AuthRequest, res: Response): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const authors = await authorService.getAuthors();
|
const { role, page } = req.query;
|
||||||
|
const authors = await authorService.getAuthors(Number(page), role as string);
|
||||||
res.json(authors);
|
res.json(authors);
|
||||||
} catch {
|
} catch {
|
||||||
res.status(500).json({ error: 'Серверная ошибка' });
|
res.status(500).json({ error: 'Серверная ошибка' });
|
||||||
|
@ -1,10 +1,29 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { AuthorRole } from '@prisma/client';
|
||||||
import { Author } from '../types/auth';
|
import { Author } from '../types/auth';
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
export const authorService = {
|
export const authorService = {
|
||||||
getAuthors: async (): Promise<Author[]> => {
|
getAuthors: async (page: number, role?: string): Promise<Author[]> => {
|
||||||
|
|
||||||
|
if (role && !Object.values(AuthorRole).includes(role as AuthorRole)) {
|
||||||
|
throw new Error(`Недопустимая роль: ${role}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageSize = 10;
|
||||||
|
|
||||||
|
const sortRoles = role
|
||||||
|
? {
|
||||||
|
roles: {
|
||||||
|
has: role as AuthorRole, // Prisma оператор для enum[]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
const skip = page ? (page - 1) * pageSize : undefined;
|
||||||
|
const take = page ? pageSize : undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Получаем авторов
|
// 1. Получаем авторов
|
||||||
const authors = await prisma.author.findMany({
|
const authors = await prisma.author.findMany({
|
||||||
@ -20,8 +39,15 @@ export const authorService = {
|
|||||||
email: true,
|
email: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
roles: true,
|
||||||
_count: {
|
_count: {
|
||||||
select: {articles: true}, // Подсчёт количества статей автора
|
select: {
|
||||||
|
articles: {
|
||||||
|
where: {
|
||||||
|
role: role as AuthorRole, // 👈 учитываются только WRITER-авторы
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
@ -29,24 +55,28 @@ export const authorService = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
where: {order: {not: 0}},
|
where: {
|
||||||
|
order: {not: 0},
|
||||||
|
...sortRoles, // 👈 добавляем условие по ролям
|
||||||
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
},
|
},
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const sql = `
|
||||||
|
SELECT "authorId", SUM("Article"."likes") AS "totalLikes"
|
||||||
|
FROM "ArticleAuthor"
|
||||||
|
JOIN "Article" ON "ArticleAuthor"."articleId" = "Article"."id"
|
||||||
|
WHERE "ArticleAuthor"."role" = $1::"AuthorRole"
|
||||||
|
GROUP BY "authorId"
|
||||||
|
`;
|
||||||
|
|
||||||
// 2. Считаем сумму лайков по авторам
|
// 2. Считаем сумму лайков по авторам
|
||||||
const likesPerAuthor = await prisma.article.groupBy({
|
const rawLikes: { authorId: string; totalLikes: number | null }[] = await prisma.$queryRawUnsafe(sql, role);
|
||||||
by: ['authorId'],
|
const likesMap = new Map(rawLikes.map(item => [item.authorId, item.totalLikes ?? 0]));
|
||||||
_sum: {
|
|
||||||
likes: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// 3. Строим map для быстрого доступа
|
|
||||||
const likesMap = new Map(
|
|
||||||
likesPerAuthor.map((item) => [item.authorId, item._sum.likes ?? 0])
|
|
||||||
);
|
|
||||||
|
|
||||||
return authors.map(author => ({
|
return authors.map(author => ({
|
||||||
id: author.id,
|
id: author.id,
|
||||||
@ -62,7 +92,8 @@ export const authorService = {
|
|||||||
userId: author.userId,
|
userId: author.userId,
|
||||||
userDisplayName: author.user?.displayName || null,
|
userDisplayName: author.user?.displayName || null,
|
||||||
isActive: author.isActive,
|
isActive: author.isActive,
|
||||||
totalLikes: likesMap.get(author.id) ?? 0,
|
roles: author.roles,
|
||||||
|
totalLikes: Number(likesMap.get(author.id) ?? 0),
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка получения авторов:', error);
|
console.error('Ошибка получения авторов:', error);
|
||||||
|
@ -10,6 +10,8 @@ export interface Article {
|
|||||||
cityId: number;
|
cityId: number;
|
||||||
author: Author;
|
author: Author;
|
||||||
authorName: string;
|
authorName: string;
|
||||||
|
coAuthorName: string;
|
||||||
|
photographerName: string;
|
||||||
coverImage: string;
|
coverImage: string;
|
||||||
images?: string[];
|
images?: string[];
|
||||||
imageSubs: string[];
|
imageSubs: string[];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user