feat: certification rules, content sanitization, handle draft course updates and reset content, better redis logging
This commit is contained in:
@@ -26,6 +26,7 @@ import {
|
||||
getCourseProgress,
|
||||
updateCourse
|
||||
} from '@api/services/course/course';
|
||||
import { assertCertificateDownloadAllowed, evaluateCourseCertification } from '@api/services/course/completion';
|
||||
import { getCourseTags, replaceCourseTags } from '@api/services/tag';
|
||||
|
||||
import { Hono } from '@api/utils/hono';
|
||||
@@ -393,6 +394,33 @@ export const courseRouter = new Hono()
|
||||
}
|
||||
}
|
||||
)
|
||||
/**
|
||||
* GET /course/:courseId/certification-evaluation
|
||||
* Full certification eligibility for the current user (blockers for UI).
|
||||
*/
|
||||
.get(
|
||||
'/:courseId/certification-evaluation',
|
||||
authMiddleware,
|
||||
courseMemberMiddleware,
|
||||
zValidator('param', ZCourseProgressParam),
|
||||
async (c) => {
|
||||
try {
|
||||
const { courseId } = c.req.valid('param');
|
||||
const user = c.get('user')!;
|
||||
const data = await evaluateCourseCertification(courseId, user.id);
|
||||
|
||||
return c.json(
|
||||
{
|
||||
success: true,
|
||||
data
|
||||
},
|
||||
200
|
||||
);
|
||||
} catch (error) {
|
||||
return handleError(c, error, 'Failed to evaluate certification');
|
||||
}
|
||||
}
|
||||
)
|
||||
/**
|
||||
* GET /course/:courseId/analytics
|
||||
* Gets course analytics including student progress, completion rates, and grades
|
||||
@@ -427,6 +455,10 @@ export const courseRouter = new Hono()
|
||||
zValidator('param', ZCourseDownloadParam),
|
||||
zValidator('json', ZCertificateDownload),
|
||||
async (c) => {
|
||||
const { courseId } = c.req.valid('param');
|
||||
const user = c.get('user')!;
|
||||
await assertCertificateDownloadAllowed(courseId, user.id);
|
||||
|
||||
const validatedData = c.req.valid('json');
|
||||
|
||||
const result = await generateCertificate(validatedData);
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ZCancelOrgPlan,
|
||||
ZCreateOrgPlan,
|
||||
ZCreateOrganization,
|
||||
ZGetAudienceQuery,
|
||||
ZGetCoursesBySiteName,
|
||||
ZGetOrgSetup,
|
||||
ZGetOrganizationCoursesQuery,
|
||||
@@ -37,6 +38,7 @@ import {
|
||||
getRecommendedCourses,
|
||||
getUserAnalytics,
|
||||
getUserEnrolledCourses,
|
||||
removeAudienceMember,
|
||||
removeTeamMember,
|
||||
updateOrg,
|
||||
updateOrgPlan
|
||||
@@ -178,15 +180,18 @@ export const organizationRouter = new Hono()
|
||||
* Gets organization audience (students)
|
||||
* Requires authentication
|
||||
*/
|
||||
.get('/audience', authMiddleware, orgTeamMemberMiddleware, async (c) => {
|
||||
.get('/audience', authMiddleware, orgTeamMemberMiddleware, zValidator('query', ZGetAudienceQuery), async (c) => {
|
||||
try {
|
||||
const orgId = c.req.header('cio-org-id')!;
|
||||
const audience = await getOrgAudience(orgId);
|
||||
const query = c.req.valid('query');
|
||||
const audience = await getOrgAudience(orgId, query);
|
||||
|
||||
return c.json(
|
||||
{
|
||||
success: true,
|
||||
data: audience
|
||||
data: audience.items,
|
||||
pagination: audience.pagination,
|
||||
query: audience.query
|
||||
},
|
||||
200
|
||||
);
|
||||
@@ -194,6 +199,35 @@ export const organizationRouter = new Hono()
|
||||
return handleError(c, error, 'Failed to fetch organization audience');
|
||||
}
|
||||
})
|
||||
/**
|
||||
* DELETE /organization/audience/:memberId
|
||||
* Removes a student from the organization
|
||||
*/
|
||||
.delete(
|
||||
'/audience/:memberId',
|
||||
authMiddleware,
|
||||
orgAdminMiddleware,
|
||||
zValidator('param', ZRemoveTeamMember),
|
||||
async (c) => {
|
||||
try {
|
||||
const orgId = c.req.header('cio-org-id')!;
|
||||
const user = c.get('user')!;
|
||||
const { memberId } = c.req.valid('param');
|
||||
|
||||
const deleted = await removeAudienceMember(orgId, memberId, user.id);
|
||||
|
||||
return c.json(
|
||||
{
|
||||
success: true,
|
||||
data: deleted
|
||||
},
|
||||
200
|
||||
);
|
||||
} catch (error) {
|
||||
return handleError(c, error, 'Failed to remove audience member');
|
||||
}
|
||||
}
|
||||
)
|
||||
/**
|
||||
* POST /organization/audience/resend-invite
|
||||
* Resends a student org invite email (new token; optional course metadata from last invite)
|
||||
@@ -604,7 +638,7 @@ export const organizationRouter = new Hono()
|
||||
.post(
|
||||
'/audience/assign-courses',
|
||||
authMiddleware,
|
||||
orgAdminMiddleware,
|
||||
orgTeamMemberMiddleware,
|
||||
zValidator('json', ZAssignAudienceCourses),
|
||||
async (c) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user