chore: move backend to hono + typescript (#558)

* feat: setup secure presigned uploads and downloads

* clean up

* update lock

* Get presigned url finally working

* 557: Migrate backend to hono + ts

* chore: update Dockerfile and package.json for backend configuration

* add dev command

* remove unused packages

* Enhance backend API and frontend components for course management

- Updated CORS configuration in the backend to allow specific methods and headers.
- Refactored lesson download endpoint to use a new route for PDF downloads.
- Removed unused certificate download functionality from the lesson router.
- Updated frontend components to reflect changes in API endpoints and improve video upload functionality.
- Introduced new video upload component and adjusted video handling in lesson materials.
- Cleaned up unnecessary imports and optimized component structure for better performance.

* PR cleanups

* Enhance email configuration by adding SMTP_SENDER environment variable

- Updated docker-compose.yaml to include SMTP_SENDER.
- Modified Dockerfile to accept SMTP_SENDER as an argument.
- Updated environment schema in env.ts to define SMTP_SENDER as optional.
- Adjusted mail service to use SMTP_SENDER for the email 'from' field.
- Updated documentation to reflect the new SMTP_SENDER variable.

* Remove debug log statement from sendEmail function

---------

Co-authored-by: Iniubong Obonguko <[email protected]>
This commit is contained in:
Rotimi Best
2025-06-09 01:38:57 +01:00
committed by GitHub
co-authored by Iniubong Obonguko
parent 9eb7b01587
commit 82be557a42
80 changed files with 3291 additions and 2302 deletions
@@ -1,5 +1,6 @@
import { Configuration, OpenAIApi } from 'openai-edge';
import { OpenAIStream, StreamingTextResponse } from 'ai';
import { env } from '$env/dynamic/private';
// Create an OpenAI API client (that's edge friendly!)
@@ -1,7 +1,7 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, content, orgName, exerciseTitle } = await request.json();
if (!to || !content || !orgName) {
@@ -17,7 +17,7 @@ export async function POST({ request }) {
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,11 +1,16 @@
import { getServerSupabase } from '$lib/utils/functions/supabase.server';
import { getFeedForNotification } from '$lib/utils/services/newsfeed/index';
import sendEmail from '$mail/sendEmail';
import { getServerSupabase } from '$lib/utils/functions/supabase.server';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
const supabase = getServerSupabase();
const sendEmailNotification = async (feedId: string, authorId: string, comment?: string) => {
const sendEmailNotification = async (
sFetch: typeof fetch,
feedId: string,
authorId: string,
comment?: string
) => {
const feed = await getFeedForNotification({
supabase,
feedId,
@@ -34,7 +39,7 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
}
];
await sendEmail(emailData);
await sendEmail(sFetch)(emailData);
// dont continue
return;
@@ -62,10 +67,10 @@ const sendEmailNotification = async (feedId: string, authorId: string, comment?:
console.log('Sending emails to all students', feed.courseMembers.length);
// This is the email sending function with a loop
await sendEmail(emailsData);
await sendEmail(sFetch)(emailsData);
};
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { authorId, feedId, comment } = await request.json();
console.log('/POST api/email/course/newsfeed');
@@ -73,7 +78,7 @@ export async function POST({ request }) {
return json({ success: false, message: 'Missing required fields' }, { status: 400 });
}
await sendEmailNotification(feedId, authorId, comment);
await sendEmailNotification(fetch, feedId, authorId, comment);
// TODO: Support sending to other platforms like telegram and discord
// sendDiscordNotification(); sendTelegramNotification();
@@ -4,7 +4,7 @@ import sendEmail from '$mail/sendEmail';
// const supabase = getSupabase();
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, courseName, teacherEmail, studentFullname, orgName } = await request.json();
// const accessToken = request.headers.get('Authorization') || '';
console.log(
@@ -48,7 +48,7 @@ export async function POST({ request }) {
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,8 +1,8 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
// Sends email to student when they successfully join a course
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, orgName, courseName } = await request.json();
console.log('/POST api/email/course/student_welcome', to, orgName);
@@ -25,7 +25,7 @@ export async function POST({ request }) {
`
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,7 +1,7 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, content, orgName } = await request.json();
if (!to || !content || !orgName) {
@@ -17,7 +17,7 @@ export async function POST({ request }) {
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,7 +1,7 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, courseName, studentEmail, studentFullname } = await request.json();
console.log(
'/POST api/email/course/teacher_student_buycourse',
@@ -31,7 +31,7 @@ export async function POST({ request }) {
`
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,7 +1,7 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, courseName, studentName, studentEmail } = await request.json();
console.log('/POST api/email/course/teacher_student_joined', to, courseName);
@@ -23,7 +23,7 @@ export async function POST({ request }) {
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,7 +1,7 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, name, orgName, courseName, orgSiteName } = await request.json();
console.log('/POST api/email/course/teacher_welcome', to, name, orgName);
@@ -28,7 +28,7 @@ export async function POST({ request }) {
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,8 +1,8 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
// API to send invite to teacher
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const body = await request.json();
const { org, email } = body;
@@ -40,7 +40,7 @@ export async function POST({ request }) {
`
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,7 +1,7 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, profileId, orgSiteName, fullname } = await request.json();
console.log('/POST api/email/verify_email', to, profileId);
@@ -36,7 +36,7 @@ export async function POST({ request }) {
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,7 +1,7 @@
import sendEmail from '$mail/sendEmail';
import { json } from '@sveltejs/kit';
import sendEmail from '$mail/sendEmail';
export async function POST({ request }) {
export async function POST({ fetch, request }) {
const { to, name } = await request.json();
console.log('/POST api/email/welcome', to, name);
@@ -33,7 +33,7 @@ export async function POST({ request }) {
}
];
await sendEmail(emailData);
await sendEmail(fetch)(emailData);
return json({
success: true,
@@ -1,7 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { env } from '$env/dynamic/public';
import CourseContainer from '$lib/components/CourseContainer/index.svelte';
import {
checkExercisesComplete,
@@ -30,8 +29,6 @@
import IconButton from '$lib/components/IconButton/index.svelte';
import CourseIcon from '$lib/components/Icons/CourseIcon.svelte';
import { PageBody, PageNav } from '$lib/components/Page';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import RoleBasedSecurity from '$lib/components/RoleBasedSecurity/index.svelte';
import { snackbar } from '$lib/components/Snackbar/store';
import MODES from '$lib/utils/constants/mode';
@@ -42,9 +39,9 @@
import { COURSE_VERSION, type Lesson, type LessonCompletion } from '$lib/utils/types';
import { Dropdown } from 'carbon-components-svelte';
import { ChevronLeft, ChevronRight, Edit, Save } from 'carbon-icons-svelte';
import Download from 'carbon-icons-svelte/lib/Download.svelte';
import OverflowMenuVertical from 'carbon-icons-svelte/lib/OverflowMenuVertical.svelte';
import ResultOld from 'carbon-icons-svelte/lib/ResultOld.svelte';
import { apiClient } from '$lib/utils/services/api';
export let data;
@@ -163,13 +160,9 @@
const lessonNumber = getLessonOrder(currentLesson.id);
const slideUrl = $lesson.materials.slide_url || '';
const response = await fetch(env.PUBLIC_SERVER_URL + '/downloadLesson', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
const response = await apiClient.post(
'/course/lesson/download/pdf',
{
title: currentLesson.title,
number: lessonNumber,
orgName: $currentOrg.name,
@@ -177,16 +170,13 @@
slideUrl: slideUrl,
video: lessonVideo,
courseTitle: $course.title
})
});
},
{
responseType: 'blob'
}
);
if (!response.ok) {
throw new Error(await response.text());
}
const data = await response.blob();
console.log(data);
const file = new Blob([data], { type: 'application/pdf' });
const file = new Blob([response.data], { type: 'application/pdf' });
const fileURL = URL.createObjectURL(file);
let a = document.createElement('a');
@@ -386,17 +376,6 @@
<Edit size={24} />
{/if}
</IconButton>
{#if $course.metadata.lessonDownload && !!env.PUBLIC_SERVER_URL}
<PrimaryButton
className="mr-"
variant={VARIANTS.OUTLINED}
onClick={downloadLesson}
{isLoading}
>
<Download size={16} />
</PrimaryButton>
{/if}
</div>
<Dropdown items={LANGUAGES} bind:selectedId={$lesson.locale} class="h-full" />