From ff524fca27346ecd28688d967a06b2ec79967de7 Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Thu, 29 Feb 2024 06:32:40 +0000 Subject: [PATCH] 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 --- apps/dashboard/package.json | 6 +- apps/dashboard/src/defer/sendEmail.ts | 46 ++++++++++++++++ apps/dashboard/src/defer/sendEmails.ts | 42 ++++++++++++++ .../components/NewsFeed/NewFeedModal.svelte | 7 +-- .../components/NewsFeed/NewsFeedCard.svelte | 20 +++++-- .../components/LMS/components/Profile.svelte | 3 +- .../src/lib/utils/services/newsfeed/index.ts | 6 +- .../lib/utils/services/notification/send.ts | 55 +++++-------------- apps/dashboard/src/lib/utils/types/feed.ts | 4 +- .../exercise_submission_update/+server.js | 6 +- .../api/email/course/newsfeed/+server.ts | 50 +++++++++-------- .../course/student_prove_payment/+server.js | 6 +- .../email/course/student_welcome/+server.js | 6 +- .../email/course/submission_update/+server.js | 6 +- .../teacher_student_buycourse/+server.js | 6 +- .../course/teacher_student_joined/+server.js | 6 +- .../email/course/teacher_welcome/+server.js | 6 +- .../src/routes/api/email/invite/+server.js | 6 +- .../src/routes/api/email/welcome/+server.js | 6 +- .../src/routes/courses/[id]/+page.svelte | 20 +++---- apps/dashboard/static/robots.txt | 17 ++++++ apps/dashboard/svelte.config.js | 7 ++- apps/dashboard/tsconfig.json | 8 ++- pnpm-lock.yaml | 14 +++++ 24 files changed, 239 insertions(+), 120 deletions(-) create mode 100644 apps/dashboard/src/defer/sendEmail.ts create mode 100644 apps/dashboard/src/defer/sendEmails.ts create mode 100644 apps/dashboard/static/robots.txt diff --git a/apps/dashboard/package.json b/apps/dashboard/package.json index 386287d6..665b7fb9 100644 --- a/apps/dashboard/package.json +++ b/apps/dashboard/package.json @@ -20,6 +20,7 @@ "dependencies": { "@aws-sdk/client-s3": "^3.386.0", "@carbon/charts-svelte": "^1.13.6", + "@defer/client": "^2.2.0", "@sentry/browser": "^7.61.0", "@sentry/integrations": "^7.61.0", "@sentry/vite-plugin": "^2.6.2", @@ -94,5 +95,8 @@ "typescript": "^5.1.6", "vite": "^4.4.8" }, - "type": "module" + "type": "module", + "engines": { + "node": "18.x" + } } diff --git a/apps/dashboard/src/defer/sendEmail.ts b/apps/dashboard/src/defer/sendEmail.ts new file mode 100644 index 00000000..4888ca92 --- /dev/null +++ b/apps/dashboard/src/defer/sendEmail.ts @@ -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" ', // 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); diff --git a/apps/dashboard/src/defer/sendEmails.ts b/apps/dashboard/src/defer/sendEmails.ts new file mode 100644 index 00000000..5c499977 --- /dev/null +++ b/apps/dashboard/src/defer/sendEmails.ts @@ -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" ', // 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); diff --git a/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewFeedModal.svelte b/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewFeedModal.svelte index 7918580e..f336d704 100644 --- a/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewFeedModal.svelte +++ b/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewFeedModal.svelte @@ -67,12 +67,7 @@ id: createdFeed.id, content: newPost, author: { - profile: { - id: author.id, - username: author.username, - fullname: author.fullname, - avatar_url: author.avatar - } + profile: { ...author } }, created_at: createdFeed.created_at, comment: [], diff --git a/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewsFeedCard.svelte b/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewsFeedCard.svelte index 4db80dbb..2cce72b4 100644 --- a/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewsFeedCard.svelte +++ b/apps/dashboard/src/lib/components/Course/components/NewsFeed/NewsFeedCard.svelte @@ -16,8 +16,8 @@ import { onMount } from 'svelte'; export let feed: Feed; - export let editFeed: Feed | any = {}; - export let author: Author | any = {}; + export let editFeed: Feed; + export let author: Author; export let edit = false; export let deleteFeed = (arg: string) => {}; export let deleteComment = (arg: string) => {}; @@ -70,6 +70,14 @@ 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(() => { if (isActive) { const el = document.getElementById(feed.id); @@ -94,13 +102,13 @@
users banner
-

