Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sale notifications #509

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions apps/web/app/dashboard2/blogs/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ export async function generateMetadata(
parent: ResolvingMetadata,
): Promise<Metadata> {
return {
title: `${MANAGE_BLOG_PAGE_HEADING} | ${
(await parent)?.title?.absolute
}`,
title: `${MANAGE_BLOG_PAGE_HEADING} | ${(await parent)?.title
?.absolute}`,
};
}

Expand Down
5 changes: 2 additions & 3 deletions apps/web/app/dashboard2/pages/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ export async function generateMetadata(
parent: ResolvingMetadata,
): Promise<Metadata> {
return {
title: `${MANAGE_PAGES_PAGE_HEADING} | ${
(await parent)?.title?.absolute
}`,
title: `${MANAGE_PAGES_PAGE_HEADING} | ${(await parent)?.title
?.absolute}`,
};
}

Expand Down
5 changes: 2 additions & 3 deletions apps/web/app/dashboard2/products/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ export async function generateMetadata(
parent: ResolvingMetadata,
): Promise<Metadata> {
return {
title: `${MANAGE_COURSES_PAGE_HEADING} | ${
(await parent)?.title?.absolute
}`,
title: `${MANAGE_COURSES_PAGE_HEADING} | ${(await parent)?.title
?.absolute}`,
};
}

Expand Down
5 changes: 2 additions & 3 deletions apps/web/app/dashboard2/settings/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ export async function generateMetadata(
parent: ResolvingMetadata,
): Promise<Metadata> {
return {
title: `${SITE_SETTINGS_PAGE_HEADING} | ${
(await parent)?.title?.absolute
}`,
title: `${SITE_SETTINGS_PAGE_HEADING} | ${(await parent)?.title
?.absolute}`,
};
}

Expand Down
6 changes: 3 additions & 3 deletions apps/web/app/verify-domain/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ export async function GET(req: Request) {
if (!domain) {
return Response.json(
{
message: `${responses.domain_doesnt_exist}: ${
host?.split(".")[0]
}`,
message: `${responses.domain_doesnt_exist}: ${host?.split(
".",
)[0]}`,
},
{ status: 404 },
);
Expand Down
10 changes: 5 additions & 5 deletions apps/web/components/admin/mails/sequence-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -633,11 +633,11 @@ const SequenceEditor = ({
value: tag.tag,
}))
: triggerType === "PRODUCT_PURCHASED"
? products.map((product) => ({
label: product.title,
value: product.courseId,
}))
: []
? products.map((product) => ({
label: product.title,
value: product.courseId,
}))
: []
}
/>
)}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components/admin/page-editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -570,8 +570,8 @@ function PageEditor({
typeof page.draftRobotsAllowed === "boolean"
? page.draftRobotsAllowed
: typeof page.robotsAllowed === "boolean"
? page.robotsAllowed
: true
? page.robotsAllowed
: true
}
socialImage={page.draftSocialImage || {}}
onClose={(e) => setLeftPaneContent("none")}
Expand Down
1 change: 1 addition & 0 deletions apps/web/config/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ export const responses = {
user_already_exists: "The user already exists",
unsubscribe_success:
"Sorry to see you go. You have been unsubscribed from our mailing list.",
sales_made_subject: `Yay! You have made a sale!`,
};

export const internal = {
Expand Down
4 changes: 2 additions & 2 deletions apps/web/graphql/settings/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
UIConstants,
} from "@courselit/common-models";

const currencyISOCodes = currencies.map((currency) =>
currency.isoCode?.toLowerCase(),
const currencyISOCodes = currencies.map(
(currency) => currency.isoCode?.toLowerCase(),
);

const verifyCurrencyISOCode = (isoCode: string) => {
Expand Down
74 changes: 73 additions & 1 deletion apps/web/lib/finalize-purchase.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import CourseModel, { Course } from "../models/Course";
import UserModel, { User } from "../models/User";
import PurchaseModel, { Purchase } from "@models/Purchase";
import DomainModel, { Domain } from "@models/Domain";
import { triggerSequences } from "./trigger-sequences";
import { recordActivity } from "./record-activity";
import { Constants, Progress } from "@courselit/common-models";
import saleEmailTemplate from "@/templates/sale-email";
import pug from "pug";
import { send } from "@/services/mail";
import { formattedLocaleDate } from "@ui-lib/utils";
import { error } from "@/services/logger";
import { responses } from "@/config/strings";
import getSymbolFromCurrency from "currency-symbol-map";

export default async (
const finalizePurchase = async (
userId: string,
courseId: string,
purchaseId?: string,
Expand Down Expand Up @@ -52,6 +61,69 @@ export default async (
purchaseId,
},
});

sendSaleNotificationToAdmins({
user,
course,
purchaseId: purchaseId!,
});
}
}
};

