289 lines
7.8 KiB
TypeScript
289 lines
7.8 KiB
TypeScript
'use server';
|
|
|
|
import { db } from '@/lib/db';
|
|
import { revalidatePath } from 'next/cache';
|
|
|
|
function extractYoutubeId(url: string): string {
|
|
if (!url) return 'dQw4w9WgXcQ';
|
|
const trimmed = url.trim();
|
|
if (trimmed.includes('v=')) {
|
|
return trimmed.split('v=')[1].split('&')[0];
|
|
}
|
|
if (trimmed.includes('youtu.be/')) {
|
|
return trimmed.split('youtu.be/')[1].split('?')[0];
|
|
}
|
|
if (trimmed.includes('embed/')) {
|
|
return trimmed.split('embed/')[1].split('?')[0];
|
|
}
|
|
if (trimmed.length === 11) {
|
|
return trimmed;
|
|
}
|
|
return 'dQw4w9WgXcQ';
|
|
}
|
|
|
|
export async function getLessons(options?: { query?: string; category?: string }) {
|
|
try {
|
|
const { query, category } = options || {};
|
|
|
|
const where: any = {};
|
|
|
|
if (category) {
|
|
where.category = category;
|
|
}
|
|
|
|
if (query && query.trim() !== '') {
|
|
const q = query.trim();
|
|
where.OR = [
|
|
{ title: { contains: q, mode: 'insensitive' } },
|
|
{ summary: { contains: q, mode: 'insensitive' } },
|
|
{ tags: { hasSome: [q] } },
|
|
];
|
|
}
|
|
|
|
const lessons = await db.lesson.findMany({
|
|
where,
|
|
include: {
|
|
codeSnippets: true,
|
|
downloads: true,
|
|
chapters: true,
|
|
},
|
|
orderBy: {
|
|
createdAt: 'desc',
|
|
},
|
|
});
|
|
|
|
return lessons;
|
|
} catch (error) {
|
|
console.error('Error fetching lessons:', error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export async function getLessonBySlug(slug: string) {
|
|
try {
|
|
const lesson = await db.lesson.findUnique({
|
|
where: { slug },
|
|
include: {
|
|
codeSnippets: true,
|
|
downloads: true,
|
|
chapters: {
|
|
orderBy: { seconds: 'asc' },
|
|
},
|
|
},
|
|
});
|
|
|
|
if (lesson) {
|
|
await db.lesson.update({
|
|
where: { id: lesson.id },
|
|
data: { viewsCount: { increment: 1 } },
|
|
}).catch(err => console.error('Failed to increment view count:', err));
|
|
}
|
|
|
|
return lesson;
|
|
} catch (error) {
|
|
console.error('Error fetching lesson by slug:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function getDashboardStats() {
|
|
try {
|
|
const [totalLessons, aggregate, totalMessages] = await Promise.all([
|
|
db.lesson.count(),
|
|
db.lesson.aggregate({
|
|
_sum: {
|
|
viewsCount: true,
|
|
downloadCount: true,
|
|
},
|
|
}),
|
|
db.contactMessage.count(),
|
|
]);
|
|
|
|
return {
|
|
totalLessons,
|
|
totalViews: aggregate._sum.viewsCount || 0,
|
|
totalDownloads: aggregate._sum.downloadCount || 0,
|
|
totalMessages,
|
|
};
|
|
} catch (error) {
|
|
console.error('Error fetching dashboard stats:', error);
|
|
return {
|
|
totalLessons: 0,
|
|
totalViews: 0,
|
|
totalDownloads: 0,
|
|
totalMessages: 0,
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function createLesson(data: {
|
|
title: string;
|
|
youtubeUrl: string;
|
|
category: string;
|
|
summary: string;
|
|
duration?: string;
|
|
notesMarkdown?: string[];
|
|
codeSnippets?: { fileName: string; language: string; code: string }[];
|
|
downloads?: { title: string; type: string; url: string; size?: string }[];
|
|
chapters?: { time: string; seconds: number; title: string }[];
|
|
}) {
|
|
try {
|
|
const slug = data.title
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/[^\w\s-]/g, '')
|
|
.replace(/[\s_-]+/g, '-')
|
|
.replace(/^-+|-+$/g, '') + '-' + Date.now().toString().slice(-4);
|
|
|
|
const youtubeId = extractYoutubeId(data.youtubeUrl);
|
|
const thumbnailUrl = `https://img.youtube.com/vi/${youtubeId}/hqdefault.jpg`;
|
|
|
|
const codeSnippets = data.codeSnippets || [];
|
|
const downloads = data.downloads || [];
|
|
const chapters = data.chapters || [];
|
|
const notesMarkdown = data.notesMarkdown || [];
|
|
|
|
const newLesson = await db.lesson.create({
|
|
data: {
|
|
title: data.title,
|
|
slug,
|
|
youtubeId,
|
|
youtubeUrl: data.youtubeUrl || `https://www.youtube.com/watch?v=${youtubeId}`,
|
|
thumbnailUrl,
|
|
duration: data.duration || '15:00',
|
|
category: data.category,
|
|
tags: [data.category, 'Tutorial', 'Source Code'],
|
|
summary: data.summary || data.title,
|
|
notesMarkdown,
|
|
codeSnippets: {
|
|
create: codeSnippets.map((snip) => ({
|
|
fileName: snip.fileName || 'app/page.tsx',
|
|
language: snip.language || 'typescript',
|
|
code: snip.code,
|
|
})),
|
|
},
|
|
downloads: {
|
|
create: downloads.map((dl) => ({
|
|
title: dl.title,
|
|
type: dl.type || 'zip',
|
|
url: dl.url || '#',
|
|
size: dl.size || '1.0 MB',
|
|
})),
|
|
},
|
|
chapters: {
|
|
create: chapters.map((chap) => ({
|
|
time: chap.time,
|
|
seconds: chap.seconds || 0,
|
|
title: chap.title,
|
|
})),
|
|
},
|
|
},
|
|
});
|
|
|
|
revalidatePath('/[locale]');
|
|
revalidatePath('/[locale]/lessons');
|
|
return { success: true, lesson: newLesson };
|
|
} catch (error: any) {
|
|
console.error('Error creating lesson:', error);
|
|
return { success: false, error: error.message || 'Failed to create lesson in database' };
|
|
}
|
|
}
|
|
|
|
export async function updateLesson(id: string, data: {
|
|
title: string;
|
|
youtubeUrl: string;
|
|
category: string;
|
|
summary: string;
|
|
duration?: string;
|
|
notesMarkdown?: string[];
|
|
codeSnippets?: { fileName: string; language: string; code: string }[];
|
|
downloads?: { title: string; type: string; url: string; size?: string }[];
|
|
chapters?: { time: string; seconds: number; title: string }[];
|
|
}) {
|
|
try {
|
|
const youtubeId = extractYoutubeId(data.youtubeUrl);
|
|
const thumbnailUrl = `https://img.youtube.com/vi/${youtubeId}/hqdefault.jpg`;
|
|
|
|
const codeSnippets = data.codeSnippets || [];
|
|
const downloads = data.downloads || [];
|
|
const chapters = data.chapters || [];
|
|
const notesMarkdown = data.notesMarkdown || [];
|
|
|
|
// Delete existing sub-models and recreate
|
|
await Promise.all([
|
|
db.codeSnippet.deleteMany({ where: { lessonId: id } }),
|
|
db.resourceDownload.deleteMany({ where: { lessonId: id } }),
|
|
db.videoChapter.deleteMany({ where: { lessonId: id } }),
|
|
]);
|
|
|
|
const updatedLesson = await db.lesson.update({
|
|
where: { id },
|
|
data: {
|
|
title: data.title,
|
|
youtubeId,
|
|
youtubeUrl: data.youtubeUrl,
|
|
thumbnailUrl,
|
|
duration: data.duration || '15:00',
|
|
category: data.category,
|
|
summary: data.summary,
|
|
notesMarkdown,
|
|
codeSnippets: {
|
|
create: codeSnippets.map((snip) => ({
|
|
fileName: snip.fileName || 'app/page.tsx',
|
|
language: snip.language || 'typescript',
|
|
code: snip.code,
|
|
})),
|
|
},
|
|
downloads: {
|
|
create: downloads.map((dl) => ({
|
|
title: dl.title,
|
|
type: dl.type || 'zip',
|
|
url: dl.url || '#',
|
|
size: dl.size || '1.0 MB',
|
|
})),
|
|
},
|
|
chapters: {
|
|
create: chapters.map((chap) => ({
|
|
time: chap.time,
|
|
seconds: chap.seconds || 0,
|
|
title: chap.title,
|
|
})),
|
|
},
|
|
},
|
|
});
|
|
|
|
revalidatePath('/[locale]');
|
|
revalidatePath('/[locale]/lessons');
|
|
return { success: true, lesson: updatedLesson };
|
|
} catch (error: any) {
|
|
console.error('Error updating lesson:', error);
|
|
return { success: false, error: error.message || 'Failed to update lesson' };
|
|
}
|
|
}
|
|
|
|
export async function deleteLesson(id: string) {
|
|
try {
|
|
await db.lesson.delete({ where: { id } });
|
|
revalidatePath('/[locale]');
|
|
revalidatePath('/[locale]/lessons');
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Error deleting lesson:', error);
|
|
return { success: false };
|
|
}
|
|
}
|
|
|
|
export async function incrementDownloadCount(lessonId: string) {
|
|
try {
|
|
await db.lesson.update({
|
|
where: { id: lessonId },
|
|
data: { downloadCount: { increment: 1 } },
|
|
});
|
|
revalidatePath('/[locale]/dersler');
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error('Error incrementing download count:', error);
|
|
return { success: false };
|
|
}
|
|
}
|