{feed.author?.profile.fullname}

+

{feed.author?.profile?.fullname}

{calDateDiff(feed.created_at)}

@@ -134,7 +142,7 @@ on:click={() => handleAddNewReaction(reactionType)} class={`flex transition ${ 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` }`} >
{reactions[reactionType]}
@@ -197,7 +205,7 @@
users banner diff --git a/apps/dashboard/src/lib/components/LMS/components/Profile.svelte b/apps/dashboard/src/lib/components/LMS/components/Profile.svelte index 52e41c06..803517ee 100644 --- a/apps/dashboard/src/lib/components/LMS/components/Profile.svelte +++ b/apps/dashboard/src/lib/components/LMS/components/Profile.svelte @@ -18,7 +18,8 @@ const updates = { fullname: $profile.fullname, - username: $profile.username + username: $profile.username, + email: $profile.email }; if (avatar) { diff --git a/apps/dashboard/src/lib/utils/services/newsfeed/index.ts b/apps/dashboard/src/lib/utils/services/newsfeed/index.ts index f921a000..3ff927bf 100644 --- a/apps/dashboard/src/lib/utils/services/newsfeed/index.ts +++ b/apps/dashboard/src/lib/utils/services/newsfeed/index.ts @@ -1,6 +1,10 @@ import type { Course } from '$lib/utils/types'; 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']) { const response = await supabase diff --git a/apps/dashboard/src/lib/utils/services/notification/send.ts b/apps/dashboard/src/lib/utils/services/notification/send.ts index b038b4f4..69b59cf6 100644 --- a/apps/dashboard/src/lib/utils/services/notification/send.ts +++ b/apps/dashboard/src/lib/utils/services/notification/send.ts @@ -1,5 +1,4 @@ import nodemailer from 'nodemailer'; -import { withEmailTemplate } from './template'; import { SMTP_HOST, SMTP_USER, @@ -10,8 +9,19 @@ import { let transporter: nodemailer.Transporter; -const getTransporter = async (isPersonal?: boolean): Promise => { +export const getTransporter = async ( + isPersonal?: boolean +): Promise => { 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({ host: SMTP_HOST, port: 465, @@ -22,49 +32,14 @@ const getTransporter = async (isPersonal?: boolean): Promise => { } }); - transporter.verify(function (error, success) { + transporter.verify(function (error) { if (error) { console.log('Transporter error', error); - reject(error); + reject(null); } else { 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" ', // 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; - } -}; diff --git a/apps/dashboard/src/lib/utils/types/feed.ts b/apps/dashboard/src/lib/utils/types/feed.ts index 39274d2f..a10cfb43 100644 --- a/apps/dashboard/src/lib/utils/types/feed.ts +++ b/apps/dashboard/src/lib/utils/types/feed.ts @@ -29,7 +29,7 @@ export interface FeedApi { }; created_at: string; comment: Comment[]; - reaction: Reaction[]; + reaction: Reaction; is_pinned: false; } @@ -41,6 +41,6 @@ export interface Feed { }; created_at: string; comment: Comment[]; - reaction: Reaction[]; + reaction: Reaction; isPinned: boolean; } diff --git a/apps/dashboard/src/routes/api/email/course/exercise_submission_update/+server.js b/apps/dashboard/src/routes/api/email/course/exercise_submission_update/+server.js index 4b0ae942..2539fed1 100644 --- a/apps/dashboard/src/routes/api/email/course/exercise_submission_update/+server.js +++ b/apps/dashboard/src/routes/api/email/course/exercise_submission_update/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -24,12 +24,12 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - sendEmail({ + await sendEmail({ from: `"${orgName} (via ClassroomIO.com)" `, to, subject: `[Submitted]: ${exerciseTitle}`, content - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/course/newsfeed/+server.ts b/apps/dashboard/src/routes/api/email/course/newsfeed/+server.ts index 1eee64aa..ab3ec704 100644 --- a/apps/dashboard/src/routes/api/email/course/newsfeed/+server.ts +++ b/apps/dashboard/src/routes/api/email/course/newsfeed/+server.ts @@ -1,20 +1,20 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; import { getFeedForNotification } from '$lib/utils/services/newsfeed/index'; +import sendEmail from '$defer/sendEmail'; +import sendEmails from '$defer/sendEmails'; const supabase = getSupabase(); const sendEmailNotification = async (feedId: string, authorId: string, comment?: string) => { const feed = await getFeedForNotification(feedId, authorId); - if (!feed) return; const postLink = `https://${feed.org.siteName}.classroomio.com/courses/${feed.courseId}?feedId=${feed.id}`; if (comment) { // send only to teacher - sendEmail({ + await sendEmail({ from: `"${feed.org.name} - ClassroomIO" `, to: feed.teacherEmail, subject: `[${feed.courseTitle}] - News feed comment`, @@ -26,31 +26,35 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?: View comment
` - }).then((info) => console.log('Email sent:', info)); + }); // dont continue return; } + if (!feed.courseMembers.length) { + return; + } + // else send to everyone except the author of the post - Promise.all( - feed.courseMembers.map((member) => { - return sendEmail({ - from: `"${feed.org.name} - ClassroomIO" `, - to: member.email, - replyTo: feed.teacherEmail, - subject: `[${feed.courseTitle}] - New post in course`, - content: ` -

