feat: remove defer (#243)
* working on moving the email logic to the backend * Moved nodemailer to the backend * uninstall defer * updated relative import path in tsconfig * working on the zod validation * Merge branch 'main' into send-email * fixed the zod import issue * fixed the zod import issue * updated the env example files * docs: update README.md (#249) * fix: handle catex error * chore: remove fly.io github action * Update README.md Updated the Readme to reflect changes made to the emailing flow --------- Co-authored-by: Rotimi Best <[email protected]> * chore: wrapp up email sending --------- Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
@@ -76,7 +76,7 @@ Here is what you need to be able to run ClassroomIO.com
|
||||
This repo is a mono repo that consists of 3 projects:
|
||||
|
||||
1. `classroomio-com`: The landing page of ClassroomIO hosted [here](https://classroomio.com)
|
||||
2. `backend`: The backend service that handles PDF & video processing.
|
||||
2. `backend`: The backend service that handles PDF, video processing, Emailing and Notifications.
|
||||
3. `dashboard`: The web application that runs the learning management system hosted [here](https://app.classroomio.com).
|
||||
4. `docs`: Official documentation of ClassroomIO hosted [here](https://classroomio.com/docs)
|
||||
|
||||
@@ -122,8 +122,11 @@ This repo is a mono repo that consists of 3 projects:
|
||||
|
||||
4. Set up your `.env` file
|
||||
|
||||
- Go to `apps/classroomio-com` and `apps/dashboard`
|
||||
- Go to `apps/classroomio-com`, `apps/dashboard` and `apps/backend`.
|
||||
- Duplicate the `.env.example` file and rename it to `.env`
|
||||
- Populate your .env files with the neccessary variables
|
||||
|
||||
To get the environmental variables for supabase continue to step(5)
|
||||
|
||||
5. Setup Supabase.
|
||||
|
||||
|
||||
@@ -13,3 +13,10 @@ CLOUDFLARE_PUBLIC_ACCOUNT_ID=
|
||||
MUSE_API_KEY=
|
||||
REDIS_API_KEY=
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
|
||||
SMTP_HOST=
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_USER_NOTIFY=
|
||||
SMTP_PASSWORD_NOTIFY=
|
||||
SMTP_PORT=
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"@aws-sdk/client-s3": "^3.388.0",
|
||||
"@aws-sdk/lib-storage": "^3.535.0",
|
||||
"@supabase/supabase-js": "^2.32.0",
|
||||
"@types/nodemailer": "^6.4.11",
|
||||
"axios": "^1.4.0",
|
||||
"body-parser": "^1.20.2",
|
||||
"bull": "^4.11.3",
|
||||
@@ -33,6 +34,7 @@
|
||||
"katex": "^0.16.10",
|
||||
"marked": "^7.0.3",
|
||||
"multer": "^1.4.3",
|
||||
"nodemailer": "^6.9.5",
|
||||
"nodemon": "^3.0.1",
|
||||
"puppeteer": "^20.9.0",
|
||||
"zod": "^3.21.4"
|
||||
|
||||
@@ -10,6 +10,7 @@ const downloadCertificate = require('./src/routes/downloadCertificate');
|
||||
const downloadLesson = require('./src/routes/downloadLesson');
|
||||
const downloadCourse = require('./src/routes/downloadCourse');
|
||||
const katex = require('./src/routes/katex');
|
||||
const sendEmail = require('./src/routes/sendEmail');
|
||||
|
||||
// Express server
|
||||
const app = express();
|
||||
@@ -34,6 +35,7 @@ app.use('/downloadLesson', downloadLesson);
|
||||
app.use('/downloadCourse', downloadCourse);
|
||||
app.use('/uploadVideo', uploadVideo);
|
||||
app.use('/katex', katex);
|
||||
app.use('/sendEmail', sendEmail);
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Welcome to ClassroomIO');
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
const express = require('express');
|
||||
const zod = require('zod');
|
||||
const { getTransporter } = require('../utils/getTransporter');
|
||||
const { withEmailTemplate } = require('../utils/withEmailTemplate');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
try {
|
||||
const mySchema = zod.array(
|
||||
zod.object({
|
||||
from: zod.string().optional(),
|
||||
to: zod.string(),
|
||||
subject: zod.string(),
|
||||
content: zod.string(),
|
||||
isPersonalEmail: zod.boolean().optional(),
|
||||
replyTo: zod.string().optional()
|
||||
})
|
||||
);
|
||||
|
||||
mySchema.parse(req.body);
|
||||
|
||||
const emailDataArray = req.body;
|
||||
|
||||
Promise.all(
|
||||
emailDataArray.map(async (emailData) => {
|
||||
try {
|
||||
const { from, to, subject, content, isPersonalEmail, replyTo } = emailData;
|
||||
|
||||
const transporter = await getTransporter(isPersonalEmail);
|
||||
|
||||
if (!transporter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await transporter.sendMail({
|
||||
from: from || '"Best from ClassroomIO" <[email protected]>',
|
||||
to,
|
||||
subject,
|
||||
replyTo,
|
||||
html: withEmailTemplate(content)
|
||||
});
|
||||
|
||||
console.log('Email status', res);
|
||||
} catch (error) {
|
||||
console.error('Error sending email', error);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
console.log('Email sent');
|
||||
return res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error sending emails:', error);
|
||||
return res.status(500).json({ error: 'Internal Server Error' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,37 @@
|
||||
const nodemailer = require('nodemailer');
|
||||
|
||||
const { SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_USER_NOTIFY, SMTP_PASSWORD_NOTIFY } =
|
||||
process.env;
|
||||
|
||||
const getTransporter = async (isPersonal = false) => {
|
||||
if (
|
||||
!SMTP_HOST ||
|
||||
!SMTP_PORT ||
|
||||
!SMTP_USER ||
|
||||
!SMTP_PASSWORD ||
|
||||
!SMTP_USER_NOTIFY ||
|
||||
!SMTP_PASSWORD_NOTIFY
|
||||
) {
|
||||
console.error('SMTP configuration missing');
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: SMTP_HOST,
|
||||
port: parseInt(SMTP_PORT || '465', 10),
|
||||
secure: true,
|
||||
auth: {
|
||||
user: isPersonal ? SMTP_USER : SMTP_USER_NOTIFY,
|
||||
pass: isPersonal ? SMTP_PASSWORD : SMTP_PASSWORD_NOTIFY
|
||||
}
|
||||
});
|
||||
await transporter.verify();
|
||||
return transporter;
|
||||
} catch (error) {
|
||||
console.error('Transporter error:', error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { getTransporter };
|
||||
@@ -0,0 +1,220 @@
|
||||
const withEmailTemplate = (content) =>
|
||||
`<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, minimum-scale=1"
|
||||
/>
|
||||
<base target="_blank" />
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #fff;
|
||||
font-family: "Poppins", "Helvetica Neue", "Segoe UI", Helvetica,
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
line-height: 26px;
|
||||
margin: 0;
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f4f4f4;
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top: 1px dashed #94a3b8;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
table td {
|
||||
padding: 5px;
|
||||
}
|
||||
.socialicons {
|
||||
max-width: 200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 27px;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
background-color: #f1f5f9;
|
||||
padding: 30px;
|
||||
max-width: 525px;
|
||||
margin: 0 auto;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.brandcolor {
|
||||
color: #00c4b8;
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
background-color: #f1f5f9;
|
||||
padding: 1rem;
|
||||
border-radius: 1rem;
|
||||
color: #475569;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.tooltip a {
|
||||
color: #1e293b;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin-top:12px;
|
||||
background: #0233BD;
|
||||
border-radius: 8px;
|
||||
text-decoration: none !important;
|
||||
color: #fff !important;
|
||||
font-weight: 500;
|
||||
padding: 10px 30px;
|
||||
display: inline-block;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.button:hover {
|
||||
background: #0233BD;
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
.footer a {
|
||||
color: #cbd5e1;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.gutter {
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.gutter img {
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0233BD;
|
||||
}
|
||||
a:hover {
|
||||
color: #0233BD;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
font-weight: 600;
|
||||
}
|
||||
@media screen and (max-width: 600px) {
|
||||
.wrap {
|
||||
max-width: auto;
|
||||
}
|
||||
.gutter {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
style="
|
||||
background-color: #fff;
|
||||
font-family: 'Poppins', 'Helvetica Neue', 'Segoe UI', Helvetica,
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 26px;
|
||||
margin: 0;
|
||||
color: #1e293b;
|
||||
"
|
||||
>
|
||||
<div class="gutter" style="padding: 30px">
|
||||
<a href="https://classroomio.com" target="_blank">
|
||||
<img
|
||||
src="https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/classroomio-logo-full.png"
|
||||
alt="ClassroomIO Logo"
|
||||
/></a>
|
||||
</div>
|
||||
<div
|
||||
class="wrap"
|
||||
style="
|
||||
background-color: #f1f5f9;
|
||||
padding: 30px;
|
||||
max-width: 525px;
|
||||
margin: 0 auto;
|
||||
border-radius: 12px;
|
||||
"
|
||||
>
|
||||
${content}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="footer"
|
||||
style="text-align: center; font-size: 12px; color: #cbd5e1"
|
||||
>
|
||||
<table class="socialicons">
|
||||
<tr>
|
||||
<td>
|
||||
<a target="_blank" href="https://twitter.com/classroomio"
|
||||
><img
|
||||
title="X"
|
||||
src="https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/classroomio-x.png"
|
||||
alt="X"
|
||||
width="32"
|
||||
/></a>
|
||||
</td>
|
||||
<td>
|
||||
<a target="_blank" href="https://www.youtube.com/@ClassroomIO"
|
||||
><img
|
||||
title="Youtube"
|
||||
src="https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/classroomio-youtube.png"
|
||||
alt="Youtube"
|
||||
width="32"
|
||||
/></a>
|
||||
</td>
|
||||
<td>
|
||||
<a target="_blank" href="https://www.facebook.com/classroomiohq"
|
||||
><img
|
||||
title="Facebook"
|
||||
src="https://tapaozmyjsuykgerrfkt.supabase.co/storage/v1/object/public/avatars/classroomio-facebook.png"
|
||||
alt="Facebook"
|
||||
width="32"
|
||||
/></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<p style="padding-top: 8px; line-height: initial">
|
||||
ClassroomIO ${new Date().getFullYear()}. All rights reserved.<br />
|
||||
<a
|
||||
style="text-decoration: none"
|
||||
href="https://app.enzuzo.com/policies/tos/958fc978-5477-11ee-a03b-7b111830c594"
|
||||
target="_blank"
|
||||
>Terms</a
|
||||
>
|
||||
|
|
||||
<a
|
||||
style="text-decoration: none"
|
||||
href="https://app.enzuzo.com/policies/privacy/958fc978-5477-11ee-a03b-7b111830c594"
|
||||
target="_blank"
|
||||
>Privacy Policy</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
module.exports = { withEmailTemplate };
|
||||
@@ -4,13 +4,6 @@ PUBLIC_SUPABASE_URL=http://localhost:54321
|
||||
PUBLIC_SUPABASE_ANON_KEY=
|
||||
PRIVATE_SUPABASE_SERVICE_ROLE=
|
||||
|
||||
# Email setup - Optional if you want to get/send emails
|
||||
SMTP_PASSWORD_NOTIFY=
|
||||
SMTP_USER_NOTIFY=
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
|
||||
# Unsplash setup - Optional if you want to use Unsplash images
|
||||
UNSPLASH_API_KEY=
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
import { defer } from '@defer/client';
|
||||
|
||||
import { withEmailTemplate } from '../lib/utils/services/notification/template';
|
||||
import { getTransporter } from '../lib/utils/services/notification/send';
|
||||
|
||||
const sendEmail = async ({
|
||||
from,
|
||||
to,
|
||||
subject,
|
||||
content,
|
||||
isPersonalEmail,
|
||||
replyTo
|
||||
}: {
|
||||
from?: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
content: string;
|
||||
isPersonalEmail?: boolean;
|
||||
replyTo?: string;
|
||||
}) => {
|
||||
try {
|
||||
const transporter = await getTransporter(isPersonalEmail);
|
||||
|
||||
if (!transporter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await transporter.sendMail({
|
||||
from: from || '"Best from ClassroomIO" <[email protected]>', // sender address
|
||||
to, // list of receivers
|
||||
subject, // Subject line
|
||||
replyTo,
|
||||
html: withEmailTemplate(content) // html body
|
||||
});
|
||||
|
||||
console.log('Message sent: %s', info.messageId);
|
||||
|
||||
return info;
|
||||
} catch (error) {
|
||||
console.error({ error });
|
||||
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
export default defer(sendEmail);
|
||||
@@ -1,42 +0,0 @@
|
||||
import { defer } from '@defer/client';
|
||||
|
||||
import { withEmailTemplate } from '../lib/utils/services/notification/template';
|
||||
import { getTransporter } from '../lib/utils/services/notification/send';
|
||||
|
||||
// Send multiple emails
|
||||
const sendEmails = async (
|
||||
emailParams: {
|
||||
from?: string;
|
||||
to: string;
|
||||
subject: string;
|
||||
content: string;
|
||||
isPersonalEmail?: boolean;
|
||||
replyTo?: string;
|
||||
}[]
|
||||
) => {
|
||||
for (const emailParam of emailParams) {
|
||||
const { from, to, subject, content, isPersonalEmail, replyTo } = emailParam;
|
||||
|
||||
try {
|
||||
const transporter = await getTransporter(isPersonalEmail);
|
||||
|
||||
if (!transporter) {
|
||||
return;
|
||||
}
|
||||
|
||||
const info = await transporter.sendMail({
|
||||
from: from || '"Best from ClassroomIO" <[email protected]>', // sender address
|
||||
to, // list of receivers
|
||||
subject, // Subject line
|
||||
replyTo,
|
||||
html: withEmailTemplate(content) // html body
|
||||
});
|
||||
|
||||
console.log('Message sent: %s', info.messageId);
|
||||
} catch (error) {
|
||||
console.error({ error });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default defer(sendEmails);
|
||||
@@ -25,7 +25,6 @@
|
||||
triggerSendEmail
|
||||
} from '$lib/utils/services/notification/notification';
|
||||
import { lesson } from '../store/lessons';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
|
||||
export let preview: false;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PUBLIC_SERVER_URL } from '$env/static/public';
|
||||
|
||||
const sendEmail = async (
|
||||
emailDataArray: {
|
||||
from?: string;
|
||||
to: any;
|
||||
subject: string;
|
||||
content: any;
|
||||
replyTo?: any;
|
||||
isPersonalEmail?: boolean;
|
||||
}[]
|
||||
) => {
|
||||
try {
|
||||
const response = await fetch(`${PUBLIC_SERVER_URL}/sendEmail`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(emailDataArray)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log('Failed to send emails');
|
||||
return;
|
||||
}
|
||||
|
||||
const responseData = await response.json();
|
||||
console.log('Emails sent successfully:', responseData);
|
||||
return responseData;
|
||||
} catch (error) {
|
||||
console.error('Error sending emails:', error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
export default sendEmail;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -24,12 +24,16 @@ export async function POST({ request }) {
|
||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||
}
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
||||
to,
|
||||
subject: `[Submitted]: ${exerciseTitle}`,
|
||||
content
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import { getFeedForNotification } from '$lib/utils/services/newsfeed/index';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmails from '$defer/sendEmails';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -13,8 +12,8 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
|
||||
const postLink = `https://${feed.org.siteName}.classroomio.com/courses/${feed.courseId}?feedId=${feed.id}`;
|
||||
|
||||
if (comment) {
|
||||
// send only to teacher
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
from: `"${feed.org.name} - ClassroomIO" <[email protected]>`,
|
||||
to: feed.teacherEmail,
|
||||
subject: `[${feed.courseTitle}] - News feed comment`,
|
||||
@@ -25,8 +24,13 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
|
||||
<div>
|
||||
<a class="button" href="${postLink}">View comment</a>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
`,
|
||||
isPersonalEmail: false,
|
||||
replyTo: '[email protected]'
|
||||
}
|
||||
];
|
||||
|
||||
await sendEmail(emailData);
|
||||
|
||||
// dont continue
|
||||
return;
|
||||
@@ -37,7 +41,7 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
|
||||
}
|
||||
|
||||
// else send to everyone except the author of the post
|
||||
const emails = feed.courseMembers.map((member) => ({
|
||||
const emailsData = feed.courseMembers.map((member) => ({
|
||||
from: `"${feed.org.name} - ClassroomIO" <[email protected]>`,
|
||||
to: member.email,
|
||||
replyTo: feed.teacherEmail,
|
||||
@@ -53,8 +57,8 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
|
||||
}));
|
||||
console.log('Sending emails to all students', feed.courseMembers.length);
|
||||
|
||||
// This is the defer function with a loop
|
||||
await sendEmails(emails);
|
||||
// This is the email sending function with a loop
|
||||
await sendEmail(emailsData);
|
||||
};
|
||||
|
||||
export async function POST({ request }) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
// import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
// const supabase = getSupabase();
|
||||
|
||||
@@ -32,7 +32,8 @@ export async function POST({ request }) {
|
||||
// return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||
// }
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
from: `"${orgName} - ClassroomIO" <[email protected]>`,
|
||||
to,
|
||||
replyTo: teacherEmail,
|
||||
@@ -44,7 +45,10 @@ export async function POST({ request }) {
|
||||
<p>Talk to you soon and see you in class.</p>
|
||||
<p>${orgName}</p>
|
||||
`
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -26,7 +26,8 @@ export async function POST({ request }) {
|
||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||
}
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
||||
to,
|
||||
subject: `${orgName} - Welcome to Class 🎉`,
|
||||
@@ -38,7 +39,9 @@ export async function POST({ request }) {
|
||||
<p>Cheers,</p>
|
||||
<p>${orgName}</p>
|
||||
`
|
||||
});
|
||||
}
|
||||
];
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -24,12 +24,16 @@ export async function POST({ request }) {
|
||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||
}
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
||||
to,
|
||||
subject: 'Submission Update',
|
||||
content
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
// import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
// const supabase = getSupabase();
|
||||
|
||||
@@ -31,7 +31,8 @@ export async function POST({ request }) {
|
||||
// return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||
// }
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
from: `ClassroomIO" <[email protected]>`,
|
||||
to,
|
||||
subject: `[${courseName}] Request to Join Course!`,
|
||||
@@ -44,7 +45,9 @@ export async function POST({ request }) {
|
||||
Email: ${studentEmail}
|
||||
</p>
|
||||
`
|
||||
});
|
||||
}
|
||||
];
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -25,7 +25,8 @@ export async function POST({ request }) {
|
||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||
}
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
from: `"ClassroomIO" <[email protected]>`,
|
||||
to,
|
||||
subject: `[${courseName}] You've got a new student 🎉!`,
|
||||
@@ -36,7 +37,10 @@ export async function POST({ request }) {
|
||||
<p>If you run into any issues, please don’t fail to reach out to us, we’d love to make your teaching
|
||||
experience as easy as possible.</p>
|
||||
`
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -28,7 +28,8 @@ export async function POST({ request }) {
|
||||
const origin = request.headers.get('origin');
|
||||
const inviteLink = `${origin}/org/${orgSiteName}/courses`;
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
||||
to,
|
||||
subject: `You have been invited to a course in ${orgName}!`,
|
||||
@@ -40,7 +41,10 @@ export async function POST({ request }) {
|
||||
<a class="button" href="${inviteLink}">Open Dashboard</a>
|
||||
</div>
|
||||
`
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -42,7 +42,8 @@ export async function POST({ request }) {
|
||||
const inviteLink = `${origin}/invite/t/${encodeURIComponent(btoa(inviteData))}`;
|
||||
console.log('inviteData', inviteData);
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
to: email,
|
||||
subject: `Join ${name} on ClassroomIO 😃`,
|
||||
content: `
|
||||
@@ -53,7 +54,9 @@ export async function POST({ request }) {
|
||||
</div>
|
||||
`,
|
||||
isPersonalEmail: true
|
||||
});
|
||||
}
|
||||
];
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -35,7 +35,8 @@ export async function POST({ request }) {
|
||||
btoa(verificationData)
|
||||
)}`;
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
to,
|
||||
subject: '[ClassroomIO]: Verify your email',
|
||||
content: `
|
||||
@@ -43,10 +44,13 @@ export async function POST({ request }) {
|
||||
<p>To verify your email, please click the <strong>Verify</strong> button below </p>
|
||||
<div>
|
||||
<a class="button" href="${verificationLink}">Verify</a>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
isPersonalEmail: true
|
||||
});
|
||||
}
|
||||
];
|
||||
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
import sendEmail from '$defer/sendEmail';
|
||||
import sendEmail from '$nodemailer/sendEmail';
|
||||
|
||||
const supabase = getSupabase();
|
||||
|
||||
@@ -25,7 +25,8 @@ export async function POST({ request }) {
|
||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||
}
|
||||
|
||||
await sendEmail({
|
||||
const emailData = [
|
||||
{
|
||||
to,
|
||||
subject: 'Thank you so so so much for choosing ClassroomIO!',
|
||||
content: `
|
||||
@@ -48,7 +49,9 @@ export async function POST({ request }) {
|
||||
</div>
|
||||
`,
|
||||
isPersonalEmail: true
|
||||
});
|
||||
}
|
||||
];
|
||||
await sendEmail(emailData);
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
|
||||
@@ -12,7 +12,7 @@ const config = {
|
||||
adapter: vercelAdapter(),
|
||||
alias: {
|
||||
$lib: path.resolve('./src/lib'),
|
||||
$defer: path.resolve('./src/defer')
|
||||
$nodemailer: path.resolve('./src/nodemailer')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"paths": {
|
||||
"$lib": ["./src/lib"],
|
||||
"$lib/*": ["./src/lib/*"],
|
||||
"$defer": ["./src/defer"],
|
||||
"$defer/*": ["./src/defer/*"]
|
||||
"$nodemailer": ["./src/nodemailer"],
|
||||
"$nodemailer/*": ["./src/nodemailer/*"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Generated
+18
-24
@@ -47,6 +47,9 @@ importers:
|
||||
'@supabase/supabase-js':
|
||||
specifier: ^2.32.0
|
||||
version: 2.39.6
|
||||
'@types/nodemailer':
|
||||
specifier: ^6.4.11
|
||||
version: 6.4.11
|
||||
axios:
|
||||
specifier: ^1.4.0
|
||||
version: 1.6.2
|
||||
@@ -80,6 +83,9 @@ importers:
|
||||
multer:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.4
|
||||
nodemailer:
|
||||
specifier: ^6.9.5
|
||||
version: 6.9.5
|
||||
nodemon:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.3
|
||||
@@ -158,7 +164,7 @@ importers:
|
||||
version: 1.13.6([email protected])([email protected])([email protected])([email protected])
|
||||
'@defer/client':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
version: 2.3.0
|
||||
'@lemonsqueezy/lemonsqueezy.js':
|
||||
specifier: ^2.2.0
|
||||
version: 2.2.0
|
||||
@@ -1607,7 +1613,7 @@ packages:
|
||||
'@babel/traverse': 7.23.4
|
||||
'@babel/types': 7.23.4
|
||||
convert-source-map: 1.9.0
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -1630,7 +1636,7 @@ packages:
|
||||
'@babel/traverse': 7.23.4
|
||||
'@babel/types': 7.23.4
|
||||
convert-source-map: 2.0.0
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
gensync: 1.0.0-beta.2
|
||||
json5: 2.2.3
|
||||
semver: 6.3.1
|
||||
@@ -1787,7 +1793,7 @@ packages:
|
||||
'@babel/core': 7.22.9
|
||||
'@babel/helper-compilation-targets': 7.22.15
|
||||
'@babel/helper-plugin-utils': 7.22.5
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
lodash.debounce: 4.0.8
|
||||
resolve: 1.22.8
|
||||
transitivePeerDependencies:
|
||||
@@ -3102,7 +3108,7 @@ packages:
|
||||
'@babel/helper-split-export-declaration': 7.22.6
|
||||
'@babel/parser': 7.23.4
|
||||
'@babel/types': 7.23.4
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
@@ -3221,8 +3227,8 @@ packages:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
|
||||
/@defer/client@2.2.0:
|
||||
resolution: {integrity: sha512-WYHhPr8Tk5t1T5mai80l79lPEsTlt06adTVJc7GP6zpBW28Qy9YJvJlmUV+I0jpMuIRq/UuSOZRJpAGyjJMetg==}
|
||||
/@defer/client@2.3.0:
|
||||
resolution: {integrity: sha512-2pCQrasR7+mR5EeOK1NgmP2sXk8Hao4IRnkRLcO2QqJpNJnUJE177m+RhEhp2SCebdIG6MXW4/NwNE5sz/uL8w==}
|
||||
engines: {node: '>=18'}
|
||||
dependencies:
|
||||
parse-duration: 1.1.0
|
||||
@@ -5775,7 +5781,7 @@ packages:
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 2.5.3([email protected])([email protected])
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
svelte: 4.1.2
|
||||
vite: 4.4.8(@types/[email protected])([email protected])
|
||||
transitivePeerDependencies:
|
||||
@@ -5789,7 +5795,7 @@ packages:
|
||||
vite: ^4.0.0
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 1.0.4(@sveltejs/[email protected])([email protected])([email protected])
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
deepmerge: 4.3.1
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.5
|
||||
@@ -6089,7 +6095,6 @@ packages:
|
||||
resolution: {integrity: sha512-Ld2c0frwpGT4VseuoeboCXQ7UJIkK3X7Lx/4YsZEiUHtHsthWAOCYtf6PAiLhMtfwV0cWJRabLBS3+LD8x6Nrw==}
|
||||
dependencies:
|
||||
'@types/node': 18.11.10
|
||||
dev: true
|
||||
|
||||
/@types/[email protected]:
|
||||
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==}
|
||||
@@ -6568,7 +6573,7 @@ packages:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
@@ -8379,17 +8384,6 @@ packages:
|
||||
ms: 2.1.3
|
||||
dev: true
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
/[email protected]([email protected]):
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
@@ -10486,7 +10480,7 @@ packages:
|
||||
engines: {node: '>= 6'}
|
||||
dependencies:
|
||||
agent-base: 6.0.2
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
@@ -11013,7 +11007,7 @@ packages:
|
||||
resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
|
||||
engines: {node: '>=10'}
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
debug: 4.3.4([email protected])
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
source-map: 0.6.1
|
||||
transitivePeerDependencies:
|
||||
|
||||
@@ -466,3 +466,8 @@ INSERT INTO "storage"."buckets" ("id", "name", "owner", "created_at", "updated_a
|
||||
--
|
||||
-- Data for Name: objects; Type: TABLE DATA; Schema: storage; Owner: supabase_storage_admin
|
||||
--
|
||||
|
||||
-- Get the sequence name from supabase database
|
||||
SELECT PG_GET_SERIAL_SEQUENCE('organizationmember', 'id');
|
||||
-- In my case i got "public.organizationmember_id_seq"
|
||||
SELECT setval('public.organizationmember_id_seq', COALESCE((SELECT MAX(id)+1 FROM organizationmember), 1), false);
|
||||
Reference in New Issue
Block a user