async function sendSaleNotificationToAdmins({
user,
course,
purchaseId,
}: {
user: User;
course: Course;
purchaseId: string;
}) {
try {
const domain: Domain | null = await DomainModel.findOne({
_id: user.domain,
});

const purchase: Purchase | null = await PurchaseModel.findOne({
orderId: purchaseId,
});

const usersWithManagePermissions = await UserModel.find(
{
domain: domain?._id,
$or: [
{ userId: course.creatorId, permissions: "course:manage" },
{ permissions: "course:manage_any" },
],
},
{ email: 1 },
).lean();

const courseAdminsEmails = usersWithManagePermissions.map(
(x) => x.email,
);

const currencySymbol = getSymbolFromCurrency(
domain?.settings?.currencyISOCode,
);
const emailBody = pug.render(saleEmailTemplate, {
order: purchase?.orderId,
courseName: course.title,
coursePrice: `${currencySymbol}${course.cost}`,
date: formattedLocaleDate(purchase!.purchasedOn),
email: user?.email,
hideCourseLitBranding: domain?.settings.hideCourseLitBranding,
});

await send({
to: courseAdminsEmails,
subject: responses.sales_made_subject,
body: emailBody,
});
} catch (err) {
error("Failed to send sale notification mail", err);
}
}

export default finalizePurchase;
4 changes: 2 additions & 2 deletions apps/web/pages/api/payment/initiate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { getPaymentMethod } from "../../../payments";
import PurchaseModel from "../../../models/Purchase";
import finalizePurchase from "../../../lib/finalize-purchase";
import { error } from "../../../services/logger";
import User from "@models/User";
import UserModel from "@models/User";
import DomainModel, { Domain } from "@models/Domain";
import { auth } from "@/auth";

