feat: widgets and ai chat

This commit is contained in:
Rotimi Best
2026-04-27 11:40:38 +01:00
parent ae1801f85f
commit 5717dd8004
391 changed files with 28177 additions and 22467 deletions
@@ -28,6 +28,7 @@ import { ROLE } from '@cio/utils/constants';
import crypto from 'node:crypto';
import { getDashboardBaseUrl } from '@api/config/dashboard-url';
import { getProfilesByEmails } from '@cio/db/queries/auth';
import { ensureComplianceEnrollmentRecordsForProfiles } from '../course/compliance';
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const ORG_INVITE_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
@@ -188,6 +189,10 @@ async function enrollAudienceStudentProfilesInCourses(
);
}
if (validProfiles.length > 0) {
await ensureComplianceEnrollmentRecordsForProfiles(courseIds, validProfiles);
}
let emailsSent = 0;
const loginUrl = getDashboardBaseUrl(organization.siteName ?? undefined);
@@ -534,6 +539,10 @@ export async function importAudienceMembers(orgId: string, data: TImportAudience
if (profiles.length > 0) {
const users = profiles.map((p) => ({ profileId: p.id, email: p.email ?? undefined }));
await enrollUsersInCourseGroups(validGroupIds, users, ROLE.STUDENT);
await ensureComplianceEnrollmentRecordsForProfiles(
courseIds,
profiles.map((profile) => profile.id)
);
}
}
}
@@ -23,6 +23,7 @@ import { getDashboardBaseUrl } from '@api/config/dashboard-url';
import { parseCourseIdsFromInviteMetadata, parseProgramIdsFromInviteMetadata } from '@api/utils/org';
import { markUserAndProfileEmailVerified } from '@cio/db/queries/auth/profile';
import { sendEmail } from '@cio/email';
import { ensureComplianceEnrollmentRecordsForProfiles } from '../course/compliance';
type OrganizationInviteStatus = 'ACTIVE' | 'EXPIRED' | 'REVOKED' | 'ACCEPTED';
@@ -440,6 +441,7 @@ export async function acceptOrganizationInvite(token: string, user: TAuthUser, c
[{ profileId: user.id, email: normalizedEmail }],
invite.invite.roleId
);
await ensureComplianceEnrollmentRecordsForProfiles(courseIds, [user.id]);
} catch (error) {
console.error('acceptOrganizationInvite course enrollment error:', error);
}
@@ -652,6 +654,7 @@ export async function acceptOrganizationInviteById(
[{ profileId: user.id, email: normalizedEmail }],
result.invite.roleId
);
await ensureComplianceEnrollmentRecordsForProfiles(courseIds, [user.id]);
} catch (error) {
console.error('acceptOrganizationInviteById course enrollment error:', error);
}
@@ -0,0 +1,39 @@
import {
searchLmsCourses,
searchLmsPrograms,
searchOrgAudience,
searchOrgCourses,
searchOrgPrograms,
searchOrgTagsAndGroups,
searchOrgWidgets
} from '@cio/db/queries';
export async function searchOrganization(orgId: string, search: string, limit: number) {
const [courses, programs, widgets, tags, audience] = await Promise.all([
searchOrgCourses(orgId, search, limit),
searchOrgPrograms(orgId, search, limit),
searchOrgWidgets(orgId, search, limit),
searchOrgTagsAndGroups(orgId, search, limit),
searchOrgAudience(orgId, search, limit)
]);
return {
courses,
programs,
widgets,
tags,
audience
};
}
export async function searchLmsOrganization(orgId: string, profileId: string, search: string, limit: number) {
const [courses, programs] = await Promise.all([
searchLmsCourses(orgId, profileId, search, limit),
searchLmsPrograms(orgId, profileId, search, limit)
]);
return {
courses,
programs
};
}