feat: move all email to defer queue #185
fix: use relative part for defer func #185 chore: move sendEmail into defer fix: remove anon function to defer #185 feat: implement batch email sending with defer.run #185 chore: debug sendEmails #185 chore: add robots.txt chore: use promise.all in sendEmails chore: debug defer sendEmails #185 chore: try defer sendEmail without loop #105 chore: comment code chore: change loop style #185 chore: use promise.all for vercel edge #185 feat: add await to sendEmailNotification #185 chore: use sendEmails #185 fix: debug sendEmails #185 fix: email not updating in LMS fix: remove early return in sendEmails #185
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-s3": "^3.386.0",
|
"@aws-sdk/client-s3": "^3.386.0",
|
||||||
"@carbon/charts-svelte": "^1.13.6",
|
"@carbon/charts-svelte": "^1.13.6",
|
||||||
|
"@defer/client": "^2.2.0",
|
||||||
"@sentry/browser": "^7.61.0",
|
"@sentry/browser": "^7.61.0",
|
||||||
"@sentry/integrations": "^7.61.0",
|
"@sentry/integrations": "^7.61.0",
|
||||||
"@sentry/vite-plugin": "^2.6.2",
|
"@sentry/vite-plugin": "^2.6.2",
|
||||||
@@ -94,5 +95,8 @@
|
|||||||
"typescript": "^5.1.6",
|
"typescript": "^5.1.6",
|
||||||
"vite": "^4.4.8"
|
"vite": "^4.4.8"
|
||||||
},
|
},
|
||||||
"type": "module"
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": "18.x"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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);
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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);
|
||||||
@@ -67,12 +67,7 @@
|
|||||||
id: createdFeed.id,
|
id: createdFeed.id,
|
||||||
content: newPost,
|
content: newPost,
|
||||||
author: {
|
author: {
|
||||||
profile: {
|
profile: { ...author }
|
||||||
id: author.id,
|
|
||||||
username: author.username,
|
|
||||||
fullname: author.fullname,
|
|
||||||
avatar_url: author.avatar
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
created_at: createdFeed.created_at,
|
created_at: createdFeed.created_at,
|
||||||
comment: [],
|
comment: [],
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
export let feed: Feed;
|
export let feed: Feed;
|
||||||
export let editFeed: Feed | any = {};
|
export let editFeed: Feed;
|
||||||
export let author: Author | any = {};
|
export let author: Author;
|
||||||
export let edit = false;
|
export let edit = false;
|
||||||
export let deleteFeed = (arg: string) => {};
|
export let deleteFeed = (arg: string) => {};
|
||||||
export let deleteComment = (arg: string) => {};
|
export let deleteComment = (arg: string) => {};
|
||||||
@@ -70,6 +70,14 @@
|
|||||||
deleteComment(id);
|
deleteComment(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getClassIfSelectedByAuthor = (reactionType) => {
|
||||||
|
const usersReacted = feed.reaction[reactionType] || [];
|
||||||
|
|
||||||
|
return usersReacted.includes(author.id)
|
||||||
|
? 'bg-primary-200 border-primary-600 pl-2'
|
||||||
|
: 'bg-gray-200 border-gray-600 pl-2';
|
||||||
|
};
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
const el = document.getElementById(feed.id);
|
const el = document.getElementById(feed.id);
|
||||||
@@ -94,13 +102,13 @@
|
|||||||
<span class="flex items-center gap-3">
|
<span class="flex items-center gap-3">
|
||||||
<div class="w-9 h-9">
|
<div class="w-9 h-9">
|
||||||
<img
|
<img
|
||||||
src={feed.author?.profile.avatar_url}
|
src={feed.author?.profile?.avatar_url}
|
||||||
alt="users banner"
|
alt="users banner"
|
||||||
class="w-full h-full rounded-full object-cover"
|
class="w-full h-full rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span>
|
<span>
|
||||||
<p class="text-base font-semibold capitalize">{feed.author?.profile.fullname}</p>
|
<p class="text-base font-semibold capitalize">{feed.author?.profile?.fullname}</p>
|
||||||
<p class="text-sm font-medium text-gray-600">{calDateDiff(feed.created_at)}</p>
|
<p class="text-sm font-medium text-gray-600">{calDateDiff(feed.created_at)}</p>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -134,7 +142,7 @@
|
|||||||
on:click={() => handleAddNewReaction(reactionType)}
|
on:click={() => handleAddNewReaction(reactionType)}
|
||||||
class={`flex transition ${
|
class={`flex transition ${
|
||||||
feed.reaction[reactionType].length >= 1 &&
|
feed.reaction[reactionType].length >= 1 &&
|
||||||
'bg-primary-200 dark:bg-gray-900 dark:text-black px-1 border border-primary-600 rounded-full'
|
`${getClassIfSelectedByAuthor(reactionType)} dark:text-black border rounded-full`
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div class="text-[15px]">{reactions[reactionType]}</div>
|
<div class="text-[15px]">{reactions[reactionType]}</div>
|
||||||
@@ -197,7 +205,7 @@
|
|||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<div class="w-7 h-7">
|
<div class="w-7 h-7">
|
||||||
<img
|
<img
|
||||||
src={author.avatar}
|
src={author.avatar_url}
|
||||||
alt="users banner"
|
alt="users banner"
|
||||||
class="w-full h-full rounded-full object-cover"
|
class="w-full h-full rounded-full object-cover"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,7 +18,8 @@
|
|||||||
|
|
||||||
const updates = {
|
const updates = {
|
||||||
fullname: $profile.fullname,
|
fullname: $profile.fullname,
|
||||||
username: $profile.username
|
username: $profile.username,
|
||||||
|
email: $profile.email
|
||||||
};
|
};
|
||||||
|
|
||||||
if (avatar) {
|
if (avatar) {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import type { Course } from '$lib/utils/types';
|
import type { Course } from '$lib/utils/types';
|
||||||
import { supabase } from '$lib/utils/functions/supabase';
|
import { supabase } from '$lib/utils/functions/supabase';
|
||||||
import type { Reaction, FeedApi } from '$lib/utils/types/feed';
|
import type { Reaction, FeedApi, Feed } from '$lib/utils/types/feed';
|
||||||
|
|
||||||
|
export async function fetchNewsFeedReaction(feedId: Feed['id']) {
|
||||||
|
return supabase.from('course_newsfeed').select(`reaction`).eq('id', feedId).single();
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchNewsFeeds(courseId?: Course['id']) {
|
export async function fetchNewsFeeds(courseId?: Course['id']) {
|
||||||
const response = await supabase
|
const response = await supabase
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import nodemailer from 'nodemailer';
|
import nodemailer from 'nodemailer';
|
||||||
import { withEmailTemplate } from './template';
|
|
||||||
import {
|
import {
|
||||||
SMTP_HOST,
|
SMTP_HOST,
|
||||||
SMTP_USER,
|
SMTP_USER,
|
||||||
@@ -10,8 +9,19 @@ import {
|
|||||||
|
|
||||||
let transporter: nodemailer.Transporter;
|
let transporter: nodemailer.Transporter;
|
||||||
|
|
||||||
const getTransporter = async (isPersonal?: boolean): Promise<boolean> => {
|
export const getTransporter = async (
|
||||||
|
isPersonal?: boolean
|
||||||
|
): Promise<nodemailer.Transporter | null> => {
|
||||||
return await new Promise((resolve, reject) => {
|
return await new Promise((resolve, reject) => {
|
||||||
|
// If email sending isn't configured
|
||||||
|
if (!SMTP_HOST) {
|
||||||
|
resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transporter) {
|
||||||
|
return resolve(transporter);
|
||||||
|
}
|
||||||
|
|
||||||
transporter = nodemailer.createTransport({
|
transporter = nodemailer.createTransport({
|
||||||
host: SMTP_HOST,
|
host: SMTP_HOST,
|
||||||
port: 465,
|
port: 465,
|
||||||
@@ -22,49 +32,14 @@ const getTransporter = async (isPersonal?: boolean): Promise<boolean> => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
transporter.verify(function (error, success) {
|
transporter.verify(function (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log('Transporter error', error);
|
console.log('Transporter error', error);
|
||||||
reject(error);
|
reject(null);
|
||||||
} else {
|
} else {
|
||||||
console.log('Server is ready to take our messages');
|
console.log('Server is ready to take our messages');
|
||||||
resolve(success);
|
resolve(transporter);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendEmail = async ({
|
|
||||||
from,
|
|
||||||
to,
|
|
||||||
subject,
|
|
||||||
content,
|
|
||||||
isPersonalEmail,
|
|
||||||
replyTo
|
|
||||||
}: {
|
|
||||||
from?: string;
|
|
||||||
to: string;
|
|
||||||
subject: string;
|
|
||||||
content: string;
|
|
||||||
isPersonalEmail?: boolean;
|
|
||||||
replyTo?: string;
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
await getTransporter(isPersonalEmail);
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export interface FeedApi {
|
|||||||
};
|
};
|
||||||
created_at: string;
|
created_at: string;
|
||||||
comment: Comment[];
|
comment: Comment[];
|
||||||
reaction: Reaction[];
|
reaction: Reaction;
|
||||||
is_pinned: false;
|
is_pinned: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +41,6 @@ export interface Feed {
|
|||||||
};
|
};
|
||||||
created_at: string;
|
created_at: string;
|
||||||
comment: Comment[];
|
comment: Comment[];
|
||||||
reaction: Reaction[];
|
reaction: Reaction;
|
||||||
isPinned: boolean;
|
isPinned: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
||||||
to,
|
to,
|
||||||
subject: `[Submitted]: ${exerciseTitle}`,
|
subject: `[Submitted]: ${exerciseTitle}`,
|
||||||
content
|
content
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
|
||||||
import { getFeedForNotification } from '$lib/utils/services/newsfeed/index';
|
import { getFeedForNotification } from '$lib/utils/services/newsfeed/index';
|
||||||
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
import sendEmails from '$defer/sendEmails';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
const sendEmailNotification = async (feedId: string, authorId: string, comment?: string) => {
|
const sendEmailNotification = async (feedId: string, authorId: string, comment?: string) => {
|
||||||
const feed = await getFeedForNotification(feedId, authorId);
|
const feed = await getFeedForNotification(feedId, authorId);
|
||||||
|
|
||||||
if (!feed) return;
|
if (!feed) return;
|
||||||
|
|
||||||
const postLink = `https://${feed.org.siteName}.classroomio.com/courses/${feed.courseId}?feedId=${feed.id}`;
|
const postLink = `https://${feed.org.siteName}.classroomio.com/courses/${feed.courseId}?feedId=${feed.id}`;
|
||||||
|
|
||||||
if (comment) {
|
if (comment) {
|
||||||
// send only to teacher
|
// send only to teacher
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
from: `"${feed.org.name} - ClassroomIO" <[email protected]>`,
|
from: `"${feed.org.name} - ClassroomIO" <[email protected]>`,
|
||||||
to: feed.teacherEmail,
|
to: feed.teacherEmail,
|
||||||
subject: `[${feed.courseTitle}] - News feed comment`,
|
subject: `[${feed.courseTitle}] - News feed comment`,
|
||||||
@@ -26,31 +26,35 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
|
|||||||
<a class="button" href="${postLink}">View comment</a>
|
<a class="button" href="${postLink}">View comment</a>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
// dont continue
|
// dont continue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!feed.courseMembers.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// else send to everyone except the author of the post
|
// else send to everyone except the author of the post
|
||||||
Promise.all(
|
const emails = feed.courseMembers.map((member) => ({
|
||||||
feed.courseMembers.map((member) => {
|
from: `"${feed.org.name} - ClassroomIO" <[email protected]>`,
|
||||||
return sendEmail({
|
to: member.email,
|
||||||
from: `"${feed.org.name} - ClassroomIO" <[email protected]>`,
|
replyTo: feed.teacherEmail,
|
||||||
to: member.email,
|
subject: `[${feed.courseTitle}] - New post in course`,
|
||||||
replyTo: feed.teacherEmail,
|
content: `
|
||||||
subject: `[${feed.courseTitle}] - New post in course`,
|
<p>${feed.teacherName} made a post in a course you are taking: ${feed.courseTitle}.</p>
|
||||||
content: `
|
|
||||||
<p>${feed.teacherName} made a post in a course you are taking: ${feed.courseTitle}.</p>
|
<div style="font-style: italic; margin-top: 10px;">${feed.content}</div>
|
||||||
|
<div>
|
||||||
<div style="font-style: italic; margin-top: 10px;">${feed.content}</div>
|
<a class="button" href="${postLink}">View post</a>
|
||||||
<div>
|
</div>
|
||||||
<a class="button" href="${postLink}">View post</a>
|
`
|
||||||
</div>
|
}));
|
||||||
`
|
console.log('Sending emails to all students', feed.courseMembers.length);
|
||||||
});
|
|
||||||
})
|
// This is the defer function with a loop
|
||||||
).then((emails) => console.log(`Sent emails to ${emails.length} students`));
|
await sendEmails(emails);
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
@@ -74,7 +78,7 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmailNotification(feedId, authorId, comment);
|
await sendEmailNotification(feedId, authorId, comment);
|
||||||
// TODO: Support sending to other platforms like telegram and discord
|
// TODO: Support sending to other platforms like telegram and discord
|
||||||
// sendDiscordNotification(); sendTelegramNotification();
|
// sendDiscordNotification(); sendTelegramNotification();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
from: `"${orgName} - ClassroomIO" <[email protected]>`,
|
from: `"${orgName} - ClassroomIO" <[email protected]>`,
|
||||||
to,
|
to,
|
||||||
replyTo: teacherEmail,
|
replyTo: teacherEmail,
|
||||||
@@ -44,7 +44,7 @@ export async function POST({ request }) {
|
|||||||
<p>Talk to you soon and see you in class.</p>
|
<p>Talk to you soon and see you in class.</p>
|
||||||
<p>${orgName}</p>
|
<p>${orgName}</p>
|
||||||
`
|
`
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
||||||
to,
|
to,
|
||||||
subject: `${orgName} - Welcome to Class 🎉`,
|
subject: `${orgName} - Welcome to Class 🎉`,
|
||||||
@@ -38,7 +38,7 @@ export async function POST({ request }) {
|
|||||||
<p>Cheers,</p>
|
<p>Cheers,</p>
|
||||||
<p>${orgName}</p>
|
<p>${orgName}</p>
|
||||||
`
|
`
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
||||||
to,
|
to,
|
||||||
subject: 'Submission Update',
|
subject: 'Submission Update',
|
||||||
content
|
content
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
from: `ClassroomIO" <[email protected]>`,
|
from: `ClassroomIO" <[email protected]>`,
|
||||||
to,
|
to,
|
||||||
subject: `[${courseName}] Request to Join Course!`,
|
subject: `[${courseName}] Request to Join Course!`,
|
||||||
@@ -44,7 +44,7 @@ export async function POST({ request }) {
|
|||||||
Email: ${studentEmail}
|
Email: ${studentEmail}
|
||||||
</p>
|
</p>
|
||||||
`
|
`
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
from: `"ClassroomIO" <[email protected]>`,
|
from: `"ClassroomIO" <[email protected]>`,
|
||||||
to,
|
to,
|
||||||
subject: `[${courseName}] You've got a new student 🎉!`,
|
subject: `[${courseName}] You've got a new student 🎉!`,
|
||||||
@@ -36,7 +36,7 @@ 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
|
<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>
|
experience as easy as possible.</p>
|
||||||
`
|
`
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export async function POST({ request }) {
|
|||||||
const origin = request.headers.get('origin');
|
const origin = request.headers.get('origin');
|
||||||
const inviteLink = `${origin}/org/${orgSiteName}/courses`;
|
const inviteLink = `${origin}/org/${orgSiteName}/courses`;
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
from: `"${orgName} (via ClassroomIO.com)" <[email protected]>`,
|
||||||
to,
|
to,
|
||||||
subject: `You have been invited to a course in ${orgName}!`,
|
subject: `You have been invited to a course in ${orgName}!`,
|
||||||
@@ -40,7 +40,7 @@ export async function POST({ request }) {
|
|||||||
<a class="button" href="${inviteLink}">Open Dashboard</a>
|
<a class="button" href="${inviteLink}">Open Dashboard</a>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export async function POST({ request }) {
|
|||||||
const inviteLink = `${origin}/invite/t/${encodeURIComponent(btoa(inviteData))}`;
|
const inviteLink = `${origin}/invite/t/${encodeURIComponent(btoa(inviteData))}`;
|
||||||
console.log('inviteData', inviteData);
|
console.log('inviteData', inviteData);
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
to: email,
|
to: email,
|
||||||
subject: `Join ${name} on ClassroomIO 😃`,
|
subject: `Join ${name} on ClassroomIO 😃`,
|
||||||
content: `
|
content: `
|
||||||
@@ -53,7 +53,7 @@ export async function POST({ request }) {
|
|||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
isPersonalEmail: true
|
isPersonalEmail: true
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { json } from '@sveltejs/kit';
|
import { json } from '@sveltejs/kit';
|
||||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||||
import { sendEmail } from '$lib/utils/services/notification/send';
|
import sendEmail from '$defer/sendEmail';
|
||||||
|
|
||||||
const supabase = getSupabase();
|
const supabase = getSupabase();
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export async function POST({ request }) {
|
|||||||
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
return json({ success: false, message: 'Unauthenticated user' }, { status: 401 });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEmail({
|
await sendEmail({
|
||||||
to,
|
to,
|
||||||
subject: 'Thank you so so so much for choosing ClassroomIO!',
|
subject: 'Thank you so so so much for choosing ClassroomIO!',
|
||||||
content: `
|
content: `
|
||||||
@@ -48,7 +48,7 @@ export async function POST({ request }) {
|
|||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
isPersonalEmail: true
|
isPersonalEmail: true
|
||||||
}).then((info) => console.log('Email sent:', info));
|
});
|
||||||
|
|
||||||
return json({
|
return json({
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
deleteNewsFeed,
|
deleteNewsFeed,
|
||||||
fetchNewsFeeds,
|
fetchNewsFeeds,
|
||||||
handleEditFeed,
|
handleEditFeed,
|
||||||
toggleFeedIsPinned
|
toggleFeedIsPinned,
|
||||||
|
fetchNewsFeedReaction
|
||||||
} from '$lib/utils/services/newsfeed';
|
} from '$lib/utils/services/newsfeed';
|
||||||
import Box from '$lib/components/Box/index.svelte';
|
import Box from '$lib/components/Box/index.svelte';
|
||||||
import { supabase } from '$lib/utils/functions/supabase';
|
import { supabase } from '$lib/utils/functions/supabase';
|
||||||
@@ -37,7 +38,7 @@
|
|||||||
id: '',
|
id: '',
|
||||||
username: '',
|
username: '',
|
||||||
fullname: '',
|
fullname: '',
|
||||||
avatar: ''
|
avatar_url: ''
|
||||||
};
|
};
|
||||||
let newsFeed: Feed[] = [];
|
let newsFeed: Feed[] = [];
|
||||||
let query = new URLSearchParams($page.url.search);
|
let query = new URLSearchParams($page.url.search);
|
||||||
@@ -66,9 +67,11 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addNewReaction = async (reactionType: string, feedId: string, authorId: string) => {
|
const addNewReaction = async (reactionType: string, feedId: string, authorId: string) => {
|
||||||
const reactedFeed = newsFeed.find((feed) => feed.id === feedId);
|
const { data } = await fetchNewsFeedReaction(feedId);
|
||||||
|
|
||||||
if (!reactedFeed) return;
|
if (!data) return;
|
||||||
|
|
||||||
|
const reactedFeed = data || newsFeed.find((feed) => feed.id === feedId);
|
||||||
|
|
||||||
let reactedAuthorIds: string[] = reactedFeed.reaction[reactionType];
|
let reactedAuthorIds: string[] = reactedFeed.reaction[reactionType];
|
||||||
|
|
||||||
@@ -131,12 +134,7 @@
|
|||||||
const newComment = {
|
const newComment = {
|
||||||
id: createdComment.id,
|
id: createdComment.id,
|
||||||
author: {
|
author: {
|
||||||
profile: {
|
profile: { ...author }
|
||||||
id: author.id,
|
|
||||||
username: author.username,
|
|
||||||
fullname: author.fullname,
|
|
||||||
avatar_url: author.avatar
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
created_at: createdComment.created_at,
|
created_at: createdComment.created_at,
|
||||||
content: comment
|
content: comment
|
||||||
@@ -208,7 +206,7 @@
|
|||||||
id: currentGroupMember?.id || '',
|
id: currentGroupMember?.id || '',
|
||||||
username: $profile.username || '',
|
username: $profile.username || '',
|
||||||
fullname: $profile.fullname || '',
|
fullname: $profile.fullname || '',
|
||||||
avatar: $profile.avatar_url || ''
|
avatar_url: $profile.avatar_url || ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
User-agent: Googlebot
|
||||||
|
Disallow:
|
||||||
|
User-agent: googlebot-image
|
||||||
|
Disallow:
|
||||||
|
User-agent: googlebot-mobile
|
||||||
|
Disallow:
|
||||||
|
User-agent: MSNBot
|
||||||
|
Disallow:
|
||||||
|
User-agent: Slurp
|
||||||
|
Disallow:
|
||||||
|
User-agent: yahoo-mmcrawler
|
||||||
|
Disallow:
|
||||||
|
User-agent: yahoo-blogs/v3.9
|
||||||
|
Disallow:
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
|
Disallow: /cgi-bin/
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||||
import vercelAdapter from '@sveltejs/adapter-vercel';
|
import vercelAdapter from '@sveltejs/adapter-vercel';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
const config = {
|
const config = {
|
||||||
@@ -8,7 +9,11 @@ const config = {
|
|||||||
preprocess: [vitePreprocess({})],
|
preprocess: [vitePreprocess({})],
|
||||||
|
|
||||||
kit: {
|
kit: {
|
||||||
adapter: vercelAdapter()
|
adapter: vercelAdapter(),
|
||||||
|
alias: {
|
||||||
|
$lib: path.resolve('./src/lib'),
|
||||||
|
$defer: path.resolve('./src/defer')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,13 @@
|
|||||||
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
|
"target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"noImplicitAny": false
|
"noImplicitAny": false,
|
||||||
|
"paths": {
|
||||||
|
"$lib": ["./src/lib"],
|
||||||
|
"$lib/*": ["./src/lib/*"],
|
||||||
|
"$defer": ["./src/defer"],
|
||||||
|
"$defer/*": ["./src/defer/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
// Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
|
||||||
|
|||||||
Generated
+14
@@ -153,6 +153,9 @@ importers:
|
|||||||
'@carbon/charts-svelte':
|
'@carbon/charts-svelte':
|
||||||
specifier: ^1.13.6
|
specifier: ^1.13.6
|
||||||
version: 1.13.6([email protected])([email protected])([email protected])([email protected])
|
version: 1.13.6([email protected])([email protected])([email protected])([email protected])
|
||||||
|
'@defer/client':
|
||||||
|
specifier: ^2.2.0
|
||||||
|
version: 2.2.0
|
||||||
'@sentry/browser':
|
'@sentry/browser':
|
||||||
specifier: ^7.61.0
|
specifier: ^7.61.0
|
||||||
version: 7.61.0
|
version: 7.61.0
|
||||||
@@ -3157,6 +3160,13 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/trace-mapping': 0.3.9
|
'@jridgewell/trace-mapping': 0.3.9
|
||||||
|
|
||||||
|
/@defer/[email protected]:
|
||||||
|
resolution: {integrity: sha512-WYHhPr8Tk5t1T5mai80l79lPEsTlt06adTVJc7GP6zpBW28Qy9YJvJlmUV+I0jpMuIRq/UuSOZRJpAGyjJMetg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
dependencies:
|
||||||
|
parse-duration: 1.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@esbuild/[email protected]:
|
/@esbuild/[email protected]:
|
||||||
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
|
resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -12975,6 +12985,10 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
callsites: 3.1.0
|
callsites: 3.1.0
|
||||||
|
|
||||||
|
/[email protected]:
|
||||||
|
resolution: {integrity: sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/[email protected]:
|
/[email protected]:
|
||||||
resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==}
|
resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user