643 lines
20 KiB
TypeScript
643 lines
20 KiB
TypeScript
import { AppError, ErrorCodes } from '@api/utils/errors';
|
|
import type { TNewOrganizationPlan, TOrganization, TOrganizationPlan } from '@db/types';
|
|
import {
|
|
deleteOrganizationAudienceMember,
|
|
cancelOrganizationPlan,
|
|
createOrganizationPlan,
|
|
deleteOrganizationMember,
|
|
getOrgIdBySiteName,
|
|
getFirstOrganizationWithPlans,
|
|
getLatestOrgInvitesByEmails,
|
|
getOrganizationAudience,
|
|
getOrganizationBySiteName,
|
|
getOrganizationTeam,
|
|
getOrganizations,
|
|
revokeActiveOrganizationInvitesByEmails,
|
|
updateOrganization,
|
|
updateOrganizationPlan
|
|
} from '@cio/db/queries/organization';
|
|
import { getCourseIdsByTagSlugs, getCourseTagsByCourseIdsForOrganization } from '@cio/db/queries/tag';
|
|
import {
|
|
getCoursesById,
|
|
getCoursesBySiteNameForSetup,
|
|
getEnrolledCourses,
|
|
getExercisesBySiteName,
|
|
getExploreCourses,
|
|
getLessonsBySiteName,
|
|
getOrgCourses,
|
|
getPublishedCoursesBySiteName
|
|
} from '@cio/db/queries/course';
|
|
import { getLastLogin, getProfileCourseProgress, getUserExercisesStats } from '@cio/db/queries/analytics';
|
|
|
|
import type { OrganizationWithPlans } from '@cio/db/queries/organization/types';
|
|
import { ROLE } from '@cio/utils/constants';
|
|
import { createOrganizationWithOwner } from '@api/services/onboarding';
|
|
import { getProfileById } from '@cio/db/queries/auth';
|
|
import { inviteTeamMembers as inviteTeamMembersSecure } from './organization/invite';
|
|
import { deriveAudienceMemberStatus } from '@api/utils/audience-member-status';
|
|
import type { OrgAudienceMember, OrgAudiencePagination, OrgAudienceQuery } from '@api/types/org';
|
|
import type { TGetAudienceQuery } from '@cio/utils/validation/organization';
|
|
|
|
/**
|
|
* Creates a new organization with the current user as owner
|
|
* @param profileId - The profile ID of the user creating the organization
|
|
* @param data - Organization creation data (name, siteName)
|
|
* @returns Created organization, member, and updated organizations list
|
|
*/
|
|
export async function createOrg(profileId: string, data: { name: string; siteName: string }) {
|
|
try {
|
|
// Reuse existing function from onboarding service
|
|
return await createOrganizationWithOwner(profileId, {
|
|
orgName: data.name,
|
|
siteName: data.siteName
|
|
});
|
|
} catch (error) {
|
|
// Re-throw AppError as-is
|
|
if (error instanceof AppError) {
|
|
throw error;
|
|
}
|
|
// Wrap unexpected errors
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to create organization',
|
|
ErrorCodes.ORG_CREATE_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets organizations with optional filters
|
|
* @param filters - Filter options (siteName, customDomain, isCustomDomainVerified)
|
|
* @returns Array of organizations with plans
|
|
*/
|
|
export async function getOrganizationsWithFilters(filters?: {
|
|
siteName?: string;
|
|
customDomain?: string;
|
|
isCustomDomainVerified?: boolean;
|
|
}): Promise<OrganizationWithPlans[]> {
|
|
try {
|
|
const organizations = await getOrganizations(filters);
|
|
return organizations;
|
|
} catch (error) {
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch organizations',
|
|
ErrorCodes.ORGANIZATION_NOT_FOUND,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the first organization with plans - for self-hosted single-org mode
|
|
* @returns First organization or null
|
|
*/
|
|
export async function getFirstOrgForSelfHosted(): Promise<OrganizationWithPlans | null> {
|
|
try {
|
|
return await getFirstOrganizationWithPlans();
|
|
} catch (error) {
|
|
console.error('getFirstOrgForSelfHosted error:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets organization team members (non-students)
|
|
* @param orgId - The organization ID
|
|
* @returns Array of team members
|
|
*/
|
|
export async function getOrgTeam(orgId: string) {
|
|
try {
|
|
const team = await getOrganizationTeam(orgId);
|
|
return team;
|
|
} catch (error) {
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch organization team',
|
|
ErrorCodes.ORG_TEAM_FETCH_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets organization audience (students)
|
|
* @param orgId - The organization ID
|
|
* @returns Audience members with invite status for profile-less rows
|
|
*/
|
|
export async function getOrgAudience(
|
|
orgId: string,
|
|
query: TGetAudienceQuery
|
|
): Promise<{ items: OrgAudienceMember[]; pagination: OrgAudiencePagination; query: OrgAudienceQuery }> {
|
|
try {
|
|
const audienceResult = await getOrganizationAudience(orgId, query);
|
|
const audience = audienceResult.items;
|
|
|
|
const emailsWithoutProfile = audience.filter((m) => !m.profileId && m.email).map((m) => m.email.toLowerCase());
|
|
|
|
const invites = await getLatestOrgInvitesByEmails(orgId, emailsWithoutProfile);
|
|
const inviteByEmail = new Map(invites.map((i) => [i.email.toLowerCase(), i]));
|
|
|
|
return {
|
|
items: audience.map(
|
|
(member): OrgAudienceMember => ({
|
|
...member,
|
|
status: deriveAudienceMemberStatus(
|
|
member.profileId,
|
|
member.email ? inviteByEmail.get(member.email.toLowerCase()) : undefined
|
|
)
|
|
})
|
|
),
|
|
pagination: {
|
|
page: audienceResult.page,
|
|
limit: audienceResult.limit,
|
|
total: audienceResult.total,
|
|
totalPages: audienceResult.totalPages
|
|
},
|
|
query: {
|
|
page: audienceResult.page,
|
|
limit: audienceResult.limit,
|
|
search: query.search,
|
|
sortBy: query.sortBy,
|
|
sortOrder: query.sortOrder
|
|
}
|
|
};
|
|
} catch (error) {
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch organization audience',
|
|
ErrorCodes.ORG_AUDIENCE_FETCH_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a student audience member from an organization
|
|
* @param orgId - The organization ID
|
|
* @param memberId - The student member ID to remove
|
|
* @returns Deleted member
|
|
*/
|
|
export async function removeAudienceMember(orgId: string, memberId: number, removedByProfileId?: string) {
|
|
try {
|
|
const deleted = await deleteOrganizationAudienceMember(orgId, memberId);
|
|
if (!deleted) {
|
|
throw new AppError('Audience member not found', ErrorCodes.ORG_AUDIENCE_REMOVE_FAILED, 404);
|
|
}
|
|
|
|
if (deleted.email && removedByProfileId) {
|
|
await revokeActiveOrganizationInvitesByEmails(orgId, [deleted.email.toLowerCase()], removedByProfileId);
|
|
}
|
|
|
|
return deleted;
|
|
} catch (error) {
|
|
if (error instanceof AppError) {
|
|
throw error;
|
|
}
|
|
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to remove audience member',
|
|
ErrorCodes.ORG_AUDIENCE_REMOVE_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets public courses for an organization (landing page)
|
|
* @param siteName - The organization siteName
|
|
* @returns Array of published courses with lesson counts
|
|
*/
|
|
export async function getPublicCourses(siteName: string, tagSlugs?: string[]) {
|
|
try {
|
|
const orgResult = await getOrgIdBySiteName(siteName);
|
|
const org = orgResult[0];
|
|
|
|
if (!org) {
|
|
throw new AppError('Organization not found', ErrorCodes.ORG_NOT_FOUND, 404);
|
|
}
|
|
|
|
let filteredCourseIds: string[] | undefined = undefined;
|
|
if (tagSlugs && tagSlugs.length > 0) {
|
|
filteredCourseIds = await getCourseIdsByTagSlugs(org.id, tagSlugs);
|
|
if (filteredCourseIds.length === 0) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
const courses = await getPublishedCoursesBySiteName(siteName, filteredCourseIds);
|
|
const tagsByCourseId = await getCourseTagsByCourseIdsForOrganization(
|
|
org.id,
|
|
courses.map((course) => course.id)
|
|
);
|
|
|
|
return courses.map((course) => ({
|
|
...course,
|
|
tags: tagsByCourseId[course.id] ?? []
|
|
}));
|
|
} catch (error) {
|
|
if (error instanceof AppError) throw error;
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch public courses',
|
|
ErrorCodes.COURSES_FETCH_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets organization courses with role-based data filtering
|
|
* @param orgId - The organization ID
|
|
* @param userId - User ID for role-based filtering (required)
|
|
* @param userRole - User's role in the organization (from context)
|
|
* @returns Object with role and courses array
|
|
* - Admins: all courses with totalStudents
|
|
* - Tutors: assigned courses with totalStudents
|
|
*/
|
|
export async function getOrganizationCourses(orgId: string, userId: string, userRole: number, tagSlugs?: string[]) {
|
|
try {
|
|
if (!userRole) {
|
|
throw new AppError('Invalid permissions', ErrorCodes.UNAUTHORIZED, 403);
|
|
}
|
|
|
|
let filteredCourseIds: string[] | undefined = undefined;
|
|
if (tagSlugs && tagSlugs.length > 0) {
|
|
filteredCourseIds = await getCourseIdsByTagSlugs(orgId, tagSlugs);
|
|
if (filteredCourseIds.length === 0) {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
switch (userRole) {
|
|
case ROLE.ADMIN: {
|
|
const courses = await getOrgCourses({ orgId, courseIds: filteredCourseIds });
|
|
const tagsByCourseId = await getCourseTagsByCourseIdsForOrganization(
|
|
orgId,
|
|
courses.map((course) => course.id)
|
|
);
|
|
|
|
return courses.map((course) => ({
|
|
...course,
|
|
tags: tagsByCourseId[course.id] ?? []
|
|
}));
|
|
}
|
|
case ROLE.TUTOR: {
|
|
const courses = await getOrgCourses({ orgId, profileId: userId, courseIds: filteredCourseIds });
|
|
const tagsByCourseId = await getCourseTagsByCourseIdsForOrganization(
|
|
orgId,
|
|
courses.map((course) => course.id)
|
|
);
|
|
|
|
return courses.map((course) => ({
|
|
...course,
|
|
tags: tagsByCourseId[course.id] ?? []
|
|
}));
|
|
}
|
|
default:
|
|
throw new AppError('Invalid permissions', ErrorCodes.UNAUTHORIZED, 403);
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof AppError) throw error;
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch courses',
|
|
ErrorCodes.COURSES_FETCH_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets user enrolled courses by organization orgId
|
|
*
|
|
* @param orgId - The organization ID
|
|
* @param userId - User ID for filtering
|
|
* @returns Array of enrolled courses
|
|
*/
|
|
export async function getUserEnrolledCourses(orgId: string, userId: string) {
|
|
try {
|
|
return getEnrolledCourses({ orgId, profileId: userId });
|
|
} catch (error) {
|
|
if (error instanceof AppError) throw error;
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch enrolled courses',
|
|
ErrorCodes.COURSES_FETCH_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets recommended courses (published courses user isn't enrolled in) for an organization
|
|
* Used in LMS explore page
|
|
*
|
|
* @param orgId - The organization ID
|
|
* @param userId - User ID to exclude enrolled courses
|
|
* @returns Array of recommended courses
|
|
*/
|
|
export async function getRecommendedCourses(orgId: string, userId: string) {
|
|
try {
|
|
return getExploreCourses({ orgId, profileId: userId });
|
|
} catch (error) {
|
|
if (error instanceof AppError) throw error;
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch recommended courses',
|
|
ErrorCodes.COURSES_FETCH_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets courses by organization orgId
|
|
* @param orgId - The organization orgId
|
|
* @returns Array of courses
|
|
*/
|
|
export async function getCoursesByOrgId(orgId: string) {
|
|
try {
|
|
return getCoursesById(orgId);
|
|
} catch (error) {
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch courses',
|
|
ErrorCodes.COURSES_FETCH_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets setup data for an organization
|
|
* @param siteName - The organization site name
|
|
* @returns Setup data including courses, lessons, exercises, and organization info
|
|
*/
|
|
export async function getOrgSetupData(siteName: string) {
|
|
try {
|
|
const [organization, courses, lessons, exercises] = await Promise.all([
|
|
getOrganizationBySiteName(siteName),
|
|
getCoursesBySiteNameForSetup(siteName),
|
|
getLessonsBySiteName(siteName),
|
|
getExercisesBySiteName(siteName)
|
|
]);
|
|
|
|
// Check if course is published
|
|
const publishedCourse = courses.find((course) => course.isPublished === true);
|
|
|
|
// Get organization avatar URL directly from organization
|
|
const orgHasAvatarUrl = !!organization?.avatarUrl;
|
|
|
|
return {
|
|
isCoursePublished: !!publishedCourse,
|
|
isCourseCreated: courses.length > 0,
|
|
orgHasAvatarUrl,
|
|
courseData: courses,
|
|
lessonData: lessons,
|
|
isLessonCreated: lessons.length > 0,
|
|
isExerciseCreated: exercises.length > 0
|
|
};
|
|
} catch (error) {
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch setup data',
|
|
ErrorCodes.INTERNAL_ERROR,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new organization plan
|
|
* @param data - Organization plan creation data
|
|
* @returns Created organization plan
|
|
*/
|
|
export async function createOrgPlan(data: TNewOrganizationPlan) {
|
|
try {
|
|
const plan = await createOrganizationPlan({
|
|
orgId: data.orgId,
|
|
planName: data.planName,
|
|
subscriptionId: data.subscriptionId,
|
|
triggeredBy: data.triggeredBy,
|
|
payload: data.payload,
|
|
isActive: true,
|
|
provider: 'polar'
|
|
});
|
|
return plan;
|
|
} catch (error) {
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to create organization plan',
|
|
ErrorCodes.ORG_PLAN_CREATE_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates an organization
|
|
* @param orgId - The organization ID
|
|
* @param data - Partial organization data to update
|
|
* @returns Updated organization
|
|
*/
|
|
export async function updateOrg(orgId: string, data: Partial<TOrganization>) {
|
|
try {
|
|
const organization = await updateOrganization(orgId, data);
|
|
if (!organization) {
|
|
throw new AppError('Organization not found', ErrorCodes.ORGANIZATION_NOT_FOUND, 404);
|
|
}
|
|
return organization;
|
|
} catch (error) {
|
|
if (error instanceof AppError) {
|
|
throw error;
|
|
}
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to update organization',
|
|
ErrorCodes.INTERNAL_ERROR,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates an organization plan
|
|
* @param subscriptionId - Subscription ID
|
|
* @param payload - Payload data to update
|
|
* @returns Updated organization plan
|
|
*/
|
|
export async function updateOrgPlan(subscriptionId: string, payload: TOrganizationPlan['payload']) {
|
|
try {
|
|
const plan = await updateOrganizationPlan(subscriptionId, payload);
|
|
if (!plan) {
|
|
throw new AppError('Organization plan not found', ErrorCodes.ORG_PLAN_NOT_FOUND, 404);
|
|
}
|
|
return plan;
|
|
} catch (error) {
|
|
if (error instanceof AppError) {
|
|
throw error;
|
|
}
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to update organization plan',
|
|
ErrorCodes.ORG_PLAN_UPDATE_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cancels an organization plan
|
|
* @param subscriptionId - Subscription ID
|
|
* @param payload - Payload data to update
|
|
* @returns Updated organization plan
|
|
*/
|
|
export async function cancelOrgPlan(subscriptionId: string, payload: TOrganizationPlan['payload']) {
|
|
try {
|
|
const plan = await cancelOrganizationPlan(subscriptionId, payload);
|
|
if (!plan) {
|
|
throw new AppError('Organization plan not found', ErrorCodes.ORG_PLAN_NOT_FOUND, 404);
|
|
}
|
|
return plan;
|
|
} catch (error) {
|
|
if (error instanceof AppError) {
|
|
throw error;
|
|
}
|
|
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to cancel organization plan',
|
|
ErrorCodes.ORG_PLAN_CANCEL_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invites team members to an organization
|
|
* @param orgId - The organization ID
|
|
* @param emails - Array of email addresses to invite
|
|
* @param roleId - Role ID to assign (ADMIN or TUTOR)
|
|
* @returns Array of created members
|
|
*/
|
|
export async function inviteTeamMembers(orgId: string, emails: string[], roleId: number, invitedByProfileId?: string) {
|
|
if (!invitedByProfileId) {
|
|
throw new AppError('Inviter profile ID is required', ErrorCodes.VALIDATION_ERROR, 400, 'invitedByProfileId');
|
|
}
|
|
|
|
return inviteTeamMembersSecure(orgId, emails, roleId, invitedByProfileId);
|
|
}
|
|
|
|
/**
|
|
* Removes a team member from an organization
|
|
* @param orgId - The organization ID
|
|
* @param memberId - The member ID to remove
|
|
* @returns Deleted member
|
|
*/
|
|
export async function removeTeamMember(orgId: string, memberId: number, removedByProfileId?: string) {
|
|
try {
|
|
const deleted = await deleteOrganizationMember(orgId, memberId);
|
|
if (!deleted) {
|
|
throw new AppError('Team member not found', ErrorCodes.ORG_TEAM_REMOVE_FAILED, 404);
|
|
}
|
|
|
|
if (deleted.email && removedByProfileId) {
|
|
await revokeActiveOrganizationInvitesByEmails(orgId, [deleted.email.toLowerCase()], removedByProfileId);
|
|
}
|
|
|
|
return deleted;
|
|
} catch (error) {
|
|
if (error instanceof AppError) {
|
|
throw error;
|
|
}
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to remove team member',
|
|
ErrorCodes.ORG_TEAM_REMOVE_FAILED,
|
|
500
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function to sum array object values by key
|
|
*/
|
|
function sumArrObject<T>(arr: T[], key: keyof T): number {
|
|
return arr.reduce((sum, item) => sum + ((item[key] as number) || 0), 0);
|
|
}
|
|
|
|
/**
|
|
* Helper function to calculate percentage with rounding
|
|
*/
|
|
function calcPercentageWithRounding(a: number, b: number): number {
|
|
if (b === 0) {
|
|
return 0;
|
|
}
|
|
const rawPercentage = (a / b) * 100;
|
|
const fixedString = rawPercentage.toFixed(1);
|
|
const roundedNumber = parseFloat(fixedString);
|
|
return isNaN(roundedNumber) ? 0 : roundedNumber;
|
|
}
|
|
|
|
/**
|
|
* Gets user analytics for an organization
|
|
* @param userId - The user ID (profile ID)
|
|
* @param orgId - The organization ID
|
|
* @returns User analytics data including courses, progress, and grades
|
|
*/
|
|
export async function getUserAnalytics(userId: string, orgId: string) {
|
|
try {
|
|
// Get user profile
|
|
const profile = await getProfileById(userId);
|
|
if (!profile) {
|
|
throw new AppError('User profile not found', ErrorCodes.PROFILE_NOT_FOUND, 404);
|
|
}
|
|
|
|
// Get last login
|
|
const lastSeen = await getLastLogin(userId);
|
|
|
|
// Get courses for this org where user is enrolled
|
|
const courses = await getEnrolledCourses({ orgId, profileId: userId });
|
|
|
|
// Build analytics data for each course
|
|
const coursesWithStats = await Promise.all(
|
|
courses.map(async (course) => {
|
|
const [userExercisesStats, courseProgress] = await Promise.all([
|
|
getUserExercisesStats(course.id, userId),
|
|
getProfileCourseProgress(course.id, userId)
|
|
]);
|
|
|
|
const totalEarnedPoints = sumArrObject(userExercisesStats, 'score');
|
|
const totalPoints = sumArrObject(userExercisesStats, 'totalPoints');
|
|
const averageGrade = calcPercentageWithRounding(totalEarnedPoints, totalPoints);
|
|
const lessonsCompleted = courseProgress.lessons_completed || 0;
|
|
const lessonsCount = courseProgress.lessons_count || 0;
|
|
|
|
return {
|
|
...course,
|
|
...courseProgress,
|
|
progress_percentage: calcPercentageWithRounding(lessonsCompleted, lessonsCount),
|
|
average_grade: averageGrade
|
|
};
|
|
})
|
|
);
|
|
|
|
// Calculate overall stats
|
|
const totalLessons = coursesWithStats.reduce((acc, course) => acc + (course.lessons_count || 0), 0);
|
|
const completedLessons = coursesWithStats.reduce((acc, course) => acc + (course.lessons_completed || 0), 0);
|
|
const overallCourseProgress = calcPercentageWithRounding(completedLessons, totalLessons);
|
|
|
|
const allGrades = sumArrObject(coursesWithStats, 'average_grade');
|
|
const overallAverageGrade = calcPercentageWithRounding(allGrades, coursesWithStats.length);
|
|
|
|
return {
|
|
user: {
|
|
id: userId,
|
|
fullName: profile.fullname || '',
|
|
email: profile.email || '',
|
|
avatarUrl: profile.avatarUrl || '',
|
|
lastSeen: lastSeen || undefined
|
|
},
|
|
courses: coursesWithStats,
|
|
overallCourseProgress,
|
|
overallAverageGrade
|
|
};
|
|
} catch (error) {
|
|
if (error instanceof AppError) {
|
|
throw error;
|
|
}
|
|
throw new AppError(
|
|
error instanceof Error ? error.message : 'Failed to fetch user analytics',
|
|
ErrorCodes.INTERNAL_ERROR,
|
|
500
|
|
);
|
|
}
|
|
}
|