Course publishing works for free, paid, drafts, published and any combination of them

This commit is contained in:
austinkelsay 2024-08-25 12:12:55 -05:00
parent 8bd28d1a6a
commit 61a78f4b28
8 changed files with 79 additions and 48 deletions

View File

@ -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;

View File

@ -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?

View File

@ -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"

View File

@ -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" />
</>

View File

@ -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;
};

View File

@ -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
}
});

View File

@ -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
};
}

View File

@ -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);