Sync LMS backend source
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import { IsString } from 'class-validator';
|
||||
|
||||
export class AssignInstructorPlatformDto {
|
||||
@IsString()
|
||||
groupId: string;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class CreateInstructorPlatformDto {
|
||||
@IsString()
|
||||
platformId: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
displayName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
agentNodeId?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
agentHostname?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
agentOwnerEmail?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sshUser?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
sshPort?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
workspaceRoot?: string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { IsEmail, IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
export class CreateInstructorStudentDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
courseId?: string | null;
|
||||
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
groupId?: string | null;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { IsArray, IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
|
||||
export class SaveInstructorGroupDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
courseId?: string | null;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
name: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
platformName?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
instructorPlatformId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
memberIds?: string[];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { IsInt, IsString, Max, Min } from 'class-validator';
|
||||
|
||||
export class SaveSubmissionScoreDto {
|
||||
@IsString()
|
||||
studentId!: string;
|
||||
|
||||
@IsString()
|
||||
weekId!: string;
|
||||
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(100)
|
||||
score!: number;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { IsArray, IsBoolean, IsInt, IsOptional, IsString, Max, Min } from 'class-validator';
|
||||
|
||||
export class UpdateInstructorCurriculumDto {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
courseId?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
groupId?: string | null;
|
||||
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(52)
|
||||
activeWeek: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
activeLearningStep?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
applyToStudents?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
completed?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
tagLabel?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
title?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
duration?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
overview?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
deliverable?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
requiresSourceSubmission?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
learningSteps?: Array<{
|
||||
title?: string;
|
||||
description?: string;
|
||||
videoEnabled?: boolean;
|
||||
videoUrl?: string;
|
||||
concepts?: string[];
|
||||
}>;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
topics?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
topicDetails?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
topicVideoUrls?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
practiceContents?: Array<{
|
||||
title?: string;
|
||||
description?: string;
|
||||
}>;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
disabledModuleIds?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
visualizationPlatformKeys?: string[];
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { IsArray, IsInt, IsNumber, IsObject, IsOptional, IsString, Max, Min } from 'class-validator';
|
||||
|
||||
export class UpdateInstructorPlatformDto {
|
||||
@IsString()
|
||||
displayName: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
vehicleType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(10)
|
||||
wheelbaseMeters?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(100)
|
||||
maxSpeedMetersPerSecond?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(90)
|
||||
maxSteeringAngleDegrees?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
sensors?: Array<{ title?: string; description?: string; enabled?: boolean }>;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
drivers?: Array<{ title?: string; description?: string }>;
|
||||
|
||||
@IsOptional()
|
||||
@IsObject()
|
||||
algorithms?: {
|
||||
slam?: string;
|
||||
planning?: string;
|
||||
control?: string;
|
||||
};
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(240)
|
||||
controlDurationMinutes?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(60)
|
||||
controlWarningMinutes?: number;
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Post, Put, Query, Req, UploadedFile, UseInterceptors } from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { Request } from 'express';
|
||||
|
||||
import { InstructorService } from './instructor.service';
|
||||
import { AssignInstructorPlatformDto } from './dto/assign-instructor-platform.dto';
|
||||
import { CreateInstructorPlatformDto } from './dto/create-instructor-platform.dto';
|
||||
import { CreateInstructorStudentDto } from './dto/create-instructor-student.dto';
|
||||
import { SaveInstructorGroupDto } from './dto/save-instructor-group.dto';
|
||||
import { SaveSubmissionScoreDto } from './dto/save-submission-score.dto';
|
||||
import { UpdateInstructorCurriculumDto } from './dto/update-instructor-curriculum.dto';
|
||||
import { UpdateInstructorPlatformDto } from './dto/update-instructor-platform.dto';
|
||||
|
||||
@Controller('instructor')
|
||||
export class InstructorController {
|
||||
constructor(private readonly instructorService: InstructorService) {}
|
||||
|
||||
@Get('students')
|
||||
listStudents(@Req() request: Request, @Query('courseId') courseId?: string) {
|
||||
return this.instructorService.listStudents(this.getBearerToken(request), courseId);
|
||||
}
|
||||
|
||||
@Get('courses')
|
||||
listCourses(@Req() request: Request) {
|
||||
return this.instructorService.listCourses(this.getBearerToken(request));
|
||||
}
|
||||
|
||||
@Get('submissions')
|
||||
listSubmissions(@Req() request: Request) {
|
||||
return this.instructorService.listSubmissions(this.getBearerToken(request));
|
||||
}
|
||||
|
||||
@Put('submissions/score')
|
||||
saveSubmissionScore(@Req() request: Request, @Body() body: SaveSubmissionScoreDto) {
|
||||
return this.instructorService.saveSubmissionScore(this.getBearerToken(request), body);
|
||||
}
|
||||
|
||||
@Post('students')
|
||||
createStudent(
|
||||
@Req() request: Request,
|
||||
@Body() body: CreateInstructorStudentDto,
|
||||
) {
|
||||
return this.instructorService.createStudent(
|
||||
this.getBearerToken(request),
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('students/:studentId')
|
||||
removeStudent(@Req() request: Request, @Param('studentId') studentId: string) {
|
||||
return this.instructorService.removeStudent(
|
||||
this.getBearerToken(request),
|
||||
studentId,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('groups')
|
||||
listGroups(@Req() request: Request, @Query('courseId') courseId?: string) {
|
||||
return this.instructorService.listGroups(this.getBearerToken(request), courseId);
|
||||
}
|
||||
|
||||
@Get('platforms')
|
||||
listPlatforms(@Req() request: Request) {
|
||||
return this.instructorService.listPlatforms(this.getBearerToken(request));
|
||||
}
|
||||
|
||||
@Get('platforms/agents')
|
||||
listPlatformAgents(@Req() request: Request) {
|
||||
return this.instructorService.listPlatformAgents(this.getBearerToken(request));
|
||||
}
|
||||
|
||||
@Get('curriculum')
|
||||
getCurriculum(@Req() request: Request, @Query('groupId') groupId?: string, @Query('courseId') courseId?: string) {
|
||||
return this.instructorService.getCurriculum(this.getBearerToken(request), groupId, courseId);
|
||||
}
|
||||
|
||||
@Put('curriculum')
|
||||
updateCurriculum(
|
||||
@Req() request: Request,
|
||||
@Body() body: UpdateInstructorCurriculumDto,
|
||||
) {
|
||||
return this.instructorService.updateCurriculum(
|
||||
this.getBearerToken(request),
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('curriculum/weeks/:week')
|
||||
deleteCurriculumWeek(
|
||||
@Req() request: Request,
|
||||
@Param('week', ParseIntPipe) week: number,
|
||||
) {
|
||||
return this.instructorService.deleteCurriculumWeek(
|
||||
this.getBearerToken(request),
|
||||
week,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('curriculum/weeks/:week/references')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
uploadCurriculumReference(
|
||||
@Req() request: Request,
|
||||
@Param('week', ParseIntPipe) week: number,
|
||||
@UploadedFile() file: { originalname: string; mimetype?: string; size?: number; buffer?: Buffer },
|
||||
@Body('title') title?: string,
|
||||
@Body('courseId') courseId?: string,
|
||||
) {
|
||||
return this.instructorService.uploadCurriculumReference(
|
||||
this.getBearerToken(request),
|
||||
{ courseId, week, title, kind: 'REFERENCE' },
|
||||
file,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('curriculum/weeks/:week/example-codes')
|
||||
@UseInterceptors(FileInterceptor('file'))
|
||||
uploadCurriculumExampleCode(
|
||||
@Req() request: Request,
|
||||
@Param('week', ParseIntPipe) week: number,
|
||||
@UploadedFile() file: { originalname: string; mimetype?: string; size?: number; buffer?: Buffer },
|
||||
@Body('title') title?: string,
|
||||
@Body('courseId') courseId?: string,
|
||||
) {
|
||||
return this.instructorService.uploadCurriculumReference(
|
||||
this.getBearerToken(request),
|
||||
{ courseId, week, title, kind: 'EXAMPLE_CODE' },
|
||||
file,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('curriculum/references/:referenceId')
|
||||
deleteCurriculumReference(
|
||||
@Req() request: Request,
|
||||
@Param('referenceId') referenceId: string,
|
||||
@Query('courseId') courseId?: string,
|
||||
) {
|
||||
return this.instructorService.deleteCurriculumReference(
|
||||
this.getBearerToken(request),
|
||||
referenceId,
|
||||
courseId,
|
||||
);
|
||||
}
|
||||
|
||||
@Post('platforms')
|
||||
createPlatform(
|
||||
@Req() request: Request,
|
||||
@Body() body: CreateInstructorPlatformDto,
|
||||
) {
|
||||
return this.instructorService.createPlatform(
|
||||
this.getBearerToken(request),
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
@Put('platforms/:platformId')
|
||||
updatePlatform(
|
||||
@Req() request: Request,
|
||||
@Param('platformId') platformId: string,
|
||||
@Body() body: UpdateInstructorPlatformDto,
|
||||
) {
|
||||
return this.instructorService.updatePlatform(this.getBearerToken(request), platformId, body);
|
||||
}
|
||||
|
||||
@Post('platforms/:platformId/assignment')
|
||||
assignPlatform(
|
||||
@Req() request: Request,
|
||||
@Param('platformId') platformId: string,
|
||||
@Body() body: AssignInstructorPlatformDto,
|
||||
@Query('force') force?: string,
|
||||
) {
|
||||
return this.instructorService.assignPlatformToGroup(
|
||||
this.getBearerToken(request),
|
||||
platformId,
|
||||
body.groupId,
|
||||
force === 'true',
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('platforms/:platformId')
|
||||
deletePlatform(
|
||||
@Req() request: Request,
|
||||
@Param('platformId') platformId: string,
|
||||
@Query('force') force?: string,
|
||||
) {
|
||||
return this.instructorService.deletePlatform(this.getBearerToken(request), platformId, force === 'true');
|
||||
}
|
||||
|
||||
@Post('groups')
|
||||
createGroup(@Req() request: Request, @Body() body: SaveInstructorGroupDto) {
|
||||
return this.instructorService.createGroup(this.getBearerToken(request), body);
|
||||
}
|
||||
|
||||
@Put('groups/:groupId')
|
||||
updateGroup(
|
||||
@Req() request: Request,
|
||||
@Param('groupId') groupId: string,
|
||||
@Body() body: SaveInstructorGroupDto,
|
||||
) {
|
||||
return this.instructorService.updateGroup(
|
||||
this.getBearerToken(request),
|
||||
groupId,
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
@Delete('groups/:groupId')
|
||||
deleteGroup(@Req() request: Request, @Param('groupId') groupId: string, @Query('courseId') courseId?: string) {
|
||||
return this.instructorService.deleteGroup(this.getBearerToken(request), groupId, courseId);
|
||||
}
|
||||
|
||||
private getBearerToken(request: Request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { ChatModule } from '../chat/chat.module';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { PlatformGatewayClient } from '../platform-control/platform-gateway.client';
|
||||
import { InstructorController } from './instructor.controller';
|
||||
import { InstructorService } from './instructor.service';
|
||||
|
||||
@Module({
|
||||
imports: [AuthModule, ChatModule, PrismaModule],
|
||||
controllers: [InstructorController],
|
||||
providers: [InstructorService, PlatformGatewayClient],
|
||||
})
|
||||
export class InstructorModule {}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user