feat: implement DASHBOARD_ORIGIN for invite and email links, enhancing self-hosted configuration and updating related documentation

This commit is contained in:
Rotimi Best
2026-03-11 17:00:44 +01:00
parent c5c65b96d7
commit fd11bdd4b0
14 changed files with 41 additions and 33 deletions
+3 -11
View File
@@ -19,7 +19,7 @@ import { ROLE } from '@cio/utils/constants';
import type { TCreateCourseInvite } from '@cio/utils/validation/course/invite';
import crypto from 'node:crypto';
import { db } from '@cio/db/drizzle';
import { env } from '@api/config/env';
import { getDashboardBaseUrl } from '@api/config/dashboard-url';
import { getCourseTeachers } from '@cio/db/queries/course/people';
import { getProfileById } from '@cio/db/queries/auth';
import { buildEmailFromName, sendEmail } from '@cio/email';
@@ -150,20 +150,12 @@ function getInviteStatus(invite: {
return 'ACTIVE';
}
function getAppBaseUrl(orgSiteName?: string): string {
if (env.NODE_ENV === 'development') {
return 'http://localhost:5173';
}
const subdomain = orgSiteName && orgSiteName !== 'app' ? orgSiteName : 'app';
return `https://${subdomain}.classroomio.com`;
}
/**
* Builds the course enroll URL for invite links (emails, etc.).
* Uses org subdomain in production.
* Uses DASHBOARD_URL when set (self-hosted), otherwise org subdomain in production.
*/
export function buildEnrollLink(courseSlug: string, token: string, orgSiteName?: string): string {
const base = getAppBaseUrl(orgSiteName);
const base = getDashboardBaseUrl(orgSiteName);
return `${base}/course/${encodeURIComponent(courseSlug)}/enroll?invite_token=${encodeURIComponent(token)}`;
}
+2 -2
View File
@@ -11,7 +11,7 @@ import {
import { ROLE } from '@cio/utils/constants';
import type { TAddCourseMembers } from '@cio/utils/validation/course/people';
import type { TGroupmember } from '@cio/db/types';
import { env } from '@api/config/env';
import { getDashboardBaseUrl } from '@api/config/dashboard-url';
import { getCourseWithOrgData } from '@cio/db/queries/course';
import { getProfileById } from '@cio/db/queries/auth';
import { buildEmailFromName, sendEmail } from '@cio/email';
@@ -176,7 +176,7 @@ export async function addMembers(courseId: string, members: TAddCourseMembers) {
const addedMembers = await Promise.all(members.map((member) => addCourseMember(courseId, member)));
// Send welcome emails for teachers (TUTOR role = 2, ADMIN role = 1)
const baseUrl = env.NODE_ENV === 'development' ? 'http://localhost:5173' : 'https://app.classroomio.com';
const baseUrl = getDashboardBaseUrl(orgSiteName);
const inviteLink = `${baseUrl}/org/${orgSiteName}/courses`;
// Send welcome emails to teachers
+2 -6
View File
@@ -10,7 +10,7 @@ import {
revokeActiveOrganizationInvitesByEmails
} from '@cio/db/queries/organization';
import { db } from '@cio/db/drizzle';
import { env } from '@api/config/env';
import { getDashboardBaseUrl } from '@api/config/dashboard-url';
import { sendEmail } from '@cio/email';
import { ROLE } from '@cio/utils/constants';
import * as schema from '@cio/db/schema';
@@ -42,12 +42,8 @@ function normalizeEmails(emails: string[]): string[] {
return [...new Set(emails.map((email) => email.toLowerCase().trim()).filter(Boolean))];
}
function getAppBaseUrl(): string {
return env.NODE_ENV === 'development' ? 'http://localhost:5173' : 'https://app.classroomio.com';
}
function buildInviteLink(token: string): string {
return `${getAppBaseUrl()}/invite/t/${encodeURIComponent(token)}`;
return `${getDashboardBaseUrl()}/invite/t/${encodeURIComponent(token)}`;
}
function getRoleLabel(roleId: number): string {
@@ -32,7 +32,7 @@ import { getGroupMemberIdByCourseAndProfile, isCourseTeamMemberOrOrgAdmin } from
import { QUESTION_TYPE_ID_TO_KEY } from '@cio/question-types';
import { db } from '@cio/db/drizzle';
import { env } from '@api/config/env';
import { getDashboardBaseUrl } from '@api/config/dashboard-url';
import { generateDocumentDownloadPresignedUrls } from '@api/utils/s3';
type SubmissionGradingState = 'queued' | 'processing' | 'awaiting_manual' | 'completed' | 'failed';
@@ -775,7 +775,7 @@ async function sendSubmissionUpdateEmail(submissionId: string, newStatusId: numb
const orgName = orgResult?.orgName || 'ClassroomIO';
const statusText = LEGACY_BOARD_STATUS_LABELS[newStatusId] || 'Updated';
const baseUrl = env.NODE_ENV === 'development' ? 'http://localhost:5173' : 'https://app.classroomio.com';
const baseUrl = getDashboardBaseUrl();
const exerciseLink = `${baseUrl}/courses/${fullSubmission.courseId}/exercises/${fullSubmission.exercise.id}`;
// Calculate total and max points
@@ -852,7 +852,7 @@ async function sendExerciseSubmissionUpdateEmail(courseId: string, exerciseId: s
const orgName = orgResult?.orgName || 'ClassroomIO';
const baseUrl = env.NODE_ENV === 'development' ? 'http://localhost:5173' : 'https://app.classroomio.com';
const baseUrl = getDashboardBaseUrl();
const exerciseLink = `${baseUrl}/courses/${courseId}/exercises/${exerciseId}`;
const submissionLink = `${baseUrl}/courses/${courseId}/submissions`;