Add complete CKE Project implementation with news management system

This commit is contained in:
albivkt
2025-07-13 01:34:11 +03:00
parent c9317555ca
commit a84810c6b9
32 changed files with 8901 additions and 811 deletions

455
lib/graphql/resolvers.ts Normal file
View File

@ -0,0 +1,455 @@
import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
const prisma = new PrismaClient();
export const resolvers = {
Query: {
// News queries
news: async (_: any, { id }: { id: string }) => {
return await prisma.news.findUnique({
where: { id },
include: { author: true }
});
},
newsList: async (_: any, args: {
page?: number;
limit?: number;
category?: string;
search?: string;
featured?: boolean;
published?: boolean;
sortBy?: string;
sortOrder?: string;
}) => {
const {
page = 1,
limit = 10,
category,
search,
featured,
published = true,
sortBy = 'publishedAt',
sortOrder = 'desc'
} = args;
const skip = (page - 1) * limit;
const where: any = {};
if (published !== undefined) {
where.published = published;
}
if (category) {
where.category = category;
}
if (featured !== undefined) {
where.featured = featured;
}
if (search) {
where.OR = [
{ title: { contains: search, mode: 'insensitive' } },
{ summary: { contains: search, mode: 'insensitive' } },
{ content: { contains: search, mode: 'insensitive' } }
];
}
const orderBy: any = {};
orderBy[sortBy] = sortOrder;
const [news, total] = await Promise.all([
prisma.news.findMany({
where,
skip,
take: limit,
orderBy,
include: { author: true }
}),
prisma.news.count({ where })
]);
const totalPages = Math.ceil(total / limit);
return {
news,
total,
page,
limit,
totalPages,
hasNextPage: page < totalPages,
hasPrevPage: page > 1
};
},
newsCount: async (_: any, args: {
category?: string;
search?: string;
published?: boolean;
}) => {
const { category, search, published = true } = args;
const where: any = {};
if (published !== undefined) {
where.published = published;
}
if (category) {
where.category = category;
}
if (search) {
where.OR = [
{ title: { contains: search, mode: 'insensitive' } },
{ summary: { contains: search, mode: 'insensitive' } },
{ content: { contains: search, mode: 'insensitive' } }
];
}
return await prisma.news.count({ where });
},
// Category queries
categories: async () => {
return await prisma.category.findMany({
orderBy: { name: 'asc' }
});
},
category: async (_: any, { id }: { id: string }) => {
return await prisma.category.findUnique({
where: { id }
});
},
// User queries
user: async (_: any, { id }: { id: string }) => {
return await prisma.user.findUnique({
where: { id },
include: { news: true }
});
},
users: async () => {
return await prisma.user.findMany({
include: { news: true }
});
},
me: async (_: any, __: any, context: any) => {
if (!context.user) {
throw new Error('Not authenticated');
}
return await prisma.user.findUnique({
where: { id: context.user.id },
include: { news: true }
});
}
},
Mutation: {
// News mutations
createNews: async (_: any, { input }: { input: any }, context: any) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const news = await prisma.news.create({
data: {
...input,
authorId: context.user.id,
publishedAt: input.publishedAt ? new Date(input.publishedAt) : new Date()
},
include: { author: true }
});
return news;
},
updateNews: async (_: any, { id, input }: { id: string; input: any }, context: any) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const existingNews = await prisma.news.findUnique({
where: { id }
});
if (!existingNews) {
throw new Error('News not found');
}
if (existingNews.authorId !== context.user.id && context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
const updateData: any = { ...input };
if (input.publishedAt) {
updateData.publishedAt = new Date(input.publishedAt);
}
const news = await prisma.news.update({
where: { id },
data: updateData,
include: { author: true }
});
return news;
},
deleteNews: async (_: any, { id }: { id: string }, context: any) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const existingNews = await prisma.news.findUnique({
where: { id }
});
if (!existingNews) {
throw new Error('News not found');
}
if (existingNews.authorId !== context.user.id && context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
await prisma.news.delete({
where: { id }
});
return true;
},
toggleNewsPublished: async (_: any, { id }: { id: string }, context: any) => {
if (!context.user) {
throw new Error('Not authenticated');
}
const existingNews = await prisma.news.findUnique({
where: { id }
});
if (!existingNews) {
throw new Error('News not found');
}
if (existingNews.authorId !== context.user.id && context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
const news = await prisma.news.update({
where: { id },
data: { published: !existingNews.published },
include: { author: true }
});
return news;
},
toggleNewsFeatured: async (_: any, { id }: { id: string }, context: any) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
const existingNews = await prisma.news.findUnique({
where: { id }
});
if (!existingNews) {
throw new Error('News not found');
}
const news = await prisma.news.update({
where: { id },
data: { featured: !existingNews.featured },
include: { author: true }
});
return news;
},
incrementNewsViews: async (_: any, { id }: { id: string }) => {
const news = await prisma.news.update({
where: { id },
data: { views: { increment: 1 } },
include: { author: true }
});
return news;
},
likeNews: async (_: any, { id }: { id: string }) => {
const news = await prisma.news.update({
where: { id },
data: { likes: { increment: 1 } },
include: { author: true }
});
return news;
},
// Category mutations
createCategory: async (_: any, { input }: { input: any }, context: any) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
const category = await prisma.category.create({
data: input
});
return category;
},
updateCategory: async (_: any, { id, input }: { id: string; input: any }, context: any) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
const category = await prisma.category.update({
where: { id },
data: input
});
return category;
},
deleteCategory: async (_: any, { id }: { id: string }, context: any) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
await prisma.category.delete({
where: { id }
});
return true;
},
// User mutations
createUser: async (_: any, { input }: { input: any }, context: any) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
const hashedPassword = await bcrypt.hash(input.password, 12);
const user = await prisma.user.create({
data: {
...input,
password: hashedPassword
},
include: { news: true }
});
return user;
},
updateUser: async (_: any, { id, input }: { id: string; input: any }, context: any) => {
if (!context.user || (context.user.id !== id && context.user.role !== 'ADMIN')) {
throw new Error('Not authorized');
}
const updateData: any = { ...input };
if (input.password) {
updateData.password = await bcrypt.hash(input.password, 12);
}
const user = await prisma.user.update({
where: { id },
data: updateData,
include: { news: true }
});
return user;
},
deleteUser: async (_: any, { id }: { id: string }, context: any) => {
if (!context.user || context.user.role !== 'ADMIN') {
throw new Error('Not authorized');
}
await prisma.user.delete({
where: { id }
});
return true;
},
// Auth mutations
login: async (_: any, { email, password }: { email: string; password: string }) => {
const user = await prisma.user.findUnique({
where: { email }
});
if (!user) {
throw new Error('Invalid credentials');
}
const isValid = await bcrypt.compare(password, user.password);
if (!isValid) {
throw new Error('Invalid credentials');
}
const token = jwt.sign(
{ userId: user.id },
process.env.NEXTAUTH_SECRET || 'secret',
{ expiresIn: '7d' }
);
return {
token,
user
};
},
register: async (_: any, { input }: { input: any }) => {
const existingUser = await prisma.user.findFirst({
where: {
OR: [
{ email: input.email },
{ username: input.username }
]
}
});
if (existingUser) {
throw new Error('User already exists');
}
const hashedPassword = await bcrypt.hash(input.password, 12);
const user = await prisma.user.create({
data: {
...input,
password: hashedPassword
}
});
const token = jwt.sign(
{ userId: user.id },
process.env.NEXTAUTH_SECRET || 'secret',
{ expiresIn: '7d' }
);
return {
token,
user
};
},
logout: async () => {
// В GraphQL logout обычно обрабатывается на клиенте
// путем удаления токена из localStorage/cookies
return true;
}
}
};