Skip to content

Commit 751c20a

Browse files
author
Rajat Saxena
committed
Course completed activity logged; Razorpay explicit error; user filtering fix; Docker build fix
1 parent 1033252 commit 751c20a

File tree

8 files changed

+147
-40
lines changed

8 files changed

+147
-40
lines changed

apps/web/config/strings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export const responses = {
121121
default_payment_plan_required:
122122
"Mark a payment plan as default before enabling the community",
123123
community_content_already_reported: "Content already reported",
124-
profile_incomplete: "Complete your profile to join this community",
124+
profile_incomplete: "Complete your profile to perform this action",
125125
cannot_reject_member_with_active_subscription:
126126
"Cannot reject a member with an active subscription",
127127
cannot_leave_community_last_moderator:

apps/web/graphql/courses/logic.ts

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,18 @@ async function formatCourse(courseId: string, ctx: GQLContext) {
9292
ctx,
9393
});
9494

95+
if (
96+
[Constants.CourseType.COURSE, Constants.CourseType.DOWNLOAD].includes(
97+
course.type,
98+
)
99+
) {
100+
const { nextLesson } = await getPrevNextCursor(
101+
course.courseId,
102+
ctx.subdomain._id,
103+
);
104+
(course as any).firstLesson = nextLesson;
105+
}
106+
95107
const result = {
96108
...course,
97109
groups: course!.groups?.map((group: any) => ({
@@ -129,19 +141,19 @@ export const getCourse = async (
129141
}
130142

131143
if (course.published) {
132-
if (
133-
[constants.course, constants.download].includes(
134-
course.type as
135-
| typeof constants.course
136-
| typeof constants.download,
137-
)
138-
) {
139-
const { nextLesson } = await getPrevNextCursor(
140-
course.courseId,
141-
ctx.subdomain._id,
142-
);
143-
(course as any).firstLesson = nextLesson;
144-
}
144+
// if (
145+
// [constants.course, constants.download].includes(
146+
// course.type as
147+
// | typeof constants.course
148+
// | typeof constants.download,
149+
// )
150+
// ) {
151+
// const { nextLesson } = await getPrevNextCursor(
152+
// course.courseId,
153+
// ctx.subdomain._id,
154+
// );
155+
// (course as any).firstLesson = nextLesson;
156+
// }
145157
// course.groups = accessibleGroups;
146158
return await formatCourse(course.courseId, ctx);
147159
} else {
@@ -471,6 +483,7 @@ export const getProducts = async ({
471483
entityId: course.courseId,
472484
entityType: Constants.MembershipEntityType.COURSE,
473485
domain: ctx.subdomain._id,
486+
status: Constants.MembershipStatus.ACTIVE,
474487
})
475488
: undefined;
476489
const sales =

apps/web/graphql/lessons/logic.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ import {
1717
} from "./helpers";
1818
import constants from "../../config/constants";
1919
import GQLContext from "../../models/GQLContext";
20-
import { Course } from "../../models/Course";
2120
import { deleteMedia } from "../../services/medialit";
2221
import { recordProgress } from "../users/logic";
23-
import { Progress, Quiz } from "@courselit/common-models";
22+
import { Constants, Progress, Quiz } from "@courselit/common-models";
2423
import LessonEvaluation from "../../models/LessonEvaluation";
2524
import { checkPermission } from "@courselit/utils";
2625
import { recordActivity } from "../../lib/record-activity";
26+
import { InternalCourse } from "@courselit/common-logic";
2727

2828
const { permissions, quiz } = constants;
2929

@@ -130,7 +130,7 @@ export const createLesson = async (
130130
lessonValidator(lessonData);
131131

132132
try {
133-
const course: Course | null = await CourseModel.findOne({
133+
const course: InternalCourse | null = await CourseModel.findOne({
134134
courseId: lessonData.courseId,
135135
domain: ctx.subdomain._id,
136136
});
@@ -199,7 +199,7 @@ export const deleteLesson = async (id: string, ctx: GQLContext) => {
199199

200200
try {
201201
// remove from the parent Course's lessons array
202-
let course: Course | null = await CourseModel.findOne({
202+
let course: InternalCourse | null = await CourseModel.findOne({
203203
domain: ctx.subdomain._id,
204204
}).elemMatch("lessons", { $eq: lesson.lessonId });
205205
if (!course) {
@@ -223,7 +223,10 @@ export const deleteLesson = async (id: string, ctx: GQLContext) => {
223223
}
224224
};
225225

226-
export const getAllLessons = async (course: Course, ctx: GQLContext) => {
226+
export const getAllLessons = async (
227+
course: InternalCourse,
228+
ctx: GQLContext,
229+
) => {
227230
const lessons = await LessonModel.find(
228231
{
229232
lessonId: {
@@ -316,16 +319,46 @@ export const markLessonCompleted = async (
316319
await recordActivity({
317320
domain: ctx.subdomain._id,
318321
userId: ctx.user.userId,
319-
type: "lesson_completed",
322+
type: Constants.ActivityType.LESSON_COMPLETED,
320323
entityId: lesson.lessonId,
321324
metadata: {
322325
courseId: lesson.courseId,
323326
},
324327
});
325328

329+
await recordCourseCompleted(lesson.courseId, ctx);
330+
326331
return true;
327332
};
328333

334+
const recordCourseCompleted = async (courseId: string, ctx: GQLContext) => {
335+
const course = await CourseModel.findOne({ courseId });
336+
if (!course) {
337+
throw new Error(responses.item_not_found);
338+
}
339+
340+
const isCourseCompleted = course.lessons.every((lessonId) => {
341+
const progress = ctx.user.purchases.find(
342+
(progress: Progress) => progress.courseId === courseId,
343+
);
344+
if (!progress) {
345+
return false;
346+
}
347+
return progress.completedLessons.includes(lessonId);
348+
});
349+
350+
if (!isCourseCompleted) {
351+
return;
352+
}
353+
354+
await recordActivity({
355+
domain: ctx.subdomain._id,
356+
userId: ctx.user.userId,
357+
type: Constants.ActivityType.COURSE_COMPLETED,
358+
entityId: courseId,
359+
});
360+
};
361+
329362
export const evaluateLesson = async (
330363
lessonId: string,
331364
answers: { answers: number[][] },

apps/web/models/GQLContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { InternalUser } from "@courselit/common-logic";
12
import { Domain } from "./Domain";
2-
import { User } from "./User";
33

44
export default interface GQLContext {
5-
user: User;
5+
user: InternalUser;
66
subdomain: Domain;
77
address: string;
88
}

apps/web/pages/course/[slug]/[id]/index.tsx

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ import { ArrowRight, CheckCircled, Circle, Lock } from "@courselit/icons";
1010
import {
1111
COURSE_PROGRESS_START,
1212
ENROLL_BUTTON_TEXT,
13-
FREE_COST,
1413
SIDEBAR_TEXT_COURSE_ABOUT,
1514
} from "../../../../ui-config/strings";
16-
import { FetchBuilder, checkPermission } from "@courselit/utils";
15+
import { FetchBuilder, checkPermission, getPlanPrice } from "@courselit/utils";
1716
import {
1817
AppState,
1918
AppDispatch,
@@ -34,7 +33,11 @@ import RouteBasedComponentScaffold, {
3433
Divider,
3534
} from "@components/public/scaffold";
3635
import Article from "@components/public/article";
37-
import { Link, Button2, PriceTag } from "@courselit/components-library";
36+
import {
37+
Link,
38+
Button2,
39+
getSymbolFromCurrency,
40+
} from "@courselit/components-library";
3841
import { useSession } from "next-auth/react";
3942
import { useEffect, useState } from "react";
4043
const { permissions } = UIConstants;
@@ -52,6 +55,8 @@ type CourseWithoutGroups = Pick<
5255
| "cost"
5356
| "courseId"
5457
| "tags"
58+
| "paymentPlans"
59+
| "defaultPaymentPlan"
5560
>;
5661

5762
export type CourseFrontend = CourseWithoutGroups & {
@@ -104,6 +109,18 @@ export const graphQuery = `
104109
},
105110
tags,
106111
firstLesson
112+
paymentPlans {
113+
planId
114+
name
115+
type
116+
oneTimeAmount
117+
emiAmount
118+
emiTotalInstallments
119+
subscriptionMonthlyAmount
120+
subscriptionYearlyAmount
121+
}
122+
leadMagnet
123+
defaultPaymentPlan
107124
}
108125
}
109126
`;
@@ -264,6 +281,12 @@ const CourseViewer = (props: CourseProps) => {
264281
} catch (err: any) {}
265282
};
266283

284+
const { amount, period } = getPlanPrice(
285+
course?.paymentPlans.find(
286+
(x) => x.planId === course?.defaultPaymentPlan,
287+
)!,
288+
);
289+
267290
return (
268291
<>
269292
<Head>
@@ -303,14 +326,18 @@ const CourseViewer = (props: CourseProps) => {
303326
<div>
304327
<p>{profile.fetched}</p>
305328
<div className="flex justify-between items-center">
306-
<PriceTag
307-
cost={course.cost}
308-
freeCostCaption={FREE_COST}
309-
currencyISOCode={
310-
props.siteInfo.currencyISOCode as string
311-
}
312-
/>
313-
<Link href={`/checkout/${course.courseId}`}>
329+
<div className="font-medium flex items-center">
330+
{getSymbolFromCurrency(
331+
props.siteInfo.currencyISOCode,
332+
)}
333+
{amount}
334+
<span className="text-sm text-muted-foreground ml-1">
335+
{period}
336+
</span>
337+
</div>
338+
<Link
339+
href={`/checkout?type=course&id=${course.courseId}`}
340+
>
314341
<Button2>{ENROLL_BUTTON_TEXT}</Button2>
315342
</Link>
316343
</div>
@@ -398,6 +425,8 @@ export function formatCourse(
398425
groups: post.groups as GroupWithLessons[],
399426
tags: post.tags,
400427
firstLesson: post.firstLesson,
428+
paymentPlans: post.paymentPlans,
429+
defaultPaymentPlan: post.defaultPaymentPlan,
401430
};
402431
}
403432

apps/web/payments-new/razorpay-payment.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,11 @@ export default class RazorpayPayment implements Payment {
125125
},
126126
(err, plan) => {
127127
if (err) {
128-
reject(new Error(err.error.description));
128+
reject(
129+
new Error(
130+
`Error creating plan on Razorpay: ${err.error.description}`,
131+
),
132+
);
129133
}
130134
this.razorpay.subscriptions.create(
131135
{
@@ -169,7 +173,11 @@ export default class RazorpayPayment implements Payment {
169173
},
170174
(err, order) => {
171175
if (err) {
172-
reject(new Error(err.error.description));
176+
reject(
177+
new Error(
178+
`Error creating plan on Razorpay: ${err.error.description}`,
179+
),
180+
);
173181
}
174182
resolve(order);
175183
},

packages/common-logic/src/utils/convert-filters-to-db-conditions.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,40 @@ export async function convertFiltersToDBConditions({
4242
}
4343
dbFilters.push(emailCondition);
4444
}
45-
if (name === "product" || name === "community") {
45+
if (name === "product") {
4646
const productCondition: { userId: unknown } = {
4747
userId: undefined,
4848
};
4949
const memberships = await membershipModel.find(
5050
{
5151
domain,
52-
entityType:
53-
name === "product"
54-
? Constants.MembershipEntityType.COURSE
55-
: Constants.MembershipEntityType.COMMUNITY,
52+
entityType: Constants.MembershipEntityType.COURSE,
53+
entityId: value,
54+
},
55+
{
56+
userId: 1,
57+
},
58+
);
59+
if (condition === "Has") {
60+
productCondition.userId = {
61+
$in: memberships.map((m) => m.userId),
62+
};
63+
}
64+
if (condition === "Does not have") {
65+
productCondition.userId = {
66+
$not: { $in: memberships.map((m) => m.userId) },
67+
};
68+
}
69+
dbFilters.push(productCondition);
70+
}
71+
if (name === "community") {
72+
const productCondition: { userId: unknown } = {
73+
userId: undefined,
74+
};
75+
const memberships = await membershipModel.find(
76+
{
77+
domain,
78+
entityType: Constants.MembershipEntityType.COMMUNITY,
5679
entityId: value,
5780
},
5881
{

services/app/Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ COPY packages/icons /app/packages/icons
1717
COPY packages/tailwind-config /app/packages/tailwind-config
1818
COPY packages/tsconfig /app/packages/tsconfig
1919
COPY packages/common-models /app/packages/common-models
20+
COPY packages/common-logic /app/packages/common-logic
2021
COPY packages/common-widgets /app/packages/common-widgets
2122
COPY packages/components-library /app/packages/components-library
2223
COPY packages/state-management /app/packages/state-management

0 commit comments

Comments
 (0)