import { PrismaClient } from '@prisma/client'; declare global { var prisma: PrismaClient | undefined; } const prisma = globalThis.prisma || new PrismaClient(); if (process.env.NODE_ENV !== 'production') { globalThis.prisma = prisma; } export default prisma; // Типы для работы с новостями export interface NewsItem { id: string; title: string; slug: string; summary: string; content: string; category: string; imageUrl?: string; featured: boolean; published: boolean; publishedAt: Date; createdAt: Date; updatedAt: Date; authorId?: string; author?: { id: string; name?: string; username: string; email: string; }; views: number; likes: number; tags: string[]; } export interface NewsCategory { id: string; name: string; slug: string; description?: string; color: string; createdAt: Date; updatedAt: Date; } export interface CreateNewsData { title: string; slug: string; summary: string; content: string; category: string; imageUrl?: string; featured?: boolean; published?: boolean; publishedAt?: Date; authorId?: string; tags?: string[]; } export interface UpdateNewsData { title?: string; slug?: string; summary?: string; content?: string; category?: string; imageUrl?: string; featured?: boolean; published?: boolean; publishedAt?: Date; tags?: string[]; } export interface NewsFilters { category?: string; search?: string; featured?: boolean; published?: boolean; authorId?: string; tags?: string[]; } export interface NewsPagination { page: number; limit: number; sortBy?: string; sortOrder?: 'asc' | 'desc'; } export interface NewsListResponse { news: NewsItem[]; total: number; page: number; limit: number; totalPages: number; hasNextPage: boolean; hasPrevPage: boolean; } // Утилиты для работы с новостями export class NewsService { static async getNews(id: string): Promise { return await prisma.news.findUnique({ where: { id }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }) as NewsItem | null; } static async getNewsBySlug(slug: string): Promise { return await prisma.news.findUnique({ where: { slug }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }) as NewsItem | null; } static async getNewsList( filters: NewsFilters = {}, pagination: NewsPagination = { page: 1, limit: 10 } ): Promise { const { category, search, featured, published = true, authorId, tags } = filters; const { page = 1, limit = 10, sortBy = 'publishedAt', sortOrder = 'desc' } = pagination; const skip = (page - 1) * limit; const where: any = {}; if (published !== undefined) { where.published = published; } if (category && category !== 'all') { where.category = category; } if (featured !== undefined) { where.featured = featured; } if (authorId) { where.authorId = authorId; } if (tags && tags.length > 0) { where.tags = { hasSome: tags }; } if (search) { where.OR = [ { title: { contains: search, mode: 'insensitive' } }, { summary: { contains: search, mode: 'insensitive' } }, { content: { contains: search, mode: 'insensitive' } }, { tags: { hasSome: [search] } } ]; } const orderBy: any = {}; orderBy[sortBy] = sortOrder; const [news, total] = await Promise.all([ prisma.news.findMany({ where, skip, take: limit, orderBy, include: { author: { select: { id: true, name: true, username: true, email: true } } } }), prisma.news.count({ where }) ]); const totalPages = Math.ceil(total / limit); return { news, total, page, limit, totalPages, hasNextPage: page < totalPages, hasPrevPage: page > 1 }; } static async createNews(data: CreateNewsData): Promise { // Преобразуем теги из массива в строку для сохранения в БД const tagsString = data.tags ? data.tags.join(',') : ''; const result = await prisma.news.create({ data: { ...data, tags: tagsString, publishedAt: data.publishedAt || new Date() }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }); // Преобразуем теги обратно в массив для возврата return { ...result, tags: result.tags ? result.tags.split(',').filter(tag => tag.trim()) : [] }; } static async updateNews(id: string, data: UpdateNewsData): Promise { // Преобразуем теги из массива в строку для сохранения в БД const updateData: any = { ...data }; if (data.tags) { updateData.tags = data.tags.join(','); } return await prisma.news.update({ where: { id }, data: updateData, include: { author: { select: { id: true, name: true, username: true, email: true } } } }) as NewsItem; } static async deleteNews(id: string): Promise { await prisma.news.delete({ where: { id } }); } static async incrementViews(id: string): Promise { return await prisma.news.update({ where: { id }, data: { views: { increment: 1 } }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }); } static async incrementLikes(id: string): Promise { return await prisma.news.update({ where: { id }, data: { likes: { increment: 1 } }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }); } static async togglePublished(id: string): Promise { const news = await prisma.news.findUnique({ where: { id } }); if (!news) throw new Error('News not found'); return await prisma.news.update({ where: { id }, data: { published: !news.published }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }); } static async toggleFeatured(id: string): Promise { const news = await prisma.news.findUnique({ where: { id } }); if (!news) throw new Error('News not found'); return await prisma.news.update({ where: { id }, data: { featured: !news.featured }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }); } static async getRelatedNews(newsId: string, category: string, limit = 3): Promise { return await prisma.news.findMany({ where: { AND: [ { id: { not: newsId } }, { category }, { published: true } ] }, take: limit, orderBy: { publishedAt: 'desc' }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }); } static async getPopularNews(limit = 5): Promise { return await prisma.news.findMany({ where: { published: true }, take: limit, orderBy: [ { views: 'desc' }, { likes: 'desc' }, { publishedAt: 'desc' } ], include: { author: { select: { id: true, name: true, username: true, email: true } } } }); } static async getFeaturedNews(limit = 3): Promise { return await prisma.news.findMany({ where: { featured: true, published: true }, take: limit, orderBy: { publishedAt: 'desc' }, include: { author: { select: { id: true, name: true, username: true, email: true } } } }); } static async getNewsStats(): Promise<{ total: number; published: number; draft: number; featured: number; totalViews: number; totalLikes: number; }> { const [ total, published, draft, featured, viewsResult, likesResult ] = await Promise.all([ prisma.news.count(), prisma.news.count({ where: { published: true } }), prisma.news.count({ where: { published: false } }), prisma.news.count({ where: { featured: true } }), prisma.news.aggregate({ _sum: { views: true } }), prisma.news.aggregate({ _sum: { likes: true } }) ]); return { total, published, draft, featured, totalViews: viewsResult._sum.views || 0, totalLikes: likesResult._sum.likes || 0 }; } } // Утилиты для работы с категориями export class CategoryService { static async getCategories(): Promise { return await prisma.category.findMany({ orderBy: { name: 'asc' } }); } static async getCategory(id: string): Promise { return await prisma.category.findUnique({ where: { id } }); } static async getCategoryBySlug(slug: string): Promise { return await prisma.category.findUnique({ where: { slug } }); } static async createCategory(data: { name: string; slug: string; description?: string; color?: string; }): Promise { return await prisma.category.create({ data }); } static async updateCategory(id: string, data: { name?: string; slug?: string; description?: string; color?: string; }): Promise { return await prisma.category.update({ where: { id }, data }); } static async deleteCategory(id: string): Promise { await prisma.category.delete({ where: { id } }); } } // Утилиты для миграции данных export class MigrationService { static async migrateFromStaticData(staticData: any[]): Promise { for (const item of staticData) { try { await prisma.news.upsert({ where: { slug: item.slug }, update: { title: item.title, summary: item.summary, content: item.content, category: item.category, imageUrl: item.imageUrl, featured: item.featured || false, published: item.published !== false, publishedAt: new Date(item.publishedAt), tags: item.tags || [] }, create: { title: item.title, slug: item.slug, summary: item.summary, content: item.content, category: item.category, imageUrl: item.imageUrl, featured: item.featured || false, published: item.published !== false, publishedAt: new Date(item.publishedAt), tags: item.tags || [] } }); } catch (error) { console.error(`Error migrating news item ${item.slug}:`, error); } } } static async createDefaultCategories(): Promise { const categories = [ { name: 'Новости компании', slug: 'company', color: 'bg-blue-500', description: 'Корпоративные новости и объявления' }, { name: 'Акции', slug: 'promotions', color: 'bg-green-500', description: 'Специальные предложения и акции' }, { name: 'Другое', slug: 'other', color: 'bg-purple-500', description: 'Прочие новости и события' } ]; for (const category of categories) { try { await prisma.category.upsert({ where: { slug: category.slug }, update: category, create: category }); } catch (error) { console.error(`Error creating category ${category.slug}:`, error); } } } static async createDefaultAdmin(): Promise { const { hashPassword } = await import('./auth'); try { await prisma.user.upsert({ where: { email: 'admin@ckeproekt.ru' }, update: {}, create: { email: 'admin@ckeproekt.ru', username: 'admin', password: await hashPassword('admin123'), role: 'ADMIN', name: 'Администратор' } }); } catch (error) { console.error('Error creating default admin:', error); } } }