37 lines
1.2 KiB
TypeScript
37 lines
1.2 KiB
TypeScript
import path from 'path';
|
|
|
|
const COURSEWARE_ID_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
|
|
export function isValidCoursewareId(value: unknown): value is string {
|
|
return typeof value === 'string' && COURSEWARE_ID_PATTERN.test(value);
|
|
}
|
|
|
|
export function assertCoursewareId(value: unknown): asserts value is string {
|
|
if (!isValidCoursewareId(value)) {
|
|
throw new Error('Invalid coursewareId');
|
|
}
|
|
}
|
|
|
|
export function isValidCoursewareVersion(value: unknown): value is number {
|
|
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 1;
|
|
}
|
|
|
|
export function assertCoursewareVersion(value: unknown): asserts value is number {
|
|
if (!isValidCoursewareVersion(value)) {
|
|
throw new Error('Invalid courseware version');
|
|
}
|
|
}
|
|
|
|
/** Resolve exactly one direct child without allowing absolute paths or traversal. */
|
|
export function resolveDirectChildPath(rootDir: string, childName: string): string {
|
|
if (!childName || path.basename(childName) !== childName) {
|
|
throw new Error('Invalid courseware storage path');
|
|
}
|
|
const root = path.resolve(rootDir);
|
|
const child = path.resolve(root, childName);
|
|
if (path.dirname(child) !== root) {
|
|
throw new Error('Invalid courseware storage path');
|
|
}
|
|
return child;
|
|
}
|