Sync LMS backend source

This commit is contained in:
2026-07-07 16:09:12 +09:00
commit 5a2ecd0fea
126 changed files with 22959 additions and 0 deletions
@@ -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;
}
+215
View File
@@ -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;
}
}
+15
View File
@@ -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