feat: enable hybrid local and remote module execution

This commit is contained in:
2026-08-06 16:17:41 +09:00
parent e06b853bcb
commit f8ccd8b0a3
18 changed files with 1312 additions and 123 deletions
@@ -0,0 +1,12 @@
ALTER TABLE "student_saved_maps"
ADD COLUMN "runtime_kind" TEXT NOT NULL DEFAULT 'SIMULATION';
UPDATE "student_saved_maps"
SET "runtime_kind" = CASE
WHEN "database_path" IS NOT NULL THEN 'PLATFORM_3D'
WHEN "source_map_key" = 'nav2-platform-slam' THEN 'PLATFORM_2D'
ELSE 'SIMULATION'
END;
CREATE INDEX "student_saved_maps_user_id_course_id_week_runtime_kind_idx"
ON "student_saved_maps"("user_id", "course_id", "week", "runtime_kind");
@@ -0,0 +1,4 @@
ALTER TABLE "student_saved_maps"
ADD COLUMN "yaml_url" TEXT,
ADD COLUMN "pgm_url" TEXT,
ADD COLUMN "database_url" TEXT;
@@ -0,0 +1,10 @@
ALTER TABLE "student_custom_modules"
ADD COLUMN "runtime_target" TEXT NOT NULL DEFAULT 'LOCAL';
DROP INDEX "student_custom_modules_user_id_course_id_week_folder_name_key";
CREATE UNIQUE INDEX "student_custom_modules_user_id_course_id_week_folder_name_runtime_target_key"
ON "student_custom_modules"("user_id", "course_id", "week", "folder_name", "runtime_target");
CREATE INDEX "student_custom_modules_user_id_course_id_week_runtime_target_sort_order_idx"
ON "student_custom_modules"("user_id", "course_id", "week", "runtime_target", "sort_order");
@@ -0,0 +1,64 @@
ALTER TABLE "instructor_curriculum_week_overrides"
ADD COLUMN "is_team_project" BOOLEAN DEFAULT false;
CREATE TABLE "team_studio_repositories" (
"id" UUID NOT NULL,
"group_id" UUID NOT NULL,
"week_id" UUID NOT NULL,
"module_id" UUID NOT NULL,
"repo_owner" TEXT NOT NULL,
"repo_name" TEXT NOT NULL,
"repository_url" TEXT NOT NULL,
"template_repository_url" TEXT NOT NULL,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "team_studio_repositories_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "team_studio_repositories_group_id_module_id_key"
ON "team_studio_repositories"("group_id", "module_id");
CREATE INDEX "team_studio_repositories_week_id_idx"
ON "team_studio_repositories"("week_id");
CREATE INDEX "team_studio_repositories_repo_owner_repo_name_idx"
ON "team_studio_repositories"("repo_owner", "repo_name");
ALTER TABLE "team_studio_repositories"
ADD CONSTRAINT "team_studio_repositories_group_id_fkey"
FOREIGN KEY ("group_id") REFERENCES "student_groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "team_studio_repositories"
ADD CONSTRAINT "team_studio_repositories_module_id_fkey"
FOREIGN KEY ("module_id") REFERENCES "course_week_modules"("id") ON DELETE CASCADE ON UPDATE CASCADE;
CREATE TABLE "team_custom_modules" (
"id" UUID NOT NULL,
"group_id" UUID NOT NULL,
"creator_id" UUID NOT NULL,
"course_id" UUID NOT NULL,
"week" INTEGER NOT NULL,
"sort_order" INTEGER NOT NULL,
"category" TEXT NOT NULL,
"name" TEXT NOT NULL,
"folder_name" TEXT NOT NULL,
"docker_image" TEXT,
"package_name" TEXT NOT NULL,
"launch_file" TEXT NOT NULL,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "team_custom_modules_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "team_custom_modules_group_id_course_id_week_folder_name_key"
ON "team_custom_modules"("group_id", "course_id", "week", "folder_name");
CREATE INDEX "team_custom_modules_group_id_course_id_week_sort_order_idx"
ON "team_custom_modules"("group_id", "course_id", "week", "sort_order");
ALTER TABLE "team_custom_modules"
ADD CONSTRAINT "team_custom_modules_group_id_fkey"
FOREIGN KEY ("group_id") REFERENCES "student_groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "team_custom_modules"
ADD CONSTRAINT "team_custom_modules_creator_id_fkey"
FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "team_custom_modules"
ADD CONSTRAINT "team_custom_modules_course_id_fkey"
FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,45 @@
CREATE TABLE "student_custom_module_repositories" (
"id" UUID NOT NULL,
"user_id" UUID NOT NULL,
"custom_module_id" UUID NOT NULL,
"repo_owner" TEXT NOT NULL,
"repo_name" TEXT NOT NULL,
"repository_url" TEXT NOT NULL,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "student_custom_module_repositories_pkey" PRIMARY KEY ("id")
);
CREATE TABLE "team_custom_module_repositories" (
"id" UUID NOT NULL,
"group_id" UUID NOT NULL,
"custom_module_id" UUID NOT NULL,
"repo_owner" TEXT NOT NULL,
"repo_name" TEXT NOT NULL,
"repository_url" TEXT NOT NULL,
"created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(3) NOT NULL,
CONSTRAINT "team_custom_module_repositories_pkey" PRIMARY KEY ("id")
);
CREATE UNIQUE INDEX "student_custom_module_repositories_custom_module_id_key" ON "student_custom_module_repositories"("custom_module_id");
CREATE INDEX "student_custom_module_repositories_user_id_idx" ON "student_custom_module_repositories"("user_id");
CREATE INDEX "student_custom_module_repositories_repo_owner_repo_name_idx" ON "student_custom_module_repositories"("repo_owner", "repo_name");
CREATE UNIQUE INDEX "team_custom_module_repositories_custom_module_id_key" ON "team_custom_module_repositories"("custom_module_id");
CREATE INDEX "team_custom_module_repositories_group_id_idx" ON "team_custom_module_repositories"("group_id");
CREATE INDEX "team_custom_module_repositories_repo_owner_repo_name_idx" ON "team_custom_module_repositories"("repo_owner", "repo_name");
ALTER TABLE "student_custom_module_repositories"
ADD CONSTRAINT "student_custom_module_repositories_user_id_fkey"
FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "student_custom_module_repositories"
ADD CONSTRAINT "student_custom_module_repositories_custom_module_id_fkey"
FOREIGN KEY ("custom_module_id") REFERENCES "student_custom_modules"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "team_custom_module_repositories"
ADD CONSTRAINT "team_custom_module_repositories_group_id_fkey"
FOREIGN KEY ("group_id") REFERENCES "student_groups"("id") ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE "team_custom_module_repositories"
ADD CONSTRAINT "team_custom_module_repositories_custom_module_id_fkey"
FOREIGN KEY ("custom_module_id") REFERENCES "team_custom_modules"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -0,0 +1,33 @@
-- Align the LMS platform record with the checked-in NAV2-MKMini profile.
-- The speed is the real YHS CAN safety limit, not the higher simulation-only cap.
UPDATE "platforms"
SET
"vehicle_type" = 'MK-mini',
"wheelbase_meters" = 0.600,
"max_speed_meters_per_second" = 0.300,
"max_steering_angle_degrees" = 28.6479
WHERE "platform_key" ILIKE '%mkmini%';
UPDATE "instructor_platforms" AS instance
SET
"vehicle_type" = 'MK-mini',
"wheelbase_meters" = 0.600,
"max_speed_meters_per_second" = 0.300,
"max_steering_angle_degrees" = 28.6479
FROM "platforms" AS platform
WHERE instance."platform_id" = platform."id"
AND platform."platform_key" ILIKE '%mkmini%';
UPDATE "course_week_modules"
SET "prebuilt_artifacts" = jsonb_build_array(
jsonb_build_object(
'type', 'build-install',
'fileName', 'lms-nav2-mkmini-build-install-amd64.tar.zst',
'url', 'https://pub-ca0a1bc004d8409da3329dbb9ac32bcf.r2.dev/studio-artifacts/modules/lms-nav2-mkmini/1.0.0/prebuilt/amd64/lms-nav2-mkmini-build-install-amd64.tar.zst',
'checksumSha256', '8b36d3fda6a81a49da140529d9874e99b734d05e01e12a49e719e2b26f8803d9',
'architecture', 'amd64',
'required', false,
'extractPath', '.'
)
)
WHERE "folder_name" = '[lms-nav2-mkmini]nav2-mkmini_v1.0.0_ws';
+96 -1
View File
@@ -31,8 +31,10 @@ model User {
platformControlSessions PlatformControlSession[]
platformControlQueueEntries PlatformControlQueueEntry[]
studioRepositories StudioRepository[]
customModuleRepositories StudentCustomModuleRepository[]
savedMaps StudentSavedMap[]
customModules StudentCustomModule[]
teamCustomModules TeamCustomModule[] @relation("TeamCustomModuleCreator")
submissionScores SubmissionScore[]
submissionTotalScores SubmissionTotalScore[]
@@ -77,6 +79,9 @@ model StudentGroup {
platformControlSessions PlatformControlSession[]
platformControlQueueEntries PlatformControlQueueEntry[]
curriculumSettings InstructorCurriculumSetting[]
teamStudioRepositories TeamStudioRepository[]
teamCustomModuleRepositories TeamCustomModuleRepository[]
teamCustomModules TeamCustomModule[]
@@unique([organizationName, name])
@@index([organizationId])
@@ -162,6 +167,7 @@ model Course {
weekMaps CourseWeekMap[]
savedMaps StudentSavedMap[]
customModules StudentCustomModule[]
teamCustomModules TeamCustomModule[]
submissionScores SubmissionScore[]
submissionTotalScores SubmissionTotalScore[]
@@ -267,8 +273,12 @@ model StudentSavedMap {
viewerModel String @map("viewer_model")
yamlPath String @map("yaml_path")
pgmPath String @map("pgm_path")
yamlUrl String? @map("yaml_url")
pgmUrl String? @map("pgm_url")
previewPgm String? @map("preview_pgm")
databasePath String? @map("database_path")
databaseUrl String? @map("database_url")
runtimeKind String @default("SIMULATION") @map("runtime_kind")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@ -276,6 +286,7 @@ model StudentSavedMap {
@@unique([userId, courseId, week, yamlPath])
@@index([userId, courseId, week, createdAt])
@@index([userId, courseId, week, runtimeKind])
@@map("student_saved_maps")
}
@@ -291,16 +302,78 @@ model StudentCustomModule {
dockerImage String? @map("docker_image")
packageName String @map("package_name")
launchFile String @map("launch_file")
runtimeTarget String @default("LOCAL") @map("runtime_target")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
repository StudentCustomModuleRepository?
@@unique([userId, courseId, week, folderName])
@@unique([userId, courseId, week, folderName, runtimeTarget])
@@index([userId, courseId, week, sortOrder])
@@index([userId, courseId, week, runtimeTarget, sortOrder])
@@map("student_custom_modules")
}
model TeamCustomModule {
id String @id @default(uuid()) @db.Uuid
groupId String @map("group_id") @db.Uuid
creatorId String @map("creator_id") @db.Uuid
courseId String @map("course_id") @db.Uuid
week Int
sortOrder Int @map("sort_order")
category String
name String
folderName String @map("folder_name")
dockerImage String? @map("docker_image")
packageName String @map("package_name")
launchFile String @map("launch_file")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
group StudentGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
creator User @relation("TeamCustomModuleCreator", fields: [creatorId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
repository TeamCustomModuleRepository?
@@unique([groupId, courseId, week, folderName])
@@index([groupId, courseId, week, sortOrder])
@@map("team_custom_modules")
}
model StudentCustomModuleRepository {
id String @id @default(uuid()) @db.Uuid
userId String @map("user_id") @db.Uuid
customModuleId String @unique @map("custom_module_id") @db.Uuid
repoOwner String @map("repo_owner")
repoName String @map("repo_name")
repositoryUrl String @map("repository_url")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
customModule StudentCustomModule @relation(fields: [customModuleId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([repoOwner, repoName])
@@map("student_custom_module_repositories")
}
model TeamCustomModuleRepository {
id String @id @default(uuid()) @db.Uuid
groupId String @map("group_id") @db.Uuid
customModuleId String @unique @map("custom_module_id") @db.Uuid
repoOwner String @map("repo_owner")
repoName String @map("repo_name")
repositoryUrl String @map("repository_url")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
group StudentGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
customModule TeamCustomModule @relation(fields: [customModuleId], references: [id], onDelete: Cascade)
@@index([groupId])
@@index([repoOwner, repoName])
@@map("team_custom_module_repositories")
}
model InstructorCurriculumSetting {
id String @id @default(uuid()) @db.Uuid
organizationId String? @map("organization_id") @db.Uuid
@@ -333,6 +406,7 @@ model InstructorCurriculumWeekOverride {
overview String?
deliverable String?
requiresSourceSubmission Boolean? @default(true) @map("requires_source_submission")
isTeamProject Boolean? @default(false) @map("is_team_project")
learningSteps Json? @map("learning_steps")
topics Json?
topicDetails Json? @map("topic_details")
@@ -540,6 +614,7 @@ model CourseWeekModule {
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
week CourseWeek @relation(fields: [weekId], references: [id], onDelete: Cascade)
studioRepositories StudioRepository[]
teamStudioRepositories TeamStudioRepository[]
submissionScores SubmissionScore[]
@@unique([weekId, folderName])
@@ -568,6 +643,26 @@ model StudioRepository {
@@map("studio_repositories")
}
model TeamStudioRepository {
id String @id @default(uuid()) @db.Uuid
groupId String @map("group_id") @db.Uuid
weekId String @map("week_id") @db.Uuid
moduleId String @map("module_id") @db.Uuid
repoOwner String @map("repo_owner")
repoName String @map("repo_name")
repositoryUrl String @map("repository_url")
templateRepositoryUrl String @map("template_repository_url")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
group StudentGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
module CourseWeekModule @relation(fields: [moduleId], references: [id], onDelete: Cascade)
@@unique([groupId, moduleId])
@@index([weekId])
@@index([repoOwner, repoName])
@@map("team_studio_repositories")
}
model SubmissionScore {
id String @id @default(uuid()) @db.Uuid
courseId String @map("course_id") @db.Uuid