mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 19:01:19 +00:00
Course publishing works for free, paid, drafts, published and any combination of them
This commit is contained in:
parent
8bd28d1a6a
commit
61a78f4b28
@ -35,7 +35,7 @@ CREATE TABLE "Purchase" (
|
||||
-- CreateTable
|
||||
CREATE TABLE "Lesson" (
|
||||
"id" TEXT NOT NULL,
|
||||
"courseId" TEXT NOT NULL,
|
||||
"courseId" TEXT,
|
||||
"resourceId" TEXT,
|
||||
"draftId" TEXT,
|
||||
"index" INTEGER NOT NULL,
|
||||
@ -140,7 +140,7 @@ ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_courseId_fkey" FOREIGN KEY ("cou
|
||||
ALTER TABLE "Purchase" ADD CONSTRAINT "Purchase_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "Resource"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lesson" ADD CONSTRAINT "Lesson_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
ALTER TABLE "Lesson" ADD CONSTRAINT "Lesson_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Lesson" ADD CONSTRAINT "Lesson_resourceId_fkey" FOREIGN KEY ("resourceId") REFERENCES "Resource"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
@ -44,8 +44,8 @@ model Purchase {
|
||||
|
||||
model Lesson {
|
||||
id String @id @default(uuid())
|
||||
courseId String
|
||||
course Course @relation(fields: [courseId], references: [id])
|
||||
courseId String?
|
||||
course Course? @relation(fields: [courseId], references: [id])
|
||||
resourceId String?
|
||||
resource Resource? @relation(fields: [resourceId], references: [id])
|
||||
draftId String?
|
||||
|
@ -73,6 +73,27 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
|
||||
});
|
||||
}
|
||||
|
||||
const handlePostLesson = async (lesson) => {
|
||||
console.log('lesson in handlePostLesson', lesson);
|
||||
let payload;
|
||||
|
||||
|
||||
if (lesson.d) {
|
||||
payload = {
|
||||
resourceId: lesson.d,
|
||||
index: lesson.index
|
||||
}
|
||||
} else if (lesson.draftId) {
|
||||
payload = {
|
||||
draftId: lesson.draftId,
|
||||
index: lesson.index
|
||||
}
|
||||
}
|
||||
|
||||
const response = await axios.post(`/api/lessons`, payload);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
const handlePostResource = async (resource) => {
|
||||
console.log('resourceeeeee:', resource.tags);
|
||||
const dTag = resource.tags.find(tag => tag[0] === 'd')[1];
|
||||
@ -125,64 +146,64 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
|
||||
await addSigner();
|
||||
}
|
||||
// Step 1: Process lessons
|
||||
const createdLessons = [];
|
||||
for (const lesson of processedLessons) {
|
||||
// publish any draft lessons and delete draft lessons
|
||||
const unpublished = lesson?.unpublished;
|
||||
if (unpublished && Object.keys(unpublished).length > 0) {
|
||||
const validationResult = validateEvent(unpublished);
|
||||
let savedLesson;
|
||||
if (lesson.unpublished) {
|
||||
const validationResult = validateEvent(lesson.unpublished);
|
||||
if (validationResult !== true) {
|
||||
console.error('Invalid event:', validationResult);
|
||||
showToast('error', 'Error', `Invalid event: ${validationResult}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const published = await unpublished.publish();
|
||||
const published = await lesson.unpublished.publish();
|
||||
savedLesson = await handlePostResource(lesson.unpublished);
|
||||
|
||||
const saved = await handlePostResource(unpublished);
|
||||
|
||||
console.log('saved', saved);
|
||||
|
||||
if (published && saved) {
|
||||
axios.delete(`/api/drafts/${lesson?.d}`)
|
||||
.then(res => {
|
||||
if (res.status === 204) {
|
||||
showToast('success', 'Success', 'Draft deleted successfully.');
|
||||
} else {
|
||||
showToast('error', 'Error', 'Failed to delete draft.');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
if (published && savedLesson) {
|
||||
const deleted = await axios.delete(`/api/drafts/${lesson.d}`);
|
||||
if (deleted && deleted.status === 204) {
|
||||
const savedLesson = await handlePostLesson(lesson);
|
||||
if (savedLesson) {
|
||||
createdLessons.push(savedLesson);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const savedLesson = await handlePostLesson(lesson);
|
||||
if (savedLesson) {
|
||||
createdLessons.push(savedLesson);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('createdLessons', createdLessons);
|
||||
|
||||
// Step 2: Create and publish course
|
||||
const courseEvent = createCourseEvent(newCourseId, processedEvent.title, processedEvent.summary, processedEvent.image, processedLessons, processedEvent.price);
|
||||
const published = await courseEvent.publish();
|
||||
|
||||
console.log('published', published);
|
||||
|
||||
if (!published) {
|
||||
throw new Error('Failed to publish course');
|
||||
}
|
||||
|
||||
// Step 3: Save course to db
|
||||
await axios.post('/api/courses', {
|
||||
const courseData = {
|
||||
id: newCourseId,
|
||||
resources: {
|
||||
connect: processedLessons.map(lesson => ({ id: lesson?.d }))
|
||||
lessons: {
|
||||
connect: createdLessons.map(lesson => ({ id: lesson.id }))
|
||||
},
|
||||
noteId: courseEvent.id,
|
||||
user: {
|
||||
connect: { id: user.id }
|
||||
},
|
||||
price: processedEvent?.price || 0
|
||||
});
|
||||
};
|
||||
|
||||
// step 4: Update all resources to have the course id
|
||||
await Promise.all(processedLessons.map(lesson => axios.put(`/api/resources/${lesson?.d}`, { courseId: newCourseId })));
|
||||
const createdCourse = await axios.post('/api/courses', courseData);
|
||||
|
||||
// Step 4: Update all lessons to have the course id
|
||||
await Promise.all(createdLessons.map(lesson => axios.put(`/api/lessons/${lesson.id}`, { courseId: newCourseId })));
|
||||
|
||||
// Step 5: Delete draft
|
||||
await axios.delete(`/api/courses/drafts/${processedEvent.id}`);
|
||||
@ -292,13 +313,15 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
|
||||
d: lesson?.id,
|
||||
kind: lesson?.price ? 30402 : 30023,
|
||||
pubkey: unsignedEvent.pubkey,
|
||||
index: lesson.index,
|
||||
unpublished: unsignedEvent
|
||||
}]);
|
||||
} else {
|
||||
setProcessedLessons(prev => [...prev, {
|
||||
d: lesson?.d,
|
||||
kind: lesson?.price ? 30402 : 30023,
|
||||
pubkey: lesson.pubkey
|
||||
pubkey: lesson.pubkey,
|
||||
index: lesson.index
|
||||
}]);
|
||||
}
|
||||
});
|
||||
@ -323,6 +346,9 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
|
||||
</div>
|
||||
<h1 className='text-4xl mt-6'>{processedEvent?.title}</h1>
|
||||
<p className='text-xl mt-6'>{processedEvent?.summary}</p>
|
||||
{processedEvent?.price && (
|
||||
<p className='text-lg mt-6'>Price: {processedEvent.price} sats</p>
|
||||
)}
|
||||
<div className='flex flex-row w-full mt-6 items-center'>
|
||||
<Image
|
||||
alt="avatar thumbnail"
|
||||
|
@ -76,6 +76,9 @@ const DraftCourseLesson = ({ lesson, course }) => {
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
{lesson?.price && (
|
||||
<p className='text-lg mt-6'>Price: {lesson.price} sats</p>
|
||||
)}
|
||||
<div className='flex flex-row w-full mt-6 items-center'>
|
||||
<Image
|
||||
alt="avatar thumbnail"
|
||||
@ -101,7 +104,7 @@ const DraftCourseLesson = ({ lesson, course }) => {
|
||||
<div className='flex flex-row w-full mt-6 items-center'>
|
||||
{isPublished ? (
|
||||
<>
|
||||
<Message severity="success" text="published" />
|
||||
<Message severity="success" text="published" className="w-auto m-2" />
|
||||
<Button onClick={() => router.push(`/details/${lesson.id}`)} label="View" outlined className="w-auto m-2" />
|
||||
<Button onClick={() => router.push(`/details/${lesson.id}/edit`)} label="Edit" severity='warning' outlined className="w-auto m-2" />
|
||||
</>
|
||||
|
@ -26,7 +26,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent }) => {
|
||||
}
|
||||
|
||||
const filterContent = (content) => {
|
||||
const contentPrice = content.price || 0;
|
||||
const contentPrice = content.price || content.tags.find(tag => tag[0] === 'price')?.[1] || 0;
|
||||
return isPaidCourse ? contentPrice > 0 : true;
|
||||
};
|
||||
|
||||
|
@ -40,22 +40,14 @@ export const createCourse = async (data) => {
|
||||
data: {
|
||||
id: data.id,
|
||||
noteId: data.noteId,
|
||||
user: { connect: { id: data.userId } },
|
||||
price: data.price,
|
||||
user: { connect: { id: data.user.connect.id } },
|
||||
lessons: {
|
||||
create: data.lessons.map((lesson, index) => ({
|
||||
resourceId: lesson.resourceId,
|
||||
draftId: lesson.draftId,
|
||||
index: index
|
||||
}))
|
||||
connect: data.lessons.connect
|
||||
}
|
||||
},
|
||||
include: {
|
||||
lessons: {
|
||||
include: {
|
||||
resource: true,
|
||||
draft: true
|
||||
}
|
||||
},
|
||||
lessons: true,
|
||||
user: true
|
||||
}
|
||||
});
|
||||
|
@ -55,6 +55,7 @@ const DraftCourse = () => {
|
||||
if (isDraft) {
|
||||
const parsedLessonObject = {
|
||||
...lesson?.draft,
|
||||
index: lesson.index,
|
||||
author: session.user
|
||||
}
|
||||
return parsedLessonObject;
|
||||
@ -68,6 +69,7 @@ const DraftCourse = () => {
|
||||
const author = await fetchAuthor(event.pubkey);
|
||||
return {
|
||||
...parseEvent(event),
|
||||
index: lesson.index,
|
||||
author
|
||||
};
|
||||
}
|
||||
|
@ -106,6 +106,14 @@ const Course = () => {
|
||||
}
|
||||
}, [lessonIds, ndk, fetchAuthor]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('lessons', lessons);
|
||||
}, [lessons]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('lessonIds', lessonIds);
|
||||
}, [lessonIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (course?.price && course?.price > 0) {
|
||||
setPaidCourse(true);
|
||||
|
Loading…
x
Reference in New Issue
Block a user