Доработан импорт статей, сделано разделение пользователей на собственно пользователей и авторов.
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 {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
password String
|
||||
displayName String
|
||||
avatarUrl String
|
||||
bio String?
|
||||
isAdmin Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
isAdmin Boolean @default(false)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
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[]
|
||||
order Int @default(0)
|
||||
isActive Boolean @default(true)
|
||||
roles AuthorRole[] @default([])
|
||||
}
|
||||
|
||||
model Article {
|
||||
@ -35,7 +61,7 @@ model Article {
|
||||
likes Int @default(0)
|
||||
dislikes Int @default(0)
|
||||
publishedAt DateTime @default(now())
|
||||
author User @relation(fields: [authorId], references: [id])
|
||||
author Author @relation(fields: [authorId], references: [id])
|
||||
authorId String
|
||||
gallery GalleryImage[]
|
||||
isActive Boolean @default(false)
|
||||
|
@ -76,6 +76,25 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
|
||||
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({
|
||||
data: {
|
||||
title,
|
||||
@ -85,7 +104,7 @@ export async function createArticle(req: AuthRequest, res: Response) : Promise<v
|
||||
cityId,
|
||||
coverImage,
|
||||
readTime,
|
||||
authorId: req.user.id
|
||||
authorId: author.id
|
||||
},
|
||||
include: {
|
||||
author: {
|
||||
@ -296,10 +315,19 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
||||
|
||||
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 {
|
||||
for (const article of articles) {
|
||||
try {
|
||||
// Шаг 1: Создание статьи
|
||||
const authorId = authorMap.get(article.authorName) || article.author.id;
|
||||
|
||||
const newArticle = await prisma.article.create({
|
||||
data: {
|
||||
importId: article.importId,
|
||||
@ -314,7 +342,7 @@ export async function importArticles(req: AuthRequest, res: Response) : Promise<
|
||||
likes: article.likes || 0,
|
||||
dislikes: article.dislikes || 0,
|
||||
author: {
|
||||
connect: { id: article.author.id },
|
||||
connect: { id: authorId },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -1,14 +1,275 @@
|
||||
import { Response } from 'express';
|
||||
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> {
|
||||
try {
|
||||
const authors = await userService.getAuthors();
|
||||
const authors = await authorService.getAuthors();
|
||||
res.json(authors);
|
||||
} catch {
|
||||
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 { 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();
|
||||
|
||||
|
||||
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;
|
@ -1,12 +1,12 @@
|
||||
import { Response } from 'express';
|
||||
import { userService } from '../../../services/userService';
|
||||
import { authorService } from '../../../services/authorService';
|
||||
import { AuthRequest } from "../../../middleware/auth";
|
||||
|
||||
|
||||
// Изменить количество лайков
|
||||
export async function updateLikes(req: AuthRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const authors = await userService.getAuthors();
|
||||
const authors = await authorService.getAuthors();
|
||||
res.json(authors);
|
||||
} catch {
|
||||
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;
|
||||
bio: string | null;
|
||||
permissions: JsonValue;
|
||||
Author: { id: string } | null;
|
||||
}> = await prisma.user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
@ -22,6 +23,9 @@ export const userService = {
|
||||
avatarUrl: true,
|
||||
bio: true,
|
||||
permissions: true,
|
||||
Author: {
|
||||
select: { id: true } // достаточно только id для проверки связи
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -32,7 +36,8 @@ export const userService = {
|
||||
return {
|
||||
...user,
|
||||
permissions: permissions as UserPermissions,
|
||||
} as User;
|
||||
isLinkedToAuthor: !!user.Author,
|
||||
} as User & { isLinkedToAuthor: boolean };
|
||||
} else {
|
||||
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 (
|
||||
userId: string,
|
||||
permissions: User['permissions']
|
||||
|
@ -19,9 +19,14 @@ export interface User {
|
||||
|
||||
export interface Author {
|
||||
id: string;
|
||||
email: string;
|
||||
email: string | null;
|
||||
displayName: string;
|
||||
avatarUrl: string;
|
||||
articlesCount: number;
|
||||
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 {
|
||||
id: string;
|
||||
importId: number;
|
||||
@ -7,6 +9,7 @@ export interface Article {
|
||||
categoryId: number;
|
||||
cityId: number;
|
||||
author: Author;
|
||||
authorName: string;
|
||||
coverImage: string;
|
||||
images?: string[];
|
||||
imageSubs: string[];
|
||||
@ -26,9 +29,3 @@ export interface GalleryImage {
|
||||
alt: string;
|
||||
}
|
||||
|
||||
export interface Author {
|
||||
id: string;
|
||||
name: string;
|
||||
avatar: string;
|
||||
bio: string;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user