Доработан импорт статей, сделано разделение пользователей на собственно пользователей и авторов.
This commit is contained in:
parent
35791b4308
commit
35b470c498
16
prisma/migrations/20250513200815_add_authors/migration.sql
Normal file
16
prisma/migrations/20250513200815_add_authors/migration.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Author" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"displayName" TEXT NOT NULL,
|
||||||
|
"bio" TEXT,
|
||||||
|
"avatarUrl" TEXT,
|
||||||
|
"order" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"okUrl" TEXT,
|
||||||
|
"vkUrl" TEXT,
|
||||||
|
"websiteUrl" TEXT,
|
||||||
|
"email" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Author_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Made the column `avatarUrl` on table `Author` required. This step will fail if there are existing NULL values in that column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Author" ALTER COLUMN "avatarUrl" SET NOT NULL;
|
@ -0,0 +1,5 @@
|
|||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Article" DROP CONSTRAINT "Article_authorId_fkey";
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Article" ADD CONSTRAINT "Article_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "Author"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[userId]` on the table `Author` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Author" ADD COLUMN "userId" TEXT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Author_userId_key" ON "Author"("userId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Author" ADD CONSTRAINT "Author_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Author" ADD COLUMN "isActive" BOOLEAN NOT NULL DEFAULT true;
|
@ -8,18 +8,44 @@ datasource db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
email String @unique
|
email String @unique
|
||||||
password String
|
password String
|
||||||
displayName String
|
displayName String
|
||||||
avatarUrl String
|
avatarUrl String
|
||||||
bio String?
|
bio String?
|
||||||
isAdmin Boolean @default(false)
|
isAdmin Boolean @default(false)
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
permissions Json
|
permissions Json
|
||||||
|
order Int @default(0)
|
||||||
|
Author Author?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuthorRole {
|
||||||
|
WRITER
|
||||||
|
PHOTOGRAPHER
|
||||||
|
EDITOR
|
||||||
|
TRANSLATOR
|
||||||
|
}
|
||||||
|
|
||||||
|
model Author {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
displayName String
|
||||||
|
bio String?
|
||||||
|
avatarUrl String
|
||||||
|
order Int @default(0)
|
||||||
|
okUrl String?
|
||||||
|
vkUrl String?
|
||||||
|
websiteUrl String?
|
||||||
|
email String?
|
||||||
|
userId String? @unique
|
||||||
|
user User? @relation(fields: [userId], references: [id])
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
articles Article[]
|
articles Article[]
|
||||||
order Int @default(0)
|
isActive Boolean @default(true)
|
||||||
|
roles AuthorRole[] @default([])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Article {
|
model Article {
|
||||||
@ -35,7 +61,7 @@ model Article {
|
|||||||
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 User @relation(fields: [authorId], references: [id])
|
author Author @relation(fields: [authorId], references: [id])
|
||||||
authorId String
|
authorId String
|
||||||
gallery GalleryImage[]
|
gallery GalleryImage[]
|
||||||
isActive Boolean @default(false)
|
isActive Boolean @default(false)
|
||||||
|
@ -76,6 +76,25 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userId = req.user?.id; // ← ID текущего пользователя
|
||||||
|
|
||||||
|
// 1. Найти автора, связанного с пользователем
|
||||||
|
let author = await prisma.author.findFirst({
|
||||||
|
where: { userId },
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Если не найден — найти автора с order = 1
|
||||||
|
if (!author) {
|
||||||
|
author = await prisma.author.findFirst({
|
||||||
|
where: { order: 1 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!author) {
|
||||||
|
res.status(400).json({ error: 'Автор по умолчанию не найден' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const article = await prisma.article.create({
|
const article = await prisma.article.create({
|
||||||
data: {
|
data: {
|
||||||
title,
|
title,
|
||||||
@ -85,7 +104,7 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
|
|||||||
cityId,
|
cityId,
|
||||||
coverImage,
|
coverImage,
|
||||||
readTime,
|
readTime,
|
||||||
authorId: req.user.id
|
authorId: author.id
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
author: {
|
author: {
|
||||||
@ -296,10 +315,19 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
|||||||
|
|
||||||
let importedCount = 0;
|
let importedCount = 0;
|
||||||
|
|
||||||
|
// Получение списка всех авторов для находдения автора по имени
|
||||||
|
const allAuthors = await prisma.author.findMany({
|
||||||
|
select: { id: true, displayName: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
const authorMap = new Map(allAuthors.map(a => [a.displayName, a.id]));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const article of articles) {
|
for (const article of articles) {
|
||||||
try {
|
try {
|
||||||
// Шаг 1: Создание статьи
|
// Шаг 1: Создание статьи
|
||||||
|
const authorId = authorMap.get(article.authorName) || article.author.id;
|
||||||
|
|
||||||
const newArticle = await prisma.article.create({
|
const newArticle = await prisma.article.create({
|
||||||
data: {
|
data: {
|
||||||
importId: article.importId,
|
importId: article.importId,
|
||||||
@ -314,7 +342,7 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
|||||||
likes: article.likes || 0,
|
likes: article.likes || 0,
|
||||||
dislikes: article.dislikes || 0,
|
dislikes: article.dislikes || 0,
|
||||||
author: {
|
author: {
|
||||||
connect: { id: article.author.id },
|
connect: { id: authorId },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,14 +1,275 @@
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { AuthRequest } from '../../../middleware/auth';
|
import { AuthRequest } from '../../../middleware/auth';
|
||||||
import { userService } from '../../../services/userService';
|
import { authorService } from '../../../services/authorService';
|
||||||
|
import { logger } from "../../../config/logger";
|
||||||
|
import { prisma } from "../../../lib/prisma";
|
||||||
|
|
||||||
|
|
||||||
// Список авторов - без permissions и без чистых админов
|
|
||||||
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 userService.getAuthors();
|
const authors = await authorService.getAuthors();
|
||||||
res.json(authors);
|
res.json(authors);
|
||||||
} catch {
|
} catch {
|
||||||
res.status(500).json({ error: 'Server error' });
|
res.status(500).json({ error: 'Server error' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function createAuthor(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user?.permissions.isAdmin) {
|
||||||
|
logger.warn(`Не администратор ${req.user?.id} пытается создать автора`);
|
||||||
|
res.status(403).json({ error: 'Требуются права администратора' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { email, displayName, bio, avatarUrl, order } = req.body;
|
||||||
|
|
||||||
|
const lastAuthor = await prisma.author.findFirst({
|
||||||
|
orderBy: {
|
||||||
|
order: 'desc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextOrder = lastAuthor ? lastAuthor.order + 1 : 1;
|
||||||
|
|
||||||
|
const author = await prisma.author.create({
|
||||||
|
data: {
|
||||||
|
email,
|
||||||
|
displayName,
|
||||||
|
bio,
|
||||||
|
avatarUrl,
|
||||||
|
order: nextOrder
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
displayName: true,
|
||||||
|
bio: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
order: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Успешное создание автора: ${author.id}`);
|
||||||
|
res.status(201).json(author);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Ошибка создания автора:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка создания автора' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateAuthor(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user?.permissions.isAdmin) {
|
||||||
|
logger.warn(`Не администратор ${req.user?.id} пытается обновить автора`);
|
||||||
|
res.status(403).json({ error: 'Требуются права администратора' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
const { email, displayName, bio, avatarUrl, order, okUrl, vkUrl, websiteUrl } = req.body;
|
||||||
|
|
||||||
|
// Подготовка данных для изменения
|
||||||
|
const updateData: any = {
|
||||||
|
email,
|
||||||
|
displayName,
|
||||||
|
bio,
|
||||||
|
avatarUrl,
|
||||||
|
order,
|
||||||
|
okUrl,
|
||||||
|
vkUrl,
|
||||||
|
websiteUrl,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Обновление данных автора
|
||||||
|
const author = await prisma.author.update({
|
||||||
|
where: { id },
|
||||||
|
data: updateData,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
displayName: true,
|
||||||
|
bio: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
order: true,
|
||||||
|
okUrl: true,
|
||||||
|
vkUrl: true,
|
||||||
|
websiteUrl: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Успешное обновление автора: ${author.id}`);
|
||||||
|
res.json(author);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Ошибка обновления автора:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка обновления автора' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteAuthor(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user?.permissions.isAdmin) {
|
||||||
|
logger.warn(`Не администратор ${req.user?.id} пытается удалить пользователя`);
|
||||||
|
res.status(403).json({ error: 'Требуются права администратора' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const existingUser = await prisma.author.findUnique({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!existingUser) {
|
||||||
|
res.status(404).json({ error: 'Автор не найден' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.author.delete({
|
||||||
|
where: { id }
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Успешное удаление автора: ${id}`);
|
||||||
|
res.json({ message: 'Успешное удаление автора' });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Ошибка удаления автора:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка удаления автора' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function linkAuthorToUser(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user?.permissions.isAdmin) {
|
||||||
|
logger.warn(`Не администратор ${req.user?.id} пытается обновить автора`);
|
||||||
|
res.status(403).json({ error: 'Требуются права администратора' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
const { userId } = req.body;
|
||||||
|
|
||||||
|
const author = await prisma.author.update({
|
||||||
|
where: { id },
|
||||||
|
data: { userId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
userId: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Успешное связывание автора: ${author.id} с пользователем ${author.userId}`);
|
||||||
|
res.json(author);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Ошибка связывания автора:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка связывания автора' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unlinkAuthorFromUser(req: AuthRequest, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user?.permissions.isAdmin) {
|
||||||
|
logger.warn(`Не администратор ${req.user?.id} пытается обновить автора`);
|
||||||
|
res.status(403).json({ error: 'Требуются права администратора' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const author = await prisma.author.update({
|
||||||
|
where: { id },
|
||||||
|
data: { userId: null },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
userId: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`Успешное отвязывание автора: ${author.id} от пользователя ${author.userId}`);
|
||||||
|
res.json(author);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Ошибка отвязывания автора:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка отвязывания автора' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function toggleActiveAuthor(req: AuthRequest, res: Response) : Promise<void> {
|
||||||
|
try {
|
||||||
|
const { isActive } = req.body;
|
||||||
|
|
||||||
|
if (!req.user) {
|
||||||
|
res.status(401).json({ error: 'Пользователь не вошел в систему' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const author = await prisma.author.findUnique({
|
||||||
|
where: { id: req.params.id }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!author) {
|
||||||
|
res.status(404).json({ error: 'Автор не найден' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedAuthor = await prisma.author.update({
|
||||||
|
where: { id: req.params.id },
|
||||||
|
data: { isActive: isActive },
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json(updatedAuthor);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Ошибка активирования автора:', error);
|
||||||
|
res.status(500).json({ error: 'Серверная ошибка' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function reorderAuthor(req: AuthRequest, res: Response) : Promise<void> {
|
||||||
|
try {
|
||||||
|
if (!req.user) {
|
||||||
|
res.status(401).json({ error: 'Пользователь не вошел в систему' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { id } = req.params;
|
||||||
|
const { direction } = req.body; // 'up' или 'down'
|
||||||
|
|
||||||
|
if (!['up', 'down'].includes(direction)) {
|
||||||
|
res.status(400).json({ error: 'Неправильное направление' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = await prisma.author.findUnique({ where: { id } });
|
||||||
|
if (!current) {
|
||||||
|
res.status(404).json({ error: 'Автор не найден' });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const neighbor = await prisma.author.findFirst({
|
||||||
|
where: {
|
||||||
|
order: direction === 'up' ? { lt: current.order } : { gt: current.order },
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
order: direction === 'up' ? 'desc' : 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!neighbor) {
|
||||||
|
res.status(200).json({ message: 'Уже достигли границы', author: current });
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await prisma.$transaction([
|
||||||
|
prisma.author.update({
|
||||||
|
where: { id: current.id },
|
||||||
|
data: { order: neighbor.order },
|
||||||
|
}),
|
||||||
|
prisma.author.update({
|
||||||
|
where: { id: neighbor.id },
|
||||||
|
data: { order: current.order },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const updated = await prisma.author.findUnique({ where: { id } });
|
||||||
|
res.json(updated);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при изменении порядка авторов:', error);
|
||||||
|
res.status(500).json({ error: 'Ошибка при изменении порядка авторов' }); }
|
||||||
|
}
|
||||||
|
@ -1,9 +1,26 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import { getAuthors } from './controllers/authors';
|
import { auth } from '../../middleware/auth';
|
||||||
|
import {
|
||||||
|
getAuthors,
|
||||||
|
createAuthor,
|
||||||
|
updateAuthor,
|
||||||
|
deleteAuthor,
|
||||||
|
linkAuthorToUser,
|
||||||
|
unlinkAuthorFromUser,
|
||||||
|
toggleActiveAuthor,
|
||||||
|
reorderAuthor
|
||||||
|
} from './controllers/authors';
|
||||||
|
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
|
||||||
router.get('/', getAuthors);
|
router.get('/', getAuthors);
|
||||||
|
router.post('/', auth, createAuthor);
|
||||||
|
router.put('/:id', auth, updateAuthor);
|
||||||
|
router.delete('/:id', auth, deleteAuthor);
|
||||||
|
router.put('/:id/link-user', auth, linkAuthorToUser);
|
||||||
|
router.put('/:id/unlink-user', auth, unlinkAuthorFromUser);
|
||||||
|
router.put('/:id/toggle-active', auth, toggleActiveAuthor);
|
||||||
|
router.put('/:id/reorder', auth, reorderAuthor);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@ -1,12 +1,12 @@
|
|||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { userService } from '../../../services/userService';
|
import { authorService } from '../../../services/authorService';
|
||||||
import { AuthRequest } from "../../../middleware/auth";
|
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 userService.getAuthors();
|
const authors = await authorService.getAuthors();
|
||||||
res.json(authors);
|
res.json(authors);
|
||||||
} catch {
|
} catch {
|
||||||
res.status(500).json({ error: 'Серверная ошибка' });
|
res.status(500).json({ error: 'Серверная ошибка' });
|
||||||
|
72
src/services/authorService.ts
Normal file
72
src/services/authorService.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
import { Author } from '../types/auth';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
export const authorService = {
|
||||||
|
getAuthors: async (): Promise<Author[]> => {
|
||||||
|
try {
|
||||||
|
// 1. Получаем авторов
|
||||||
|
const authors = await prisma.author.findMany({
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
displayName: true,
|
||||||
|
avatarUrl: true,
|
||||||
|
bio: true,
|
||||||
|
order: true,
|
||||||
|
okUrl: true,
|
||||||
|
vkUrl: true,
|
||||||
|
websiteUrl: true,
|
||||||
|
email: true,
|
||||||
|
userId: true,
|
||||||
|
isActive: true,
|
||||||
|
_count: {
|
||||||
|
select: {articles: true}, // Подсчёт количества статей автора
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
displayName: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
where: {order: {not: 0}},
|
||||||
|
orderBy: {
|
||||||
|
order: 'asc',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. Считаем сумму лайков по авторам
|
||||||
|
const likesPerAuthor = await prisma.article.groupBy({
|
||||||
|
by: ['authorId'],
|
||||||
|
_sum: {
|
||||||
|
likes: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 3. Строим map для быстрого доступа
|
||||||
|
const likesMap = new Map(
|
||||||
|
likesPerAuthor.map((item) => [item.authorId, item._sum.likes ?? 0])
|
||||||
|
);
|
||||||
|
|
||||||
|
return authors.map(author => ({
|
||||||
|
id: author.id,
|
||||||
|
displayName: author.displayName,
|
||||||
|
avatarUrl: author.avatarUrl,
|
||||||
|
bio: author.bio,
|
||||||
|
order: author.order,
|
||||||
|
okUrl: author.okUrl,
|
||||||
|
vkUrl: author.vkUrl,
|
||||||
|
websiteUrl: author.websiteUrl,
|
||||||
|
email: author.email,
|
||||||
|
articlesCount: author._count.articles, // Количество статей
|
||||||
|
userId: author.userId,
|
||||||
|
userDisplayName: author.user?.displayName || null,
|
||||||
|
isActive: author.isActive,
|
||||||
|
totalLikes: likesMap.get(author.id) ?? 0,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка получения авторов:', error);
|
||||||
|
throw new Error('Ошибка получения авторов');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ export const userService = {
|
|||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
bio: string | null;
|
bio: string | null;
|
||||||
permissions: JsonValue;
|
permissions: JsonValue;
|
||||||
|
Author: { id: string } | null;
|
||||||
}> = await prisma.user.findMany({
|
}> = await prisma.user.findMany({
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@ -22,6 +23,9 @@ export const userService = {
|
|||||||
avatarUrl: true,
|
avatarUrl: true,
|
||||||
bio: true,
|
bio: true,
|
||||||
permissions: true,
|
permissions: true,
|
||||||
|
Author: {
|
||||||
|
select: { id: true } // достаточно только id для проверки связи
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,7 +36,8 @@ export const userService = {
|
|||||||
return {
|
return {
|
||||||
...user,
|
...user,
|
||||||
permissions: permissions as UserPermissions,
|
permissions: permissions as UserPermissions,
|
||||||
} as User;
|
isLinkedToAuthor: !!user.Author,
|
||||||
|
} as User & { isLinkedToAuthor: boolean };
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Invalid permissions format for user ${user.id}`);
|
throw new Error(`Invalid permissions format for user ${user.id}`);
|
||||||
}
|
}
|
||||||
@ -43,39 +48,6 @@ export const userService = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getAuthors: async (): Promise<Author[]> => {
|
|
||||||
try {
|
|
||||||
const authors = await prisma.user.findMany({
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
email: true,
|
|
||||||
displayName: true,
|
|
||||||
avatarUrl: true,
|
|
||||||
bio: true,
|
|
||||||
_count: {
|
|
||||||
select: { articles: true }, // Подсчёт количества статей автора
|
|
||||||
},
|
|
||||||
},
|
|
||||||
where: { order: { not: 0 } },
|
|
||||||
orderBy: {
|
|
||||||
order: 'asc',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return authors.map(author => ({
|
|
||||||
id: author.id,
|
|
||||||
email: author.email,
|
|
||||||
displayName: author.displayName,
|
|
||||||
avatarUrl: author.avatarUrl,
|
|
||||||
bio: author.bio,
|
|
||||||
articlesCount: author._count.articles, // Количество статей
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка получения авторов:', error);
|
|
||||||
throw new Error('Ошибка получения авторов');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUserPermissions: async (
|
updateUserPermissions: async (
|
||||||
userId: string,
|
userId: string,
|
||||||
permissions: User['permissions']
|
permissions: User['permissions']
|
||||||
|
@ -19,9 +19,14 @@ export interface User {
|
|||||||
|
|
||||||
export interface Author {
|
export interface Author {
|
||||||
id: string;
|
id: string;
|
||||||
email: string;
|
email: string | null;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
articlesCount: number;
|
|
||||||
bio: string | null;
|
bio: string | null;
|
||||||
|
order: number;
|
||||||
|
okUrl: string | null;
|
||||||
|
vkUrl: string | null;
|
||||||
|
websiteUrl: string | null;
|
||||||
|
articlesCount: number;
|
||||||
|
userId?: string | null;
|
||||||
}
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Author } from "./auth";
|
||||||
|
|
||||||
export interface Article {
|
export interface Article {
|
||||||
id: string;
|
id: string;
|
||||||
importId: number;
|
importId: number;
|
||||||
@ -7,6 +9,7 @@ export interface Article {
|
|||||||
categoryId: number;
|
categoryId: number;
|
||||||
cityId: number;
|
cityId: number;
|
||||||
author: Author;
|
author: Author;
|
||||||
|
authorName: string;
|
||||||
coverImage: string;
|
coverImage: string;
|
||||||
images?: string[];
|
images?: string[];
|
||||||
imageSubs: string[];
|
imageSubs: string[];
|
||||||
@ -26,9 +29,3 @@ export interface GalleryImage {
|
|||||||
alt: string;
|
alt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Author {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
avatar: string;
|
|
||||||
bio: string;
|
|
||||||
}
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user