strapi-plugin-masterclass
Version:
1,803 lines • 912 kB
JavaScript
import Stripe from "stripe";
import axios from "axios";
import { v4 } from "uuid";
import require$$1 from "crypto";
import require$$0$1 from "child_process";
import require$$0$2 from "os";
import require$$0$4 from "path";
import require$$0$3 from "fs";
import require$$0$5 from "assert";
import require$$2 from "events";
import require$$0$7 from "buffer";
import require$$0$6 from "stream";
import require$$2$1 from "util";
import require$$0$8 from "constants";
import "node:stream";
const bootstrap = ({ strapi: strapi2 }) => {
if (!strapi2.plugins["mux-video-uploader"]) {
throw new Error(
"The Mux Video Uploader plugin is required. Please install strapi-plugin-mux-video-uploader."
);
}
if (!strapi2.plugins["seo"]) {
throw new Error(
"The @strapi/plugin-seo plugin is required. Please install @strapi/plugin-seo."
);
}
};
const destroy = ({ strapi: strapi2 }) => {
};
const pluginId = "masterclass";
const CATEGORY_MODEL = `plugin::${pluginId}.mc-category`;
const COURSE_MODEL = `plugin::${pluginId}.mc-course`;
const LECTURE_MODEL = `plugin::${pluginId}.mc-lecture`;
const MODULE_MODEL = `plugin::${pluginId}.mc-module`;
const STUDENT_COURSE_MODEL = `plugin::${pluginId}.mc-student-course`;
const ORDER_MODEL = `plugin::${pluginId}.mc-order`;
const pageActions = ["create", "update", "delete"];
const LectureActions = {
async create(context, strapi2) {
let connectVideo = [];
let connectModule = [];
if (context.params.data.video) {
const { connect } = context.params.data.video;
connectVideo = connect ? connect : [];
}
if (context.params.data.module) {
const { connect } = context.params.data.module;
connectModule = connect ? connect : [];
}
if (!connectVideo.length) {
context.params.data.duration = 0;
return;
}
const videoDocumentId = connectVideo[0].documentId;
const video = await strapi2.documents("plugin::mux-video-uploader.mux-asset").findOne({
documentId: videoDocumentId,
fields: ["duration"]
});
if (!video) {
return;
}
const videoDuration = Math.floor(video.duration ? video.duration : 0);
context.params.data.duration = videoDuration;
if (!connectModule.length) {
return;
}
const moduleDocumentId = connectModule[0].documentId;
const module = await strapi2.documents(MODULE_MODEL).findOne({
documentId: moduleDocumentId,
fields: ["duration"],
populate: {
course: {
fields: ["duration", "documentId"]
}
}
});
if (!module) {
return;
}
await strapi2.documents(MODULE_MODEL).update({
documentId: moduleDocumentId,
data: {
duration: module.duration ? module.duration + videoDuration : videoDuration
}
});
const { course } = module;
if (!course) {
return;
}
await strapi2.documents(COURSE_MODEL).update({
documentId: course.documentId,
data: {
duration: course.duration ? course.duration + videoDuration : videoDuration
}
});
},
async update(context, strapi2) {
let connectVideo = [];
let disconnectVideo = [];
let connectModule = [];
let disconnectModule = [];
if (context.params.data.video) {
const { connect, disconnect } = context.params.data.video;
connectVideo = connect ? connect : [];
disconnectVideo = disconnect ? disconnect : [];
}
if (context.params.data.module) {
const { connect, disconnect } = context.params.data.module;
connectModule = connect ? connect : [];
disconnectModule = disconnect ? disconnect : [];
}
let oldVideoDuration = context.params.data.duration ? context.params.data.duration : 0;
let newVideoDuration = context.params.data.duration ? context.params.data.duration : 0;
if (disconnectVideo.length > 0) {
context.params.data.duration = 0;
const videoDocumentId = disconnectVideo[0].documentId;
const video = await strapi2.documents("plugin::mux-video-uploader.mux-asset").findOne({
documentId: videoDocumentId,
fields: ["duration"]
});
if (video) {
oldVideoDuration = Math.floor(video.duration ? video.duration : 0);
}
}
if (connectVideo.length > 0) {
const videoDocumentId = connectVideo[0].documentId;
const video = await strapi2.documents("plugin::mux-video-uploader.mux-asset").findOne({
documentId: videoDocumentId,
fields: ["duration"]
});
if (video) {
const duration = Math.floor(video.duration ? video.duration : 0);
context.params.data.duration = duration;
newVideoDuration = duration;
}
}
if (!connectModule.length && !disconnectModule.length) {
return;
}
if (disconnectModule.length && oldVideoDuration > 0) {
const moduleDocumentId = disconnectModule[0].documentId;
const module = await strapi2.documents(MODULE_MODEL).findOne({
documentId: moduleDocumentId,
fields: ["duration"],
populate: {
course: {
fields: ["duration", "documentId"]
}
}
});
if (module) {
let newModuleDuration = 0;
if (module.duration && module.duration - oldVideoDuration > 0) {
newModuleDuration = module.duration - oldVideoDuration;
}
await strapi2.documents(MODULE_MODEL).update({
documentId: moduleDocumentId,
data: {
duration: newModuleDuration
}
});
const { course } = module;
if (course) {
let newCourseDuration = 0;
if (course.duration && course.duration - oldVideoDuration > 0) {
newCourseDuration = course.duration - oldVideoDuration;
}
await strapi2.documents(COURSE_MODEL).update({
documentId: course.documentId,
data: {
duration: newCourseDuration
}
});
}
}
}
if (connectModule.length && newVideoDuration > 0) {
const moduleDocumentId = connectModule[0].documentId;
const module = await strapi2.documents(MODULE_MODEL).findOne({
documentId: moduleDocumentId,
fields: ["duration"],
populate: {
course: {
fields: ["duration", "documentId"]
}
}
});
if (module) {
let newModuleDuration = newVideoDuration;
if (module.duration) {
newModuleDuration = module.duration + newVideoDuration;
}
await strapi2.documents(MODULE_MODEL).update({
documentId: moduleDocumentId,
data: {
duration: newModuleDuration
}
});
const { course } = module;
if (course) {
let newCourseDuration = newVideoDuration;
if (course.duration) {
newCourseDuration = course.duration + newVideoDuration;
}
await strapi2.documents(COURSE_MODEL).update({
documentId: course.documentId,
data: {
duration: newCourseDuration
}
});
}
}
}
},
// WARNING: This is not going to update the module or course durations correctly
// if invoked from bulk delete from the content manages due to race conditions.
async delete(context, strapi2) {
const lecture = await strapi2.documents(LECTURE_MODEL).findOne({
documentId: context.params.documentId,
fields: ["duration"],
populate: {
module: {
fields: ["duration"],
populate: {
course: {
fields: ["duration", "documentId"]
}
}
}
}
});
const { module } = lecture;
if (!module) {
return;
}
const videoDuration = lecture.duration ? lecture.duration : 0;
if (!videoDuration) {
return;
}
let newModuleDuration = 0;
if (module.duration && module.duration - videoDuration > 0) {
newModuleDuration = module.duration - videoDuration;
}
await strapi2.documents(MODULE_MODEL).update({
documentId: module.documentId,
data: {
duration: newModuleDuration
}
});
const { course } = module;
if (course) {
let newCourseDuration = 0;
if (course.duration && course.duration - videoDuration > 0) {
newCourseDuration = course.duration - videoDuration;
}
await strapi2.documents(COURSE_MODEL).update({
documentId: course.documentId,
data: {
duration: newCourseDuration
}
});
}
}
};
const ModuleActions = {
async create(context, strapi2) {
context.params.data.duration = 0;
let connectLectures = [];
let connectCourse = [];
if (context.params.data.lectures) {
const { connect } = context.params.data.lectures;
connectLectures = connect ? connect : [];
}
if (context.params.data.course) {
const { connect } = context.params.data.course;
connectCourse = connect ? connect : [];
}
if (!connectLectures.length) {
return;
}
let totalLecturesDuration = 0;
await Promise.all(connectLectures.map(async (connectLecture) => {
const lectureDocumentId = connectLecture.documentId;
const lecture = await strapi2.documents(LECTURE_MODEL).findOne({
documentId: lectureDocumentId,
fields: ["duration"]
});
if (lecture && lecture.duration > 0) {
totalLecturesDuration += lecture.duration;
}
}));
if (!(totalLecturesDuration > 0)) {
return;
}
context.params.data.duration = totalLecturesDuration;
if (!connectCourse.length) {
return;
}
const courseDocumentId = connectCourse[0].documentId;
const course = await strapi2.documents(COURSE_MODEL).findOne({
documentId: courseDocumentId,
fields: ["duration"]
});
if (!course) {
return;
}
const newCourseDuration = course.duration ? course.duration + totalLecturesDuration : totalLecturesDuration;
await strapi2.documents(COURSE_MODEL).update({
documentId: courseDocumentId,
data: {
duration: newCourseDuration
}
});
},
async update(context, strapi2) {
let connectLectures = [];
let disconnectLectures = [];
let connectCourse = [];
let disconnectCourse = [];
if (context.params.data.lectures) {
const { connect, disconnect } = context.params.data.lectures;
connectLectures = connect ? connect : [];
disconnectLectures = disconnect ? disconnect : [];
}
if (context.params.data.course) {
const { connect, disconnect } = context.params.data.course;
connectCourse = connect ? connect : [];
disconnectCourse = disconnect ? disconnect : [];
}
let removedLecturesDuration = 0;
let addedLecturesDuration = 0;
if (disconnectLectures.length) {
await Promise.all(disconnectLectures.map(async (disconnectLecture) => {
const lectureDocumentId = disconnectLecture.documentId;
const lecture = await strapi2.documents(LECTURE_MODEL).findOne({
documentId: lectureDocumentId,
fields: ["duration"]
});
if (lecture && lecture.duration > 0) {
removedLecturesDuration += lecture.duration;
}
}));
}
if (connectLectures.length) {
await Promise.all(connectLectures.map(async (connectLecture) => {
const lectureDocumentId = connectLecture.documentId;
const lecture = await strapi2.documents(LECTURE_MODEL).findOne({
documentId: lectureDocumentId,
fields: ["duration"]
});
if (lecture && lecture.duration > 0) {
addedLecturesDuration += lecture.duration;
}
}));
}
const diffModuleDuration = addedLecturesDuration - removedLecturesDuration;
const currentModuleDuration = context.params.data.duration ? context.params.data.duration : 0;
const newModuleDuration = currentModuleDuration + diffModuleDuration;
context.params.data.duration = newModuleDuration;
if (disconnectCourse.length && currentModuleDuration > 0) {
const courseDocumentId = disconnectCourse[0].documentId;
const course = await strapi2.documents(COURSE_MODEL).findOne({
documentId: courseDocumentId,
fields: ["duration"]
});
if (course) {
let newCourseDuration = course.duration ? course.duration - currentModuleDuration : 0;
await strapi2.documents(COURSE_MODEL).update({
documentId: courseDocumentId,
data: {
duration: newCourseDuration
}
});
}
}
if (connectCourse.length) {
const courseDocumentId = connectCourse[0].documentId;
const course = await strapi2.documents(COURSE_MODEL).findOne({
documentId: courseDocumentId,
fields: ["duration"]
});
if (course) {
let newCourseDuration = course.duration ? course.duration + newModuleDuration : newModuleDuration;
await strapi2.documents(COURSE_MODEL).update({
documentId: courseDocumentId,
data: {
duration: newCourseDuration
}
});
}
}
if (disconnectCourse.length || connectCourse.length) {
return;
}
const module = await strapi2.documents(MODULE_MODEL).findOne({
documentId: context.params.documentId,
populate: {
course: {
fields: ["duration", "documentId"]
}
}
});
if (module && module.course && diffModuleDuration != 0) {
const { course } = module;
let newCourseDuration = course.duration ? course.duration + diffModuleDuration : newModuleDuration;
await strapi2.documents(COURSE_MODEL).update({
documentId: course.documentId,
data: {
duration: newCourseDuration
}
});
}
},
// WARNING: This is not going to update the module or course durations correctly
// if invoked from bulk delete from the content manages due to race conditions.
async delete(context, strapi2) {
const module = await strapi2.documents(MODULE_MODEL).findOne({
documentId: context.params.documentId,
fields: ["duration"],
populate: {
course: {
fields: ["duration", "documentId"]
}
}
});
const { course } = module;
if (!module) {
return;
}
const moduleDuration = module.duration ? module.duration : 0;
if (!moduleDuration) {
return;
}
if (course) {
let newCourseDuration = 0;
if (course.duration && course.duration - moduleDuration > 0) {
newCourseDuration = course.duration - moduleDuration;
}
await strapi2.documents(COURSE_MODEL).update({
documentId: course.documentId,
data: {
duration: newCourseDuration
}
});
}
}
};
const CourseActions = {
async create(context, strapi2) {
let connectModules = [];
if (context.params.data.modules) {
const { connect } = context.params.data.modules;
connectModules = connect ? connect : [];
}
if (!connectModules.length) {
context.params.data.duration = 0;
return;
}
let totalModulesDuration = 0;
await Promise.all(connectModules.map(async (connectModule) => {
const moduleDocumentId = connectModule.documentId;
const module = await strapi2.documents(MODULE_MODEL).findOne({
documentId: moduleDocumentId,
fields: ["duration"]
});
if (module && module.duration > 0) {
totalModulesDuration += module.duration;
}
}));
if (!(totalModulesDuration > 0)) {
return;
}
context.params.data.duration = totalModulesDuration;
},
async update(context, strapi2) {
let connectModules = [];
let disconnectModules = [];
if (context.params.data.modules) {
const { connect, disconnect } = context.params.data.modules;
connectModules = connect ? connect : [];
disconnectModules = disconnect ? disconnect : [];
}
if (!connectModules.length && !disconnectModules.length) {
return;
}
let removedModulesDuration = 0;
let addedModulesDuration = 0;
if (disconnectModules.length) {
await Promise.all(disconnectModules.map(async (disconnectModule) => {
const moduleDocumentId = disconnectModule.documentId;
const module = await strapi2.documents(MODULE_MODEL).findOne({
documentId: moduleDocumentId,
fields: ["duration"]
});
if (module && module.duration > 0) {
removedModulesDuration += module.duration;
}
}));
}
if (connectModules.length) {
await Promise.all(connectModules.map(async (connectModule) => {
const moduleDocumentId = connectModule.documentId;
const module = await strapi2.documents(MODULE_MODEL).findOne({
documentId: moduleDocumentId,
fields: ["duration"]
});
if (module && module.duration > 0) {
addedModulesDuration += module.duration;
}
}));
}
const diffCourseDuration = addedModulesDuration - removedModulesDuration;
const currentCourseDuration = context.params.data.duration ? context.params.data.duration : 0;
const newCourseDuration = currentCourseDuration + diffCourseDuration;
context.params.data.duration = newCourseDuration;
},
async delete(context, strapi2) {
}
};
const registerDocServiceMiddleware = ({ strapi: strapi2 }) => {
strapi2.documents.use(async (context, next) => {
if (context.uid.endsWith("mc-lecture") && pageActions.includes(context.action)) {
await LectureActions[context.action](context, strapi2);
}
if (context.uid.endsWith("mc-module") && pageActions.includes(context.action)) {
await ModuleActions[context.action](context, strapi2);
}
if (context.uid.endsWith("mc-course") && pageActions.includes(context.action)) {
await CourseActions[context.action](context, strapi2);
}
return next();
});
};
const register = ({ strapi: strapi2 }) => {
const user = strapi2.contentType("plugin::users-permissions.user");
user.attributes = {
// Spread previous defined attributes
...user.attributes,
// Add new, or override attributes
courses: {
type: "relation",
relation: "oneToMany",
target: "plugin::masterclass.mc-student-course",
mappedBy: "student"
}
};
registerDocServiceMiddleware({ strapi: strapi2 });
};
const config = {
default: {
stripeSecretKey: "",
paypalClientId: "",
paypalClientSecret: "",
brandName: "",
paypalReturnUrl: "",
paypalCancelUrl: "",
paypalProductionMode: false,
callbackUrl: "",
paymentMethods: ["card"],
allowPromotionCodes: false,
checkoutSuccessUrl: "",
checkoutCancelUrl: ""
},
validator(config2) {
const missingConfigs = [];
if (!config2.stripeSecretKey) {
missingConfigs.push("accessTokenId");
}
if (missingConfigs.length > 0) {
throw new Error(
`Please remember to set up the file based config for your plugin. Refer to the "Configuration" of the README for this plugin for additional details. Configs missing: ${missingConfigs.join(", ")}`
);
}
}
};
const kind$6 = "collectionType";
const collectionName$6 = "mc_categories";
const info$6 = {
singularName: "mc-category",
pluralName: "mc-categories",
displayName: "Category",
description: ""
};
const options$6 = {
draftAndPublish: false
};
const pluginOptions$1 = {
"content-manager": {
visible: true
},
"content-type-builder": {
visible: true
}
};
const attributes$6 = {
title: {
type: "string"
},
description: {
type: "blocks"
},
thumbnail: {
type: "media",
multiple: false,
required: false,
allowedTypes: [
"images",
"files"
]
},
slug: {
type: "uid",
targetField: "title"
},
courses: {
type: "relation",
relation: "oneToMany",
target: "plugin::masterclass.mc-course",
mappedBy: "category"
}
};
const mcCategory = {
kind: kind$6,
collectionName: collectionName$6,
info: info$6,
options: options$6,
pluginOptions: pluginOptions$1,
attributes: attributes$6
};
const kind$5 = "collectionType";
const collectionName$5 = "mc_courses";
const info$5 = {
singularName: "mc-course",
pluralName: "mc-courses",
displayName: "Course",
description: ""
};
const options$5 = {
draftAndPublish: false
};
const attributes$5 = {
title: {
type: "string"
},
duration: {
type: "integer",
configurable: false
},
description: {
type: "blocks"
},
price: {
type: "decimal"
},
thumbnail: {
type: "media",
multiple: false,
required: false,
allowedTypes: [
"images",
"files"
]
},
long_description: {
type: "blocks"
},
difficulty: {
type: "enumeration",
"enum": [
"Beginner",
"Intermediate",
"Advanced"
]
},
language: {
type: "enumeration",
"enum": [
"English",
"Русский"
]
},
category: {
type: "relation",
relation: "manyToOne",
target: "plugin::masterclass.mc-category",
inversedBy: "courses"
},
slug: {
type: "uid",
targetField: "title"
},
students: {
type: "relation",
relation: "oneToMany",
target: "plugin::masterclass.mc-student-course",
mappedBy: "course"
},
modules: {
type: "relation",
relation: "oneToMany",
target: "plugin::masterclass.mc-module",
mappedBy: "course"
},
instructor: {
type: "relation",
relation: "manyToOne",
target: "plugin::masterclass.mc-instructor",
inversedBy: "courses"
},
seo: {
type: "component",
repeatable: false,
component: "shared.seo"
}
};
const mcCourse = {
kind: kind$5,
collectionName: collectionName$5,
info: info$5,
options: options$5,
attributes: attributes$5
};
const kind$4 = "collectionType";
const collectionName$4 = "mc_lectures";
const info$4 = {
singularName: "mc-lecture",
pluralName: "mc-lectures",
displayName: "Lecture",
description: ""
};
const options$4 = {
draftAndPublish: false
};
const attributes$4 = {
title: {
type: "string"
},
slug: {
type: "uid",
targetField: "title"
},
duration: {
type: "integer",
configurable: false
},
video: {
type: "relation",
relation: "oneToOne",
target: "plugin::mux-video-uploader.mux-asset"
},
module: {
type: "relation",
relation: "manyToOne",
target: "plugin::masterclass.mc-module",
inversedBy: "lectures"
},
description: {
type: "blocks"
}
};
const mcLecture = {
kind: kind$4,
collectionName: collectionName$4,
info: info$4,
options: options$4,
attributes: attributes$4
};
const kind$3 = "collectionType";
const collectionName$3 = "mc_modules";
const info$3 = {
singularName: "mc-module",
pluralName: "mc-modules",
displayName: "Module",
description: ""
};
const options$3 = {
draftAndPublish: false
};
const attributes$3 = {
title: {
type: "string"
},
duration: {
type: "integer",
configurable: false
},
course: {
type: "relation",
relation: "manyToOne",
target: "plugin::masterclass.mc-course",
inversedBy: "modules"
},
lectures: {
type: "relation",
relation: "oneToMany",
target: "plugin::masterclass.mc-lecture",
mappedBy: "module"
},
slug: {
type: "uid",
targetField: "title"
},
description: {
type: "blocks"
}
};
const mcModule = {
kind: kind$3,
collectionName: collectionName$3,
info: info$3,
options: options$3,
attributes: attributes$3
};
const kind$2 = "collectionType";
const collectionName$2 = "mc_student_courses";
const info$2 = {
singularName: "mc-student-course",
pluralName: "mc-student-courses",
displayName: "StudentCourse",
description: ""
};
const options$2 = {
draftAndPublish: false
};
const attributes$2 = {
course: {
type: "relation",
relation: "manyToOne",
target: "plugin::masterclass.mc-course",
inversedBy: "students"
},
student: {
type: "relation",
relation: "manyToOne",
target: "plugin::users-permissions.user",
inversedBy: "courses"
},
current_lecture: {
type: "relation",
relation: "oneToOne",
target: "plugin::masterclass.mc-lecture"
},
lectures_completed: {
type: "relation",
relation: "oneToMany",
target: "plugin::masterclass.mc-lecture"
}
};
const mcStudentCourse = {
kind: kind$2,
collectionName: collectionName$2,
info: info$2,
options: options$2,
attributes: attributes$2
};
const kind$1 = "collectionType";
const collectionName$1 = "mc_order";
const info$1 = {
singularName: "mc-order",
pluralName: "mc-orders",
displayName: "Order",
description: ""
};
const options$1 = {
draftAndPublish: false,
comment: ""
};
const attributes$1 = {
amount: {
type: "decimal",
configurable: false
},
user: {
type: "relation",
relation: "oneToOne",
target: "plugin::users-permissions.user",
configurable: false
},
confirmed: {
type: "boolean",
"default": false,
configurable: false
},
checkout_session: {
type: "string",
configurable: false
},
payment_method: {
type: "enumeration",
"enum": [
"paypal",
"credit_card"
],
configurable: false
},
items: {
type: "json",
configurable: false
},
response: {
type: "json",
configurable: false
},
courses: {
type: "relation",
relation: "oneToMany",
target: "plugin::masterclass.mc-course",
configurable: false
}
};
const mcOrder = {
kind: kind$1,
collectionName: collectionName$1,
info: info$1,
options: options$1,
attributes: attributes$1
};
const kind = "collectionType";
const collectionName = "mc_instructors";
const info = {
singularName: "mc-instructor",
pluralName: "mc-instructors",
displayName: "Instructor",
description: ""
};
const options = {
draftAndPublish: false
};
const pluginOptions = {
"content-manager": {
visible: true
},
"content-type-builder": {
visible: true
}
};
const attributes = {
name: {
type: "string"
},
bio: {
type: "blocks"
},
image: {
type: "media",
multiple: false,
required: false,
allowedTypes: [
"images"
]
},
slug: {
type: "uid",
targetField: "name"
},
designation: {
type: "string"
},
courses: {
type: "relation",
relation: "oneToMany",
target: "plugin::masterclass.mc-course",
mappedBy: "instructor"
}
};
const mcInstructor = {
kind,
collectionName,
info,
options,
pluginOptions,
attributes
};
const contentTypes = {
"mc-category": { schema: mcCategory },
"mc-course": { schema: mcCourse },
"mc-lecture": { schema: mcLecture },
"mc-module": { schema: mcModule },
"mc-student-course": { schema: mcStudentCourse },
"mc-order": { schema: mcOrder },
"mc-instructor": { schema: mcInstructor }
};
const controller = ({ strapi: strapi2 }) => ({
async find(ctx) {
let courses = await strapi2.documents(COURSE_MODEL).findMany({
populate: {
seo: {
populate: {
metaImage: {
fields: ["alternativeText", "url"]
},
openGraph: {
populate: {
ogImage: {
fields: ["alternativeText", "url"]
}
}
}
}
},
thumbnail: {
fields: ["name", "url"]
},
modules: {
fields: ["title", "duration", "slug"],
populate: {
lectures: {
fields: ["title", "duration", "slug", "description"]
}
}
},
category: {
fields: ["slug", "title", "id"]
},
students: {
fields: ["documentId"]
},
instructor: {
fields: ["name", "slug", "bio", "designation"],
populate: {
image: {
fields: ["name", "url"]
}
}
}
}
});
courses = courses.map((course) => {
const totalLectures = course.modules.reduce((acc, module) => {
return acc + module.lectures.length;
}, 0);
return {
...course,
total_students: course.students.length,
total_lectures: totalLectures
};
});
ctx.body = { courses };
},
async findOne(ctx) {
const { slug } = ctx.params;
let course = await strapi2.documents(COURSE_MODEL).findFirst({
filters: { slug: { $eq: slug } },
populate: {
thumbnail: {
fields: ["name", "url"]
},
modules: {
fields: ["title", "duration", "description", "slug"],
populate: {
lectures: {
fields: ["title", "duration", "slug", "description"]
}
}
},
seo: {
populate: {
metaImage: {
fields: ["alternativeText", "url"]
},
openGraph: {
populate: {
ogImage: {
fields: ["alternativeText", "url"]
}
}
}
}
},
category: {
fields: ["slug", "title", "id"]
},
instructor: {
fields: ["name", "slug", "bio", "designation"],
populate: {
image: {
fields: ["name", "url"]
}
}
},
students: {
fields: ["documentId"]
}
}
});
const totalLectures = course.modules.reduce((acc, module) => {
return acc + module.lectures.length;
}, 0);
course = {
...course,
total_students: course.students.length,
total_lectures: totalLectures
};
ctx.body = { course };
},
async findSlugs(ctx) {
const courses = await strapi2.documents(COURSE_MODEL).findMany({
filters: {},
fields: ["slug"]
});
ctx.body = { courses };
},
/*
* Get the classes the user (if any) has marked as seen and the number of students
*/
async getCourseDetails(ctx) {
const { user } = ctx.state;
const { courseId } = ctx.params;
let classesCompleted = [];
if (user) {
const student = await strapi2.documents(STUDENT_COURSE_MODEL).findFirst(
{
filters: {
student: { documentId: { $eq: user.documentId } },
course: { documentId: { $eq: courseId } }
},
populate: {
lectures_completed: {
fields: ["documentId", "slug"]
},
current_lecture: {
fields: ["documentId", "slug"]
}
}
}
);
if (student) {
classesCompleted = student.lectures_completed;
}
}
const students = await strapi2.documents(STUDENT_COURSE_MODEL).count({
filters: {
course: { documentId: { $eq: courseId } }
}
});
ctx.body = { classesCompleted, students };
},
/*
* Get user progress
*/
async getClassesCompleted(ctx) {
const { user } = ctx.state;
const { courseId } = ctx.params;
if (!user) {
return ctx.badRequest("There must be an user");
}
const student = await strapi2.documents(STUDENT_COURSE_MODEL).findFirst(
{
filters: {
student: { documentId: { $eq: user.documentId } },
course: { documentId: { $eq: courseId } }
},
populate: {
lectures_completed: {
fields: ["id", "slug"]
}
}
}
);
if (!student) {
return ctx.badRequest("No access to this course");
}
ctx.body = { classesCompleted: student.lectures_completed };
},
/*
* Get current lecture to resume course
*/
async getCurrentLecture(ctx) {
const { user } = ctx.state;
const { courseId } = ctx.params;
if (!user) {
return ctx.badRequest("There must be an user");
}
const student = await strapi2.documents(STUDENT_COURSE_MODEL).findFirst(
{
filters: {
student: { documentId: { $eq: user.documentId } },
course: { documentId: { $eq: courseId } }
},
populate: {
course: {
populate: {
modules: {
populate: {
lectures: {
fields: ["documentId", "title", "description", "duration", "slug"],
populate: {
video: {
fields: ["id", "asset_id"]
}
}
}
}
}
}
},
current_lecture: {
fields: ["id", "documentId", "title", "slug"]
}
}
}
);
if (!student) {
return ctx.badRequest("No access to this course");
}
const lectures = student.course.modules.reduce((lectures2, module) => {
return lectures2.concat(module.lectures);
}, []);
if (!lectures.length) {
return ctx.badRequest("This course does not have any lecture");
}
const currentLecture = student.current_lecture || lectures[0];
return {
currentLecture
};
},
/*
* Resume course
*/
async resumeCourse(ctx) {
const { user } = ctx.state;
const { courseId } = ctx.params;
if (!user) {
return ctx.badRequest("There must be an user");
}
const student = await strapi2.documents(STUDENT_COURSE_MODEL).findFirst(
{
filters: {
student: { documentId: { $eq: user.documentId } },
course: { documentId: { $eq: courseId } }
},
populate: {
course: {
populate: {
modules: {
populate: {
lectures: {
populate: {
video: {
fields: ["id", "asset_id", "playback_id"]
}
}
}
}
}
}
},
lectures_completed: {
fields: ["id"]
},
current_lecture: {
fields: ["id"],
populate: {
video: {
fields: ["asset_id", "playback_id"]
}
}
}
}
}
);
if (!student) {
return ctx.badRequest("No access to this course");
}
const lectures = student.course.modules.reduce((lectures2, module) => {
return lectures2.concat(module.lectures);
}, []);
if (!lectures.length) {
return ctx.badRequest("This course does not have any lecture");
}
const currentLecture = student.current_lecture || lectures[0];
const signed = await strapi2.service("plugin::mux-video-uploader.mux").signPlaybackId(currentLecture.video.playback_id, "video");
const playbackID = currentLecture.video.playback_id;
return {
PlayAuth: `https://stream.mux.com/${playbackID}.m3u8?token=${signed.token}`,
VideoId: playbackID,
classesCompleted: student.lectures_completed,
currentLectureID: currentLecture.id
};
},
/*
* Get play auth for the given lecture
*/
async getPlayAuth(ctx) {
const { user } = ctx.state;
if (!user) {
return ctx.badRequest("There must be an user");
}
const { courseId } = ctx.params;
const { lecture } = ctx.query;
const student = await strapi2.documents(STUDENT_COURSE_MODEL).findFirst(
{
filters: {
student: { documentId: { $eq: user.documentId } },
course: { documentId: { $eq: courseId } }
},
populate: {
lectures_completed: {
fields: ["id"]
}
}
}
);
if (!student) {
return ctx.badRequest("No access to this course");
}
const newCurrentLecture = await strapi2.documents(LECTURE_MODEL).findFirst(
{
filters: { slug: { $eq: lecture } },
populate: {
video: {
fields: ["asset_id", "playback_id"]
}
}
}
);
if (!newCurrentLecture) {
return ctx.badRequest("The lecture does not exist");
}
await strapi2.documents(STUDENT_COURSE_MODEL).update({
documentId: student.documentId,
data: { current_lecture: newCurrentLecture.id }
});
const signed = await strapi2.service("plugin::mux-video-uploader.mux").signPlaybackId(newCurrentLecture.video.playback_id, "video");
const playbackID = newCurrentLecture.video.playback_id;
return {
PlayAuth: `https://stream.mux.com/${playbackID}.m3u8?token=${signed.token}`,
VideoId: newCurrentLecture.video.playback_id,
classesCompleted: student.lectures_completed,
currentLectureID: newCurrentLecture.id
};
},
async checkLecture(ctx) {
const { user } = ctx.state;
const { courseId } = ctx.params;
const { lecture_id } = ctx.request.body;
const student = await strapi2.documents(STUDENT_COURSE_MODEL).findFirst(
{
filters: {
student: { documentId: { $eq: user.documentId } },
course: { documentId: { $eq: courseId } }
},
populate: {
course: {
populate: {
modules: {
populate: {
lectures: {
fields: ["id", "slug", "documentId"]
}
}
}
}
},
lectures_completed: {
fields: ["id", "slug", "documentId"]
},
current_lecture: {
fields: ["id", "slug", "documentId"]
}
}
}
);
if (!student) {
return ctx.badRequest("No access to this course");
}
const rawLectures = student.course.modules.reduce((lectures, module) => {
return lectures.concat(module.lectures);
}, []);
if (!rawLectures.length) {
return ctx.badRequest("This course does not have any lectures");
}
const currentLectureIndex = rawLectures.findIndex((l) => l.documentId === lecture_id);
if (currentLectureIndex < 0) {
return ctx.badRequest("The lecture does not exist or does not belong to this course");
}
let classesCompleted = student.lectures_completed;
if (!classesCompleted || !classesCompleted.length) {
classesCompleted = [lecture_id];
} else {
const idx = classesCompleted.findIndex((l) => l.documentId === lecture_id);
if (idx < 0) {
classesCompleted.push(lecture_id);
}
}
let newCurrentLecture = student.current_lecture;
if (currentLectureIndex !== rawLectures.length - 1) {
newCurrentLecture = rawLectures[currentLectureIndex + 1];
} else {
newCurrentLecture = rawLectures[currentLectureIndex];
}
await strapi2.documents(STUDENT_COURSE_MODEL).update(
{
documentId: student.documentId,
data: {
current_lecture: newCurrentLecture ? newCurrentLecture.id : null,
lectures_completed: classesCompleted
}
}
);
ctx.body = {
ok: true
};
},
// this handler only returns the IDs of all the courses purchased by the user
async getItemsPurchased(ctx) {
const { user } = ctx.state;
if (!user) {
return ctx.badRequest("There must be an user");
}
const student = await strapi2.documents("plugin::users-permissions.user").findOne({
documentId: user.documentId,
populate: {
courses: {
populate: {
course: {
fields: ["documentId"]
}
}
}
}
});
let res = student;
if (!student) {
res = {
courses: []
};
}
ctx.body = res;
},
// this handler returns the full information of all the courses purchased by the user
async getMyLearning(ctx) {
const { user } = ctx.state;
if (!user) {
return ctx.badRequest("There must be an user");
}
const student = await strapi2.documents("plugin::users-permissions.user").findOne({
documentId: user.documentId,
fields: [],
populate: {
courses: {
populate: {
current_lecture: {
fields: ["slug"]
},
lectures_completed: {
fields: ["slug"]
},
course: {
fields: [
"documentId",
"duration",
"title",
"description",
"price",
"slug"
],
populate: {
thumbnail: {
fields: ["documentId", "name", "url"]
},
modules: {
fields: ["documentId", "title", "duration"],
populate: {
lectures: {
fields: ["documentId", "title", "duration", "description"]
}
}
},
category: {
fields: ["documentId", "slug", "title"]
}
}
}
}
}
}
});
let res = student;
if (!student) {
res = {
courses: []
};
}
ctx.body = res;
}
});
const courseQuery = {
fields: "*",
populate: {
thumbnail: {
fields: ["name", "url"]
},
modules: {
populate: {
lectures: {
fields: ["title", "duration"]
}
}
}
}
};
const categories = ({ strapi: strapi2 }) => ({
async index(ctx) {
const categories2 = await strapi2.documents(CATEGORY_MODEL).findMany({
populate: {
thumbnail: {
fields: ["name", "url"]
},
courses: courseQuery
}
});
const result = await Promise.all(categories2.map(async (category) => {
let courses_count = await strapi2.documents(COURSE_MODEL).count({
filters: {
category: {
id: {
$eq: category.id
}
}
}
});
category.courses_count = courses_count;
return category;
}));
const schema2 = strapi2.getModel(CATEGORY_MODEL);
ctx.body = {
categories: await strapi2.contentAPI.sanitize.output(result, schema2)
};
},
async findOne(ctx) {
const { document_id } = ctx.params;
const category = await strapi2.documents(CATEGORY_MODEL).findOne({
documentId: document_id,
populate: {
thumbnail: {
fields: ["name", "url"]
},
courses: courseQuery
}
});
let courses_count = await strapi2.documents(COURSE_MODEL).count({
filters: {
category: {
id: {
$eq: category.id
}
}
}
});
category.courses_count = courses_count;
const schema2 = strapi2.getModel(CATEGORY_MODEL);
ctx.body = {
category: await strapi2.contentAPI.sanitize.output(category, schema2)
};
}
});
const PLUGIN_NAME = "masterclass";
const getConfig = async () => await strapi.config.get(`plugin::${PLUGIN_NAME}`);
const getService = (name) => {
const service = strapi.plugin(PLUGIN_NAME).service(name);
return service;
};
const orders = ({ strapi: strapi2 }) => ({
async find(ctx) {
const { user } = ctx.state;
if (!user) {
return ctx.badRequest("User must be authenticated");
}
const result = await strapi2.documents(ORDER_MODEL).findMany({
filters: {
user
}
});
ctx.body = {
orders: result
};
},
/**
* Retrieve an order by id, only if it belongs to the user
*/
async findOne(ctx) {
const { id } = ctx.params;
const { user } = ctx.state;
if (!user) {
return ctx.badRequest("User must be authenticated");
}
if (!id) {
return ctx.badRequest("Id is required");
}
const order = await strapi2.documents(ORDER_MODEL).findOne({
documentId: id,
populate: {
user: { fields: ["id"] },
courses: true
}
});
if (order && order.user.id !== user.id) {
return ctx.forbidden("This order does not belong to this user");
}
ctx.body = {
order
};
},
async create(ctx) {
const { courses, payment_method } = ctx.request.body;
if (!courses || !courses.length) {
return ctx.badRequest("no items received");
}
if (!["credit_card", "paypal"].includes(payment_method)) {
return ctx.badRequest("invalid payment_method: " + payment_method);
}
let { user } = ctx.state;
if (user) {
const student = await strapi2.documents(STUDENT_COURSE_MODEL).findFirst({
filters: {
student: {
documentId: {
$eq: user.documentId
}
},
course: {
documentId: {
$in: courses
}
}
}
});
if (student) {
ctx.body = {};
return ctx.badRequest("user already purchased this course", { redirectToLogin: true });
}
}
const _courses = await strapi2.documents(COURSE_MODEL).findMany({
filters: {
documentId: {
$in: courses
}
}
});
if (!_courses.length) {
return ctx.badRequest("courses not found");
}
const params = {
user,
payment_method,
courses: _courses
};
let result;
try {
result = await getService("payments").create(params);
if (result.error) {
return ctx[result.status](result.msg);
}
} catch (err) {
console.log("orders create error:", err);
return ctx.internalServerError("something went wrong");
}
ctx.body = result;
},
async confirmWithUser(ctx) {
const { checkout_session } = ctx.request.body;
if (!checkout_session) {
return ctx.badRequest("checkout_session is required");
}
let { user } = ctx.state;
let order = await strapi2.documents(ORDER_MODEL).findFirst({
filters: {
checkout_session: {
$eq: checkout_session
}
},
populate: {
user: {
fields: ["id", "email"]
},
courses: courseQuery
}
});
if (!order) {
return ctx.badRequest("order not found");
}
const email = order.user.email;
if (user.email != email) {
return ctx.badRequest("unmatched user and order email");
}
const { courses } = order;
if (order.confirmed) {
ctx.body = {
courses,
is_new_account: false,
user_email: user.email,
checkout_session: order.checkout_session,
id: order.id,
documentId: order.documentId,
amount: order.amount,
confirmed: order.confirmed,
payment_method: order.payment_method,
createdAt: order.createdAt,
publishedAt: order.publishedAt,
updatedAt: order.updatedAt
};
return;
}
try {
const params = {
checkout_session
};
const result = await getService("payments").confirm(params);
if (result.error) {
return ctx[result.status](result.msg);
}
order = result;
} catch (err) {
console.log(err);
return ctx.internalServerError("something went wrong");
}
if (!order.confirmed) {
return ctx.badRequest("could not confirm payment");
}
await getService("courses").signIntoMultipleCourses({ user, courses });
ctx.body = {
courses,
is_new_account: false,
user_email: user.email,
checkout_session: order.checkout_session,
id: order.id,
documentId: order.documentId,
amount: order.amount,
confirmed: order.confirmed,
payment_method: order.payment_method,
createdAt: order.createdAt,
publishedAt: order.publishedAt,
updatedAt: order.updatedAt
};
},
async confirm(ctx) {
const { checkout_session } = ctx.request.body;
if (!checkout_session) {
return ctx.badRequest("checkout_session is required");
}
let order = await strapi2.documents(ORDER_MODEL).findFirst({
filters: {
checkout_session: {
$eq: checkout_session
}
},
populate: {
user: {
fields: ["id", "confirmed", "email"]
},
courses: courseQuery
}
});
if (!order) {
return ctx.badRequest("order not found");
}
let { user } = order;
if (!user) {
user = {
confirmed: false,
email: ""
};
}
const { courses } = order;
if (order.confirmed) {
ctx.body = {
courses: courses.map((c) => ({ slug: c.slug, id: c.id, documentId: c.documentId })),
is_new_account: !user.confirmed,
user_email: user.email,
checkout_session: order.checkout_session,
id: order.id,
documentId: order.documentId,
amount: order.amount,
confirmed: order.confirmed,
payment_method: order.payment_method,
createdAt: order.createdAt,
publishedAt: order.publishedAt,
updatedAt: order.updatedAt
};
return;
}
try {
const params = { checkout_session };
const result = await getService("payments").confirm(params);
if (result.error) {
return ctx[result.status](result.msg);
}
order = result;
user = order.user;
} catch (err) {
console.log(err);
return ctx.internalServerError("something went wrong");
}
if (!order.confirmed) {
return ctx.badRequest("could not confirm payment");
}
if (order.user && courses.length > 0) {
await getService("courses").signIntoMultipleCourses({ user: order.user, courses });
}
ctx.body = {
courses: courses.map((c) => ({ slug: c.slug, id: c.id, documentId: c.documentId })),
is_new_account: !