${feed.teacherName} made a post in a course you are taking: ${feed.courseTitle}.

- -
${feed.content}
-
- View post -
- ` - }); - }) - ).then((emails) => console.log(`Sent emails to ${emails.length} students`)); + const emails = feed.courseMembers.map((member) => ({ + from: `"${feed.org.name} - ClassroomIO" `, + to: member.email, + replyTo: feed.teacherEmail, + subject: `[${feed.courseTitle}] - New post in course`, + content: ` +

${feed.teacherName} made a post in a course you are taking: ${feed.courseTitle}.

+ +
${feed.content}
+
+ View post +
+ ` + })); + console.log('Sending emails to all students', feed.courseMembers.length); + + // This is the defer function with a loop + await sendEmails(emails); }; export async function POST({ request }) { @@ -74,7 +78,7 @@ export async function POST({ request }) { 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 // sendDiscordNotification(); sendTelegramNotification(); diff --git a/apps/dashboard/src/routes/api/email/course/student_prove_payment/+server.js b/apps/dashboard/src/routes/api/email/course/student_prove_payment/+server.js index 059bb0b1..79ae02ca 100644 --- a/apps/dashboard/src/routes/api/email/course/student_prove_payment/+server.js +++ b/apps/dashboard/src/routes/api/email/course/student_prove_payment/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -32,7 +32,7 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - sendEmail({ + await sendEmail({ from: `"${orgName} - ClassroomIO" `, to, replyTo: teacherEmail, @@ -44,7 +44,7 @@ export async function POST({ request }) {

Talk to you soon and see you in class.

${orgName}

` - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/course/student_welcome/+server.js b/apps/dashboard/src/routes/api/email/course/student_welcome/+server.js index 82fa9f01..46686be8 100644 --- a/apps/dashboard/src/routes/api/email/course/student_welcome/+server.js +++ b/apps/dashboard/src/routes/api/email/course/student_welcome/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -26,7 +26,7 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - sendEmail({ + await sendEmail({ from: `"${orgName} (via ClassroomIO.com)" `, to, subject: `${orgName} - Welcome to Class πŸŽ‰`, @@ -38,7 +38,7 @@ export async function POST({ request }) {

Cheers,

${orgName}

` - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/course/submission_update/+server.js b/apps/dashboard/src/routes/api/email/course/submission_update/+server.js index 914f2eb4..fa32bbef 100644 --- a/apps/dashboard/src/routes/api/email/course/submission_update/+server.js +++ b/apps/dashboard/src/routes/api/email/course/submission_update/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -24,12 +24,12 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - sendEmail({ + await sendEmail({ from: `"${orgName} (via ClassroomIO.com)" `, to, subject: 'Submission Update', content - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/course/teacher_student_buycourse/+server.js b/apps/dashboard/src/routes/api/email/course/teacher_student_buycourse/+server.js index c9c0ea09..59b8ed4e 100644 --- a/apps/dashboard/src/routes/api/email/course/teacher_student_buycourse/+server.js +++ b/apps/dashboard/src/routes/api/email/course/teacher_student_buycourse/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -31,7 +31,7 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - sendEmail({ + await sendEmail({ from: `ClassroomIO" `, to, subject: `[${courseName}] Request to Join Course!`, @@ -44,7 +44,7 @@ export async function POST({ request }) { Email: ${studentEmail}

` - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/course/teacher_student_joined/+server.js b/apps/dashboard/src/routes/api/email/course/teacher_student_joined/+server.js index 277a10e3..3ac2523f 100644 --- a/apps/dashboard/src/routes/api/email/course/teacher_student_joined/+server.js +++ b/apps/dashboard/src/routes/api/email/course/teacher_student_joined/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -25,7 +25,7 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - sendEmail({ + await sendEmail({ from: `"ClassroomIO" `, to, subject: `[${courseName}] You've got a new student πŸŽ‰!`, @@ -36,7 +36,7 @@ export async function POST({ request }) {

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.

` - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/course/teacher_welcome/+server.js b/apps/dashboard/src/routes/api/email/course/teacher_welcome/+server.js index 1de7b6a4..2380dc27 100644 --- a/apps/dashboard/src/routes/api/email/course/teacher_welcome/+server.js +++ b/apps/dashboard/src/routes/api/email/course/teacher_welcome/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -28,7 +28,7 @@ export async function POST({ request }) { const origin = request.headers.get('origin'); const inviteLink = `${origin}/org/${orgSiteName}/courses`; - sendEmail({ + await sendEmail({ from: `"${orgName} (via ClassroomIO.com)" `, to, subject: `You have been invited to a course in ${orgName}!`, @@ -40,7 +40,7 @@ export async function POST({ request }) { Open Dashboard
` - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/invite/+server.js b/apps/dashboard/src/routes/api/email/invite/+server.js index 6f73f436..38afeb35 100644 --- a/apps/dashboard/src/routes/api/email/invite/+server.js +++ b/apps/dashboard/src/routes/api/email/invite/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -42,7 +42,7 @@ export async function POST({ request }) { const inviteLink = `${origin}/invite/t/${encodeURIComponent(btoa(inviteData))}`; console.log('inviteData', inviteData); - sendEmail({ + await sendEmail({ to: email, subject: `Join ${name} on ClassroomIO πŸ˜ƒ`, content: ` @@ -53,7 +53,7 @@ export async function POST({ request }) { `, isPersonalEmail: true - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/api/email/welcome/+server.js b/apps/dashboard/src/routes/api/email/welcome/+server.js index 64b93ee2..160811cd 100644 --- a/apps/dashboard/src/routes/api/email/welcome/+server.js +++ b/apps/dashboard/src/routes/api/email/welcome/+server.js @@ -1,6 +1,6 @@ import { json } from '@sveltejs/kit'; import { getSupabase } from '$lib/utils/functions/supabase'; -import { sendEmail } from '$lib/utils/services/notification/send'; +import sendEmail from '$defer/sendEmail'; const supabase = getSupabase(); @@ -25,7 +25,7 @@ export async function POST({ request }) { return json({ success: false, message: 'Unauthenticated user' }, { status: 401 }); } - sendEmail({ + await sendEmail({ to, subject: 'Thank you so so so much for choosing ClassroomIO!', content: ` @@ -48,7 +48,7 @@ export async function POST({ request }) { `, isPersonalEmail: true - }).then((info) => console.log('Email sent:', info)); + }); return json({ success: true, diff --git a/apps/dashboard/src/routes/courses/[id]/+page.svelte b/apps/dashboard/src/routes/courses/[id]/+page.svelte index e1451bdb..be5344f8 100644 --- a/apps/dashboard/src/routes/courses/[id]/+page.svelte +++ b/apps/dashboard/src/routes/courses/[id]/+page.svelte @@ -17,7 +17,8 @@ deleteNewsFeed, fetchNewsFeeds, handleEditFeed, - toggleFeedIsPinned + toggleFeedIsPinned, + fetchNewsFeedReaction } from '$lib/utils/services/newsfeed'; import Box from '$lib/components/Box/index.svelte'; import { supabase } from '$lib/utils/functions/supabase'; @@ -37,7 +38,7 @@ id: '', username: '', fullname: '', - avatar: '' + avatar_url: '' }; let newsFeed: Feed[] = []; let query = new URLSearchParams($page.url.search); @@ -66,9 +67,11 @@ }; 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]; @@ -131,12 +134,7 @@ const newComment = { id: createdComment.id, author: { - profile: { - id: author.id, - username: author.username, - fullname: author.fullname, - avatar_url: author.avatar - } + profile: { ...author } }, created_at: createdComment.created_at, content: comment @@ -208,7 +206,7 @@ id: currentGroupMember?.id || '', username: $profile.username || '', fullname: $profile.fullname || '', - avatar: $profile.avatar_url || '' + avatar_url: $profile.avatar_url || '' }; } diff --git a/apps/dashboard/static/robots.txt b/apps/dashboard/static/robots.txt new file mode 100644 index 00000000..4e8dd7ec --- /dev/null +++ b/apps/dashboard/static/robots.txt @@ -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/ diff --git a/apps/dashboard/svelte.config.js b/apps/dashboard/svelte.config.js index f28ad2ed..c0b2189f 100644 --- a/apps/dashboard/svelte.config.js +++ b/apps/dashboard/svelte.config.js @@ -1,5 +1,6 @@ import { vitePreprocess } from '@sveltejs/kit/vite'; import vercelAdapter from '@sveltejs/adapter-vercel'; +import path from 'path'; /** @type {import('@sveltejs/kit').Config} */ const config = { @@ -8,7 +9,11 @@ const config = { preprocess: [vitePreprocess({})], kit: { - adapter: vercelAdapter() + adapter: vercelAdapter(), + alias: { + $lib: path.resolve('./src/lib'), + $defer: path.resolve('./src/defer') + } } }; diff --git a/apps/dashboard/tsconfig.json b/apps/dashboard/tsconfig.json index 5f225c45..d9ba990f 100644 --- a/apps/dashboard/tsconfig.json +++ b/apps/dashboard/tsconfig.json @@ -12,7 +12,13 @@ "target": "ESNext" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, "module": "ESNext", "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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 195f9588..66967b77 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -153,6 +153,9 @@ importers: '@carbon/charts-svelte': specifier: ^1.13.6 version: 1.13.6(d3-cloud@1.2.7)(d3-sankey@0.12.3)(d3@7.8.5)(svelte@4.1.2) + '@defer/client': + specifier: ^2.2.0 + version: 2.2.0 '@sentry/browser': specifier: ^7.61.0 version: 7.61.0 @@ -3157,6 +3160,13 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.9 + /@defer/client@2.2.0: + resolution: {integrity: sha512-WYHhPr8Tk5t1T5mai80l79lPEsTlt06adTVJc7GP6zpBW28Qy9YJvJlmUV+I0jpMuIRq/UuSOZRJpAGyjJMetg==} + engines: {node: '>=18'} + dependencies: + parse-duration: 1.1.0 + dev: false + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -12975,6 +12985,10 @@ packages: dependencies: callsites: 3.1.0 + /parse-duration@1.1.0: + resolution: {integrity: sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==} + dev: false + /parse-entities@4.0.1: resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} dependencies: