Aug 18, 2024

draft attack

improved flow hopefully
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { useRouter } from 'next/router';
import { useImageProxy } from '@/hooks/useImageProxy';
import { Tag } from 'primereact/tag';
import { Button } from 'primereact/button';
import Image from 'next/image';
import dynamic from 'next/dynamic';
import axios from 'axios';
import { nip04, nip19 } from 'nostr-tools';
import { v4 as uuidv4 } from 'uuid';
import { useSession } from 'next-auth/react';
import { useNDKContext } from "@/context/NDKContext";
import { NDKEvent } from "@nostr-dev-kit/ndk";
import { findKind0Fields } from '@/utils/nostr';
import { useToast } from '@/hooks/useToast';
import 'primeicons/primeicons.css';

const MDDisplay = dynamic(() => import("@uiw/react-markdown-preview"), { ssr: false });

// ... keep the validateEvent function as is ...

export default function DraftCourseDetails({ processedEvent, draftId, lessons }) {
    // ... keep state declarations and hooks as is ...

    const fetchAuthor = useCallback(async (pubkey) => {
        if (!pubkey) return;
        const author = await ndk.getUser({ pubkey });
        const profile = await author.fetchProfile();
        const fields = await findKind0Fields(profile);
        if (fields) setAuthor(fields);
    }, [ndk]);

    useEffect(() => {
        if (processedEvent) fetchAuthor(processedEvent?.user?.pubkey);
    }, [fetchAuthor, processedEvent]);

    useEffect(() => {
        if (session) setUser(session.user);
    }, [session]);

    const handleDelete = async () => {
        try {
            await axios.delete(`/api/courses/drafts/${processedEvent.id}`);
            showToast('success', 'Success', 'Draft Course deleted successfully');
            router.push('/');
        } catch (error) {
            showToast('error', 'Error', 'Failed to delete draft course');
        }
    };

    const handlePostResource = async (resource) => {
        // ... keep this function as is ...
    };

    const createCourseEvent = (courseId, title, summary, coverImage, lessons, price) => {
        const event = new NDKEvent(ndk);
        event.kind = 30004;
        event.content = "";
        event.tags = [
            ['d', courseId],
            ['name', title],
            ['picture', coverImage],
            ['image', coverImage],
            ['description', summary],
            ['l', "Education"],
            ['price', price.toString()],
            ...lessons.map((lesson) => ['a', `${lesson.kind}:${lesson.pubkey}:${lesson.d}`]),
        ];
        return event;
    };

    const handleSubmit = async (e) => {
        e.preventDefault();
        const newCourseId = uuidv4();

        try {
            if (!ndk.signer) await addSigner();

            // Process lessons
            await Promise.all(processedLessons.map(async (lesson) => {
                const unpublished = lesson?.unpublished;
                if (unpublished && Object.keys(unpublished).length > 0) {
                    const validationResult = validateEvent(unpublished);
                    if (validationResult !== true) {
                        throw new Error(`Invalid event: ${validationResult}`);
                    }

                    const published = await unpublished.publish();
                    const saved = await handlePostResource(unpublished);

                    if (published && saved) {
                        await axios.delete(`/api/drafts/${lesson?.d}`);
                    }
                }
            }));

            // Create and publish course
            const courseEvent = createCourseEvent(newCourseId, processedEvent.title, processedEvent.summary, processedEvent.image, processedLessons, processedEvent.price);
            const published = await courseEvent.publish();

            if (!published) {
                throw new Error('Failed to publish course');
            }

            // Save course to db
            await axios.post('/api/courses', {
                id: newCourseId,
                resources: {
                    connect: processedLessons.map(lesson => ({ id: lesson?.d }))
                },
                noteId: courseEvent.id,
                user: {
                    connect: { id: user.id }
                },
                price: processedEvent?.price || 0
            });

            // Update resources with course id
            await Promise.all(processedLessons.map(lesson => 
                axios.put(`/api/resources/${lesson?.d}`, { courseId: newCourseId })
            ));

            // Delete draft
            await axios.delete(`/api/courses/drafts/${processedEvent.id}`);

            showToast('success', 'Success', 'Course created successfully');
            router.push(`/course/${courseEvent.id}`);

        } catch (error) {
            console.error('Error creating course:', error);
            showToast('error', 'Error', error.message || 'Failed to create course. Please try again.');
        }
    };

    useEffect(() => {
        const buildEvent = async (draft) => {
            // ... keep this function as is ...
        };

        const buildDraftEvent = async (lesson) => {
            const { unsignedEvent } = await buildEvent(lesson);
            return unsignedEvent;
        };

        const processLessons = async () => {
            if (!hasRunEffect.current && lessons.length > 0 && user && author) {
                hasRunEffect.current = true;
                
                const processedLessons = await Promise.all(lessons.map(async (lesson) => {
                    const isDraft = !lesson?.pubkey;
                    if (isDraft) {
                        const unsignedEvent = await buildDraftEvent(lesson);
                        return {
                            d: lesson?.id,
                            kind: lesson?.price ? 30402 : 30023,
                            pubkey: unsignedEvent.pubkey,
                            unpublished: unsignedEvent
                        };
                    } else {
                        return {
                            d: lesson?.d,
                            kind: lesson?.price ? 30402 : 30023,
                            pubkey: lesson.pubkey
                        };
                    }
                }));

                setProcessedLessons(processedLessons);
            }
        };

        processLessons();
    }, [lessons, user, author, ndk]);

    // ... keep the return statement (JSX) as is ...
}