fix(lms): support direct R2 map uploads and preserve empty course groups

This commit is contained in:
2026-09-09 13:57:28 +09:00
parent 121bf41266
commit d70f99e9c9
12 changed files with 348 additions and 57 deletions
+19 -17
View File
@@ -22,6 +22,7 @@ import { R2PublicFileService } from '../storage/r2-public-file.service';
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 { groupCourseScope } from './group-course-scope';
import { SaveSubmissionScoreDto } from './dto/save-submission-score.dto';
import { UpdateInstructorCurriculumDto } from './dto/update-instructor-curriculum.dto';
import { UpdateInstructorPlatformDto } from './dto/update-instructor-platform.dto';
@@ -1126,6 +1127,8 @@ export class InstructorService {
? { courseId: course.id }
: { course: { teacherId: instructor.id } };
if (courseId?.trim() && !course) throw new NotFoundException('Course not found');
const groups = await this.prisma.studentGroup.findMany({
where: {
AND: [
@@ -1135,20 +1138,7 @@ export class InstructorService {
organizationName ? { organizationName } : undefined,
].filter(Boolean) as Prisma.StudentGroupWhereInput[],
},
{
members: {
some: {
user: {
enrollments: {
some: {
status: 'ACTIVE',
...enrollmentScope,
},
},
},
},
},
},
groupCourseScope(instructor.id, course?.id),
],
},
orderBy: { name: 'asc' },
@@ -2568,6 +2558,9 @@ export class InstructorService {
const organizationName = authUser.primaryAffiliation?.organizationName?.trim();
if (!organizationName) throw new BadRequestException('Instructor organization is required');
const course = await this.findInstructorCourse(organizationName, instructor.id, input.courseId);
if (!course) throw new NotFoundException('Course not found');
const name = input.name.trim();
if (!name) throw new BadRequestException('Group name is required');
@@ -2584,6 +2577,7 @@ export class InstructorService {
await this.prisma.$transaction(async (tx) => {
const group = await tx.studentGroup.create({
data: {
courseId: course.id,
organizationId,
organizationName,
name,
@@ -2637,7 +2631,9 @@ export class InstructorService {
const organizationName = authUser.primaryAffiliation?.organizationName?.trim();
if (!organizationName) throw new BadRequestException('Instructor organization is required');
const group = await this.findInstructorGroup(groupId, organizationId, organizationName);
const course = await this.findInstructorCourse(organizationName, instructor.id, input.courseId);
if (!course) throw new NotFoundException('Course not found');
const group = await this.findInstructorGroup(groupId, organizationId, organizationName, instructor.id, course.id);
const name = input.name.trim();
if (!name) throw new BadRequestException('Group name is required');
const memberIds = await this.validateActiveStudentIds(
@@ -2665,6 +2661,7 @@ export class InstructorService {
await tx.studentGroup.update({
where: { id: group.id },
data: {
courseId: course.id,
name,
instructorPlatformId: assignedPlatform?.id ?? null,
platformName: assignedPlatform?.displayName ?? null,
@@ -2734,12 +2731,14 @@ export class InstructorService {
}
async deleteGroup(accessToken: string | undefined, groupId: string, courseId?: string | null) {
const { authUser } = await this.getInstructor(accessToken);
const { authUser, instructor } = await this.getInstructor(accessToken);
const organizationId = authUser.primaryAffiliation?.organizationId;
const organizationName = authUser.primaryAffiliation?.organizationName?.trim();
if (!organizationName) throw new BadRequestException('Instructor organization is required');
const group = await this.findInstructorGroup(groupId, organizationId, organizationName);
const course = await this.findInstructorCourse(organizationName, instructor.id, courseId);
if (!course) throw new NotFoundException('Course not found');
const group = await this.findInstructorGroup(groupId, organizationId, organizationName, instructor.id, course.id);
const previousAssignment = await this.getGroupPlatformAssignment(group.id);
const termination = await this.prepareGroupPlatformTermination({
groupId: group.id,
@@ -3004,10 +3003,13 @@ export class InstructorService {
groupId: string,
organizationId: string | undefined,
organizationName: string,
instructorId: string,
courseId: string,
) {
const group = await this.prisma.studentGroup.findFirst({
where: {
id: groupId,
AND: [groupCourseScope(instructorId, courseId)],
OR: [
organizationId ? { organizationId } : undefined,
{ organizationName },