Files
Nexus_Mat/backend/services/materialService.ts

374 lines
11 KiB
TypeScript

import prisma from '../../lib/prisma';
import { MaterialDTO, MaterialType } from '../../types';
export const MaterialService = {
/**
* Fetch all materials with relations (Author, Comments)
*/
async getAllMaterials(filterType?: MaterialType, page = 1, limit = 20, search?: string) {
const where: any = filterType ? { type: filterType } : {};
if (search && search.trim()) {
const q = search.trim();
where.OR = [
{ title: { contains: q, mode: 'insensitive' } },
{ description: { contains: q, mode: 'insensitive' } },
{ tags: { some: { name: { contains: q, mode: 'insensitive' } } } }
];
}
const [materials, total] = await Promise.all([
prisma.material.findMany({
where,
include: {
author: {
select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true }
},
comments: {
include: {
author: { select: { id: true, username: true, role: true, status: true, createdAt: true, lastLogin: true, avatarUrl: true } }
},
orderBy: { createdAt: 'desc' }
},
favorites: true, // To count them
tags: true
},
orderBy: { createdAt: 'desc' },
skip: (page - 1) * limit,
take: limit
}),
prisma.material.count({ where })
]);
// Transform Prisma Result to DTO
const items = materials.map(m => ({
id: m.id,
title: m.title,
description: m.description,
type: m.type as MaterialType,
contentUrl: m.contentUrl || undefined,
codeSnippet: m.codeSnippet || undefined,
language: m.language || undefined,
author: {
...m.author,
role: m.author.role as any,
createdAt: m.author.createdAt.toISOString(),
lastLogin: m.author.lastLogin?.toISOString() || '',
avatarUrl: m.author.avatarUrl || ''
},
stats: {
views: m.views,
downloads: m.downloads,
favorites: m.favorites.length
},
tags: m.tags.map(t => t.name),
createdAt: m.createdAt.toISOString(),
comments: m.comments.map(c => ({
id: c.id,
content: c.content,
createdAt: c.createdAt.toISOString(),
author: {
...c.author,
role: c.author.role as any,
createdAt: c.author.createdAt.toISOString(),
lastLogin: c.author.lastLogin?.toISOString() || '',
avatarUrl: c.author.avatarUrl || ''
}
}))
}));
return { items, total };
},
async getMaterialsByAuthor(userId: string, page = 1, limit = 20) {
const [materials, total] = await Promise.all([
prisma.material.findMany({
where: { authorId: userId },
include: {
author: {
select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true }
},
comments: {
include: {
author: { select: { id: true, username: true, role: true, status: true, createdAt: true, lastLogin: true, avatarUrl: true } }
},
orderBy: { createdAt: 'desc' }
},
favorites: true,
tags: true
},
orderBy: { createdAt: 'desc' },
skip: (page - 1) * limit,
take: limit
}),
prisma.material.count({ where: { authorId: userId } })
]);
const items = materials.map(m => ({
id: m.id,
title: m.title,
description: m.description,
type: m.type as MaterialType,
contentUrl: m.contentUrl || undefined,
codeSnippet: m.codeSnippet || undefined,
language: m.language || undefined,
author: {
...m.author,
role: m.author.role as any,
createdAt: m.author.createdAt.toISOString(),
lastLogin: m.author.lastLogin?.toISOString() || '',
avatarUrl: m.author.avatarUrl || ''
},
stats: {
views: m.views,
downloads: m.downloads,
favorites: m.favorites.length
},
tags: m.tags.map(t => t.name),
createdAt: m.createdAt.toISOString(),
comments: m.comments.map(c => ({
id: c.id,
content: c.content,
createdAt: c.createdAt.toISOString(),
author: {
...c.author,
role: c.author.role as any,
createdAt: c.author.createdAt.toISOString(),
lastLogin: c.author.lastLogin?.toISOString() || '',
avatarUrl: c.author.avatarUrl || ''
}
}))
}));
return { items, total };
},
async getFavoritedMaterialsByUser(userId: string, page = 1, limit = 20) {
const where = { favorites: { some: { userId } } };
const [materials, total] = await Promise.all([
prisma.material.findMany({
where: { favorites: { some: { userId } } },
include: {
author: {
select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true }
},
comments: {
include: {
author: { select: { id: true, username: true, role: true, status: true, createdAt: true, lastLogin: true, avatarUrl: true } }
},
orderBy: { createdAt: 'desc' }
},
favorites: true,
tags: true
},
orderBy: { createdAt: 'desc' },
skip: (page - 1) * limit,
take: limit
}),
prisma.material.count({ where })
]);
const items = materials.map(m => ({
id: m.id,
title: m.title,
description: m.description,
type: m.type as MaterialType,
contentUrl: m.contentUrl || undefined,
codeSnippet: m.codeSnippet || undefined,
language: m.language || undefined,
author: {
...m.author,
role: m.author.role as any,
createdAt: m.author.createdAt.toISOString(),
lastLogin: m.author.lastLogin?.toISOString() || '',
avatarUrl: m.author.avatarUrl || ''
},
stats: {
views: m.views,
downloads: m.downloads,
favorites: m.favorites.length
},
tags: m.tags.map(t => t.name),
createdAt: m.createdAt.toISOString(),
comments: m.comments.map(c => ({
id: c.id,
content: c.content,
createdAt: c.createdAt.toISOString(),
author: {
...c.author,
role: c.author.role as any,
createdAt: c.author.createdAt.toISOString(),
lastLogin: c.author.lastLogin?.toISOString() || '',
avatarUrl: c.author.avatarUrl || ''
}
}))
}));
return { items, total };
},
/**
* Create a new Material
*/
async createMaterial(userId: string, data: any) {
return prisma.material.create({
data: {
title: data.title,
description: data.description,
type: data.type,
codeSnippet: data.codeSnippet,
contentUrl: data.contentUrl,
language: data.language || 'text',
authorId: userId,
tags: {
connectOrCreate: data.tags.map((tag: string) => ({
where: { name: tag },
create: { name: tag }
}))
}
}
});
},
async deleteMaterial(id: string) {
return prisma.material.delete({ where: { id } });
},
async incrementView(id: string) {
return prisma.material.update({
where: { id },
data: { views: { increment: 1 } }
});
},
/**
* Get material by ID with all relations
*/
async getMaterialById(id: string): Promise<MaterialDTO | null> {
const material = await prisma.material.findUnique({
where: { id },
include: {
author: {
select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true }
},
comments: {
include: {
author: { select: { id: true, username: true, role: true, status: true, createdAt: true, lastLogin: true, avatarUrl: true } }
},
orderBy: { createdAt: 'desc' }
},
favorites: true,
tags: true
}
});
if (!material) return null;
// Also increment view count
await this.incrementView(id);
// Transform to DTO
return {
id: material.id,
title: material.title,
description: material.description,
type: material.type as MaterialType,
contentUrl: material.contentUrl || undefined,
codeSnippet: material.codeSnippet || undefined,
language: material.language || undefined,
author: {
...material.author,
role: material.author.role as any,
createdAt: material.author.createdAt.toISOString(),
lastLogin: material.author.lastLogin?.toISOString() || '',
avatarUrl: material.author.avatarUrl || ''
},
stats: {
views: material.views + 1, // Include the increment we just did
downloads: material.downloads,
favorites: material.favorites.length
},
tags: material.tags.map(t => t.name),
createdAt: material.createdAt.toISOString(),
comments: material.comments.map(c => ({
id: c.id,
content: c.content,
createdAt: c.createdAt.toISOString(),
author: {
...c.author,
role: c.author.role as any,
createdAt: c.author.createdAt.toISOString(),
lastLogin: c.author.lastLogin?.toISOString() || '',
avatarUrl: c.author.avatarUrl || ''
}
}))
};
},
/**
* Add a comment to a material
*/
async addComment(materialId: string, userId: string, content: string) {
return prisma.comment.create({
data: {
content,
authorId: userId,
materialId
},
include: {
author: { select: { id: true, username: true, avatarUrl: true, role: true, status: true, createdAt: true, lastLogin: true } }
}
});
},
/**
* Toggle favorite status for a material
* Returns the new favorite count
*/
async toggleFavorite(materialId: string, userId: string): Promise<number> {
// Check if already favorited
const existing = await prisma.favorite.findUnique({
where: {
userId_materialId: {
userId,
materialId
}
}
});
if (existing) {
// Remove favorite
await prisma.favorite.delete({
where: {
userId_materialId: {
userId,
materialId
}
}
});
} else {
// Add favorite
await prisma.favorite.create({
data: {
userId,
materialId
}
});
}
// Return new favorite count
const count = await prisma.favorite.count({
where: { materialId }
});
return count;
},
/**
* Increment download count
*/
async incrementDownload(id: string) {
return prisma.material.update({
where: { id },
data: { downloads: { increment: 1 } }
});
}
};