mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-04-19 19:01:19 +00:00
fix course and lesson tracking for paid courses, add usercourses and userlessons into session
This commit is contained in:
parent
eb1b8675b9
commit
215a00e593
@ -34,7 +34,11 @@ export default function CourseDetailsNew({ processedEvent, paidCourse, lessons,
|
||||
const isMobileView = windowWidth <= 768;
|
||||
const { ndk } = useNDKContext();
|
||||
|
||||
const { isCompleted } = useTrackCourse({courseId: processedEvent?.d});
|
||||
const { isCompleted } = useTrackCourse({
|
||||
courseId: processedEvent?.d,
|
||||
paidCourse,
|
||||
decryptionPerformed
|
||||
});
|
||||
|
||||
const fetchAuthor = useCallback(async (pubkey) => {
|
||||
if (!pubkey) return;
|
||||
|
@ -34,6 +34,8 @@ const DocumentLesson = ({ lesson, course, decryptionPerformed, isPaid, setComple
|
||||
lessonId: lesson?.d,
|
||||
courseId: course?.d,
|
||||
readTime: readTime,
|
||||
paidCourse: isPaid,
|
||||
decryptionPerformed: decryptionPerformed,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -35,7 +35,9 @@ const VideoLesson = ({ lesson, course, decryptionPerformed, isPaid, setCompleted
|
||||
lessonId: lesson?.d,
|
||||
videoDuration,
|
||||
courseId: course?.d,
|
||||
videoPlayed
|
||||
videoPlayed,
|
||||
paidCourse: isPaid,
|
||||
decryptionPerformed
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -7,11 +7,21 @@ const lnAddress = process.env.NEXT_PUBLIC_LIGHTNING_ADDRESS;
|
||||
export const getAllUsers = async () => {
|
||||
return await prisma.user.findMany({
|
||||
include: {
|
||||
role: true, // Include related role
|
||||
role: true,
|
||||
purchased: {
|
||||
include: {
|
||||
course: true, // Include course details in purchases
|
||||
resource: true, // Include resource details in purchases
|
||||
course: true,
|
||||
resource: true,
|
||||
},
|
||||
},
|
||||
userCourses: {
|
||||
include: {
|
||||
course: true,
|
||||
},
|
||||
},
|
||||
userLessons: {
|
||||
include: {
|
||||
lesson: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -22,11 +32,21 @@ export const getUserById = async (id) => {
|
||||
return await prisma.user.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
role: true, // Include related role
|
||||
role: true,
|
||||
purchased: {
|
||||
include: {
|
||||
course: true, // Include course details in purchases
|
||||
resource: true, // Include resource details in purchases
|
||||
course: true,
|
||||
resource: true,
|
||||
},
|
||||
},
|
||||
userCourses: {
|
||||
include: {
|
||||
course: true,
|
||||
},
|
||||
},
|
||||
userLessons: {
|
||||
include: {
|
||||
lesson: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -37,11 +57,21 @@ export const getUserByPubkey = async (pubkey) => {
|
||||
return await prisma.user.findUnique({
|
||||
where: { pubkey },
|
||||
include: {
|
||||
role: true, // Include related role
|
||||
role: true,
|
||||
purchased: {
|
||||
include: {
|
||||
course: true, // Include course details in purchases
|
||||
resource: true, // Include resource details in purchases
|
||||
course: true,
|
||||
resource: true,
|
||||
},
|
||||
},
|
||||
userCourses: {
|
||||
include: {
|
||||
course: true,
|
||||
},
|
||||
},
|
||||
userLessons: {
|
||||
include: {
|
||||
lesson: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2,24 +2,30 @@ import React, {useState, useEffect, useRef, useCallback} from 'react';
|
||||
import {useSession} from 'next-auth/react';
|
||||
import axios from 'axios';
|
||||
|
||||
const useTrackCourse = ({courseId}) => {
|
||||
const useTrackCourse = ({courseId, paidCourse, decryptionPerformed}) => {
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const {data: session} = useSession();
|
||||
const {data: session, update} = useSession();
|
||||
const completedRef = useRef(false);
|
||||
|
||||
const checkOrCreateUserCourse = useCallback(async () => {
|
||||
if (!session?.user) return false;
|
||||
if (!session?.user || !courseId) return false;
|
||||
try {
|
||||
const response = await axios.get(`/api/users/${session.user.id}/courses/${courseId}`);
|
||||
if (response.status === 200 && response?.data) {
|
||||
setIsCompleted(true);
|
||||
completedRef.current = true;
|
||||
} else if (response.status === 204) {
|
||||
await axios.post(`/api/users/${session.user.id}/courses?courseSlug=${courseId}`, {
|
||||
completed: false,
|
||||
started: true,
|
||||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
// Only create a new UserCourse entry if it's a free course or if decryption has been performed for a paid course
|
||||
if (paidCourse === false || (paidCourse && decryptionPerformed)) {
|
||||
console.log("creating new UserCourse entry");
|
||||
await axios.post(`/api/users/${session.user.id}/courses?courseSlug=${courseId}`, {
|
||||
completed: false,
|
||||
started: true,
|
||||
startedAt: new Date().toISOString(),
|
||||
});
|
||||
// Call session update after creating a new UserCourse entry
|
||||
await update();
|
||||
}
|
||||
|
||||
setIsCompleted(false);
|
||||
return false;
|
||||
@ -31,13 +37,13 @@ const useTrackCourse = ({courseId}) => {
|
||||
console.error('Error checking or creating UserCourse:', error);
|
||||
return false;
|
||||
}
|
||||
}, [session, courseId]);
|
||||
}, [courseId, paidCourse, decryptionPerformed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!completedRef.current && courseId) {
|
||||
if (!completedRef.current && courseId && session?.user) {
|
||||
checkOrCreateUserCourse();
|
||||
}
|
||||
}, [checkOrCreateUserCourse, courseId]);
|
||||
}, [courseId]);
|
||||
|
||||
return {isCompleted};
|
||||
};
|
||||
|
@ -2,13 +2,13 @@ import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import axios from 'axios';
|
||||
|
||||
const useTrackDocumentLesson = ({ lessonId, courseId, readTime }) => {
|
||||
const useTrackDocumentLesson = ({ lessonId, courseId, readTime, paidCourse, decryptionPerformed }) => {
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const [timeSpent, setTimeSpent] = useState(0);
|
||||
const [isTracking, setIsTracking] = useState(false);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const timerRef = useRef(null);
|
||||
const { data: session } = useSession();
|
||||
const { data: session, update } = useSession();
|
||||
const completedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -31,11 +31,16 @@ const useTrackDocumentLesson = ({ lessonId, courseId, readTime }) => {
|
||||
return false;
|
||||
}
|
||||
} else if (response.status === 204) {
|
||||
await axios.post(`/api/users/${session.user.id}/lessons?courseId=${courseId}`, {
|
||||
resourceId: lessonId,
|
||||
opened: true,
|
||||
openedAt: new Date().toISOString(),
|
||||
});
|
||||
// Only create a new UserLesson entry if it's a free course or if decryption has been performed for a paid course
|
||||
if (paidCourse === false || (paidCourse && decryptionPerformed)) {
|
||||
await axios.post(`/api/users/${session.user.id}/lessons?courseId=${courseId}`, {
|
||||
resourceId: lessonId,
|
||||
opened: true,
|
||||
openedAt: new Date().toISOString(),
|
||||
});
|
||||
// Call session update after creating a new UserLesson entry
|
||||
await update();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
console.error('Error checking or creating UserLesson:', response.statusText);
|
||||
@ -45,7 +50,7 @@ const useTrackDocumentLesson = ({ lessonId, courseId, readTime }) => {
|
||||
console.error('Error checking or creating UserLesson:', error);
|
||||
return false;
|
||||
}
|
||||
}, [session, lessonId, courseId]);
|
||||
}, [session, lessonId, courseId, update, paidCourse, decryptionPerformed]);
|
||||
|
||||
const markLessonAsCompleted = useCallback(async () => {
|
||||
if (!session?.user || completedRef.current) return;
|
||||
@ -60,20 +65,22 @@ const useTrackDocumentLesson = ({ lessonId, courseId, readTime }) => {
|
||||
if (response.status === 200) {
|
||||
setIsCompleted(true);
|
||||
setIsTracking(false);
|
||||
// Call session update after marking the lesson as completed
|
||||
await update();
|
||||
} else {
|
||||
console.error('Failed to mark lesson as completed:', response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error marking lesson as completed:', error);
|
||||
}
|
||||
}, [lessonId, courseId, session]);
|
||||
}, [lessonId, courseId, session, update]);
|
||||
|
||||
useEffect(() => {
|
||||
const initializeTracking = async () => {
|
||||
if (isAdmin) return; // Skip tracking for admin users
|
||||
|
||||
const alreadyCompleted = await checkOrCreateUserLesson();
|
||||
if (!alreadyCompleted && !completedRef.current) {
|
||||
if (!alreadyCompleted && !completedRef.current && (!paidCourse || (paidCourse && decryptionPerformed))) {
|
||||
setIsTracking(true);
|
||||
timerRef.current = setInterval(() => {
|
||||
setTimeSpent(prevTime => prevTime + 1);
|
||||
@ -88,7 +95,7 @@ const useTrackDocumentLesson = ({ lessonId, courseId, readTime }) => {
|
||||
clearInterval(timerRef.current);
|
||||
}
|
||||
};
|
||||
}, [lessonId, checkOrCreateUserLesson, isAdmin]);
|
||||
}, [lessonId, checkOrCreateUserLesson, isAdmin, paidCourse, decryptionPerformed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAdmin) return; // Skip tracking for admin users
|
||||
|
@ -2,13 +2,13 @@ import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { useSession } from 'next-auth/react';
|
||||
import axios from 'axios';
|
||||
|
||||
const useTrackVideoLesson = ({lessonId, videoDuration, courseId, videoPlayed}) => {
|
||||
const useTrackVideoLesson = ({lessonId, videoDuration, courseId, videoPlayed, paidCourse, decryptionPerformed}) => {
|
||||
const [isCompleted, setIsCompleted] = useState(false);
|
||||
const [timeSpent, setTimeSpent] = useState(0);
|
||||
const [isTracking, setIsTracking] = useState(false);
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const timerRef = useRef(null);
|
||||
const { data: session } = useSession();
|
||||
const { data: session, update } = useSession();
|
||||
const completedRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
@ -31,11 +31,16 @@ const useTrackVideoLesson = ({lessonId, videoDuration, courseId, videoPlayed}) =
|
||||
return false;
|
||||
}
|
||||
} else if (response.status === 204) {
|
||||
await axios.post(`/api/users/${session.user.id}/lessons?courseId=${courseId}`, {
|
||||
resourceId: lessonId,
|
||||
opened: true,
|
||||
openedAt: new Date().toISOString(),
|
||||
});
|
||||
// Only create a new UserLesson entry if it's a free course or if decryption has been performed for a paid course
|
||||
if (paidCourse === false || (paidCourse && decryptionPerformed)) {
|
||||
await axios.post(`/api/users/${session.user.id}/lessons?courseId=${courseId}`, {
|
||||
resourceId: lessonId,
|
||||
opened: true,
|
||||
openedAt: new Date().toISOString(),
|
||||
});
|
||||
// Call session update after creating a new UserLesson entry
|
||||
await update();
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
console.error('Error checking or creating UserLesson:', response.statusText);
|
||||
@ -45,7 +50,7 @@ const useTrackVideoLesson = ({lessonId, videoDuration, courseId, videoPlayed}) =
|
||||
console.error('Error checking or creating UserLesson:', error);
|
||||
return false;
|
||||
}
|
||||
}, [session, lessonId, courseId]);
|
||||
}, [session, lessonId, courseId, update, paidCourse, decryptionPerformed]);
|
||||
|
||||
const markLessonAsCompleted = useCallback(async () => {
|
||||
if (!session?.user || completedRef.current) return;
|
||||
@ -60,20 +65,22 @@ const useTrackVideoLesson = ({lessonId, videoDuration, courseId, videoPlayed}) =
|
||||
if (response.status === 200) {
|
||||
setIsCompleted(true);
|
||||
setIsTracking(false);
|
||||
// Call session update after marking the lesson as completed
|
||||
await update();
|
||||
} else {
|
||||
console.error('Failed to mark lesson as completed:', response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error marking lesson as completed:', error);
|
||||
}
|
||||
}, [lessonId, courseId, session]);
|
||||
}, [lessonId, courseId, session, update]);
|
||||
|
||||
useEffect(() => {
|
||||
const initializeTracking = async () => {
|
||||
if (isAdmin) return;
|
||||
|
||||
const alreadyCompleted = await checkOrCreateUserLesson();
|
||||
if (!alreadyCompleted && videoDuration && !completedRef.current && videoPlayed) {
|
||||
if (!alreadyCompleted && videoDuration && !completedRef.current && videoPlayed && (paidCourse === false || (paidCourse && decryptionPerformed))) {
|
||||
console.log(`Tracking started for lesson ${lessonId}, video duration: ${videoDuration} seconds, video played: ${videoPlayed}`);
|
||||
setIsTracking(true);
|
||||
timerRef.current = setInterval(() => {
|
||||
@ -89,7 +96,7 @@ const useTrackVideoLesson = ({lessonId, videoDuration, courseId, videoPlayed}) =
|
||||
clearInterval(timerRef.current);
|
||||
}
|
||||
};
|
||||
}, [lessonId, videoDuration, checkOrCreateUserLesson, videoPlayed, isAdmin]);
|
||||
}, [lessonId, videoDuration, checkOrCreateUserLesson, videoPlayed, isAdmin, paidCourse, decryptionPerformed]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAdmin) return;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { getServerSession } from "next-auth/next"
|
||||
import { authOptions } from "./auth/[...nextauth]"
|
||||
import { getLessonsByCourseId } from "@/db/models/lessonModels"
|
||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
||||
import { S3Client, GetObjectCommand } from "@aws-sdk/client-s3"
|
||||
import appConfig from "@/config/appConfig";
|
||||
@ -35,9 +36,18 @@ export default async function handler(req, res) {
|
||||
// Check if the user is authorized to access the video
|
||||
if (!session.user.role?.subscribed && !appConfig.authorPubkeys.includes(session.user.pubkey)) {
|
||||
const purchasedVideo = session.user.purchased?.find(purchase => purchase?.resource?.videoId === videoKey)
|
||||
console.log("purchasedVideo", purchasedVideo)
|
||||
// first check if it is individual video
|
||||
if (!purchasedVideo) {
|
||||
return res.status(403).json({ error: "Forbidden: You don't have access to this video" })
|
||||
// next we have to check if it is in a course the user has purchased
|
||||
const allPurchasedCourses = session?.user?.purchased?.filter(purchase => purchase?.courseId) || []
|
||||
|
||||
const allPurchasedLessons = await Promise.all(
|
||||
allPurchasedCourses.map(course => getLessonsByCourseId(course.courseId))
|
||||
).then(lessonsArrays => lessonsArrays.flat())
|
||||
|
||||
if (!allPurchasedLessons.some(lesson => lesson?.resource?.videoId === videoKey)) {
|
||||
return res.status(403).json({ error: "Forbidden: You don't have access to this video" })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,9 @@ export default async function handler(req, res) {
|
||||
// If slug is a pubkey
|
||||
user = await getUserByPubkey(slug);
|
||||
} else if (isEmail) {
|
||||
// todo
|
||||
// If slug is an email
|
||||
user = await getUserByEmail(slug);
|
||||
// user = await getUserByEmail(slug);
|
||||
} else {
|
||||
// Assume slug is an ID
|
||||
const id = parseInt(slug);
|
||||
|
@ -139,7 +139,7 @@ const Course = () => {
|
||||
const { ndk, addSigner } = useNDKContext();
|
||||
const { data: session, update } = useSession();
|
||||
const { showToast } = useToast();
|
||||
const [paidCourse, setPaidCourse] = useState(false);
|
||||
const [paidCourse, setPaidCourse] = useState(null);
|
||||
const [expandedIndex, setExpandedIndex] = useState(null);
|
||||
const [completedLessons, setCompletedLessons] = useState([]);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user