feat: implement automation feature

This commit is contained in:
Rotimi Best
2026-03-18 23:39:12 +01:00
parent 576d0366de
commit ef4424e267
121 changed files with 4544 additions and 1055 deletions
@@ -0,0 +1,80 @@
import { Context, Next } from 'hono';
import type { TOrganizationApiKeyScope } from '@cio/utils/validation/organization';
import { ErrorCodes } from '@api/utils/errors';
import { organizationApiKeyHasScopes } from '@api/services/organization/automation-key';
import { getCourseOrganizationId } from '@cio/db/queries/tag';
import { courseMemberMiddleware } from './course-member';
export const courseMemberOrAutomationKeyMiddleware =
(requiredScopes: readonly TOrganizationApiKeyScope[] = []) =>
async (c: Context, next: Next) => {
try {
const automationKey = c.get('automationKey');
if (!automationKey) {
return courseMemberMiddleware(c, next);
}
if (!organizationApiKeyHasScopes(automationKey.scopes ?? [], requiredScopes)) {
return c.json(
{
success: false,
error: 'Automation key is missing required scopes',
code: ErrorCodes.FORBIDDEN
},
403
);
}
const courseId = c.req.param('courseId') || c.req.query('courseId');
if (!courseId) {
return c.json(
{
success: false,
error: 'Course ID is required',
code: 'COURSE_ID_REQUIRED'
},
400
);
}
const courseOrgId = await getCourseOrganizationId(courseId);
if (!courseOrgId) {
return c.json(
{
success: false,
error: 'Course not found',
code: ErrorCodes.COURSE_NOT_FOUND
},
404
);
}
if (courseOrgId !== automationKey.organizationId) {
return c.json(
{
success: false,
error: 'Invalid permissions',
code: ErrorCodes.UNAUTHORIZED
},
403
);
}
c.set('orgId', automationKey.organizationId);
c.set('actorId', automationKey.createdByProfileId);
return next();
} catch (error) {
console.error('Error in courseMemberOrAutomationKeyMiddleware:', error);
return c.json(
{
success: false,
error: 'Failed to verify course access',
code: 'COURSE_MEMBER_CHECK_FAILED'
},
500
);
}
};
@@ -0,0 +1,80 @@
import { Context, Next } from 'hono';
import type { TOrganizationApiKeyScope } from '@cio/utils/validation/organization';
import { ErrorCodes } from '@api/utils/errors';
import { organizationApiKeyHasScopes } from '@api/services/organization/automation-key';
import { getCourseOrganizationId } from '@cio/db/queries/tag';
import { courseTeamMemberMiddleware } from './course-team-member';
export const courseTeamMemberOrAutomationKeyMiddleware =
(requiredScopes: readonly TOrganizationApiKeyScope[] = []) =>
async (c: Context, next: Next) => {
try {
const automationKey = c.get('automationKey');
if (!automationKey) {
return courseTeamMemberMiddleware(c, next);
}
if (!organizationApiKeyHasScopes(automationKey.scopes ?? [], requiredScopes)) {
return c.json(
{
success: false,
error: 'Automation key is missing required scopes',
code: ErrorCodes.FORBIDDEN
},
403
);
}
const courseId = c.req.param('courseId') || c.req.query('courseId');
if (!courseId) {
return c.json(
{
success: false,
error: 'Course ID is required',
code: 'COURSE_ID_REQUIRED'
},
400
);
}
const courseOrgId = await getCourseOrganizationId(courseId);
if (!courseOrgId) {
return c.json(
{
success: false,
error: 'Course not found',
code: ErrorCodes.COURSE_NOT_FOUND
},
404
);
}
if (courseOrgId !== automationKey.organizationId) {
return c.json(
{
success: false,
error: 'Invalid permissions',
code: ErrorCodes.UNAUTHORIZED
},
403
);
}
c.set('orgId', automationKey.organizationId);
c.set('actorId', automationKey.createdByProfileId);
return next();
} catch (error) {
console.error('Error in courseTeamMemberOrAutomationKeyMiddleware:', error);
return c.json(
{
success: false,
error: 'Failed to verify course team access',
code: 'COURSE_TEAM_MEMBER_CHECK_FAILED'
},
500
);
}
};
@@ -0,0 +1,45 @@
import { Context, Next } from 'hono';
import type { TOrganizationApiKeyScope } from '@cio/utils/validation/organization';
import { ErrorCodes } from '@api/utils/errors';
import { organizationApiKeyHasScopes } from '@api/services/organization/automation-key';
import { orgMemberMiddleware } from './org-member';
export const orgMemberOrAutomationKeyMiddleware =
(requiredScopes: readonly TOrganizationApiKeyScope[] = []) =>
async (c: Context, next: Next) => {
try {
const automationKey = c.get('automationKey');
if (!automationKey) {
return orgMemberMiddleware(c, next);
}
if (!organizationApiKeyHasScopes(automationKey.scopes ?? [], requiredScopes)) {
return c.json(
{
success: false,
error: 'Automation key is missing required scopes',
code: ErrorCodes.FORBIDDEN
},
403
);
}
c.set('orgId', automationKey.organizationId);
c.set('userRole', null);
c.set('actorId', automationKey.createdByProfileId);
return next();
} catch (error) {
console.error('Error in orgMemberOrAutomationKeyMiddleware:', error);
return c.json(
{
success: false,
error: 'Failed to verify organization membership',
code: 'ORG_MEMBER_CHECK_FAILED'
},
500
);
}
};