Expand All @@ -32,7 +32,7 @@ export default async function handler(

let user;
if (session) {
user = await User.findOne({
user = await UserModel.findOne({
email: session.user!.email,
domain: domain._id,
active: true,
Expand Down
2 changes: 1 addition & 1 deletion apps/web/services/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
metadata,
});
} else {
console.error(severityError, message, metadata);
console.error(severityInfo, message, metadata);

Check warning

Code scanning / CodeQL

Log injection Medium

Log entry depends on a
user-provided value
.

Copilot Autofix AI about 1 month ago

To fix the log injection issue, we need to sanitize the user input before logging it. Specifically, we should remove any newline characters from the message parameter to prevent log injection. This can be done using the String.prototype.replace method to replace newline characters with an empty string.

We will apply this sanitization in the info, warn, and error functions in the apps/web/services/logger.ts file to ensure that all log messages are sanitized before being logged.

Suggested changeset 1
apps/web/services/logger.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/web/services/logger.ts b/apps/web/services/logger.ts
--- a/apps/web/services/logger.ts
+++ b/apps/web/services/logger.ts
@@ -9,2 +9,4 @@
 ) => {
+    // Sanitize message to remove newline characters
+    const sanitizedMessage = message.replace(/\n|\r/g, "");
     if (process.env.NODE_ENV === "production") {
@@ -12,3 +14,3 @@
             severity: severityInfo,
-            message,
+            message: sanitizedMessage,
             metadata,
@@ -16,3 +18,3 @@
     } else {
-        console.error(severityInfo, message, metadata);
+        console.error(severityInfo, sanitizedMessage, metadata);
     }
@@ -24,2 +26,4 @@
 ) => {
+    // Sanitize message to remove newline characters
+    const sanitizedMessage = message.replace(/\n|\r/g, "");
     if (process.env.NODE_ENV === "production") {
@@ -27,3 +31,3 @@
             severity: severityWarn,
-            message,
+            message: sanitizedMessage,
             metadata,
@@ -31,3 +35,3 @@
     } else {
-        console.error(severityError, message, metadata);
+        console.error(severityError, sanitizedMessage, metadata);
     }
@@ -43,2 +47,4 @@
 ) => {
+    // Sanitize message to remove newline characters
+    const sanitizedMessage = message.replace(/\n|\r/g, "");
     if (process.env.NODE_ENV === "production") {
@@ -46,3 +52,3 @@
             severity: severityError,
-            message,
+            message: sanitizedMessage,
             metadata,
@@ -50,3 +56,3 @@
     } else {
-        console.error(severityError, message, metadata);
+        console.error(severityError, sanitizedMessage, metadata);
     }
EOF
@@ -9,2 +9,4 @@
) => {
// Sanitize message to remove newline characters
const sanitizedMessage = message.replace(/\n|\r/g, "");
if (process.env.NODE_ENV === "production") {
@@ -12,3 +14,3 @@
severity: severityInfo,
message,
message: sanitizedMessage,
metadata,
@@ -16,3 +18,3 @@
} else {
console.error(severityInfo, message, metadata);
console.error(severityInfo, sanitizedMessage, metadata);
}
@@ -24,2 +26,4 @@
) => {
// Sanitize message to remove newline characters
const sanitizedMessage = message.replace(/\n|\r/g, "");
if (process.env.NODE_ENV === "production") {
@@ -27,3 +31,3 @@
severity: severityWarn,
message,
message: sanitizedMessage,
metadata,
@@ -31,3 +35,3 @@
} else {
console.error(severityError, message, metadata);
console.error(severityError, sanitizedMessage, metadata);
}
@@ -43,2 +47,4 @@
) => {
// Sanitize message to remove newline characters
const sanitizedMessage = message.replace(/\n|\r/g, "");
if (process.env.NODE_ENV === "production") {
@@ -46,3 +52,3 @@
severity: severityError,
message,
message: sanitizedMessage,
metadata,
@@ -50,3 +56,3 @@
} else {
console.error(severityError, message, metadata);
console.error(severityError, sanitizedMessage, metadata);
}
Copilot is powered by AI and may make mistakes. Always verify output.
Positive Feedback
Negative Feedback

Provide additional feedback

Please help us improve GitHub Copilot by sharing more details about this comment.

Please select one or more of the options
}
};

Expand Down
73 changes: 73 additions & 0 deletions apps/web/templates/sale-email.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const saleEmailTemplate = `
doctype html
html
head
style(type='text/css').
.email-container {
max-width: 960px;
}
.cta-container {
margin: 32px 0px;
text-align: center;
}
.cta {
border: 1px solid #07077b;
border-radius: 4px;
padding: 4px 8px;
text-decoration: none;
color: white;
background-color: #07077b;
font-weight: bold;
}
.cta:hover {
background-color: #060665;
}
.courselit-branding-container {
margin: 40px 0px;
}
.courselit-branding-cta {
text-decoration: none;
color: #000000;
padding: 6px 10px;
background-color: #FFFFFF;
border: 1px solid;
border-radius: 6px;
text-align: center;
}
.sale-notification-heading {
padding-bottom: 5px;
}
.sale-details {
padding-bottom: 10px;
}
.course-name-and-price {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
padding-bottom: 20px;
}
body
div(class="email-container")
div(class="sale-notification-heading")
p Yay! You have made a sale!
div(class="sale-details")
p <strong>Order</strong>: #{order}
p <strong>Date</strong>: #{date}
p <strong>Email</strong>: #{email}
div(class="course-name-and-price")
span #{courseName}
span #{coursePrice}
p(class="signature")
| Best,
p #[a(href="https://x.com/courselit") @CourseLit]
if !hideCourseLitBranding
div(class="courselit-branding-container")
a(
href="https://courselit.app"
target="_blank"
class="courselit-branding-cta"
) Powered by <strong>CourseLit</strong>
`;

export default saleEmailTemplate;
4 changes: 2 additions & 2 deletions packages/common-widgets/src/banner/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ export default function Widget({
: undefined
: description
: product.description
? JSON.parse(product.description as string)
: undefined;
? JSON.parse(product.description as string)
: undefined;

let direction: any;
switch (alignment) {
Expand Down
4 changes: 2 additions & 2 deletions packages/common-widgets/src/email-form/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ const Widget = ({
alignment === "center"
? "center"
: alignment === "right"
? "flex-end"
: "flex-start";
? "flex-end"
: "flex-start";

useEffect(() => {
if (state.config.turnstileSiteKey) {
Expand Down
4 changes: 2 additions & 2 deletions packages/common-widgets/src/header/widget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ export default function Widget({ state, settings }: WidgetProps) {
linkAlignment === "right"
? "justify-end"
: linkAlignment === "center"
? "justify-center"
: "justify-start"
? "justify-center"
: "justify-start"
}`}
style={{
gap: `${spacingBetweenLinks}px`,
Expand Down
18 changes: 9 additions & 9 deletions packages/common-widgets/src/hero/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,15 +198,15 @@ export default function Widget({
descriptionFontSize === 0
? "text-base"
: descriptionFontSize === 1
? "text-lg lg:text-xl"
: `text-${
descriptionFontSize -
1 ===
1
? ""
: descriptionFontSize -
1
}xl lg:text-${descriptionFontSize}xl`,
? "text-lg lg:text-xl"
: `text-${
descriptionFontSize -
1 ===
1
? ""
: descriptionFontSize -
1
}xl lg:text-${descriptionFontSize}xl`,
buttonAction && buttonCaption
? "mb-8"
: "mb-0",
Expand Down
Loading