CREATE TABLE "course_environment_profiles" ( "id" UUID NOT NULL DEFAULT gen_random_uuid(), "course_id" UUID NOT NULL, "week" INTEGER NOT NULL, "platform_key" TEXT NOT NULL DEFAULT 'futuredrive-local-pc', "profile_type" TEXT NOT NULL, "name" TEXT NOT NULL, "config" JSONB NOT NULL, "enabled" BOOLEAN NOT NULL DEFAULT true, "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT "course_environment_profiles_pkey" PRIMARY KEY ("id") ); CREATE UNIQUE INDEX "course_environment_profiles_course_id_week_platform_key_key" ON "course_environment_profiles"("course_id", "week", "platform_key"); CREATE INDEX "course_environment_profiles_course_id_week_idx" ON "course_environment_profiles"("course_id", "week"); CREATE INDEX "course_environment_profiles_profile_type_idx" ON "course_environment_profiles"("profile_type"); ALTER TABLE "course_environment_profiles" ADD CONSTRAINT "course_environment_profiles_course_id_fkey" FOREIGN KEY ("course_id") REFERENCES "courses"("id") ON DELETE CASCADE ON UPDATE CASCADE; COMMENT ON TABLE "course_environment_profiles" IS '강좌/주차/플랫폼별 개발환경 실행 프로파일.'; COMMENT ON COLUMN "course_environment_profiles"."config" IS 'Docker runtime, ROS launch, bridge, viewer 실행 설정 JSON.'; INSERT INTO "course_environment_profiles" ( "course_id", "week", "platform_key", "profile_type", "name", "config", "updated_at" ) SELECT course."id", 3, 'futuredrive-local-pc', 'SLAM', 'TurtleBot3 SLAM 로컬 시뮬레이션', '{ "type": "SLAM", "viewer": { "type": "slam", "url": "/slam-viewer/index.html", "healthUrl": "http://localhost:8765/health" }, "runtime": { "containers": [ { "name": "nav2-slam", "image": "nav2-slam:humble", "network": "host", "privileged": true, "env": { "DISPLAY": ":1", "QT_X11_NO_MITSHM": "1", "LIBGL_ALWAYS_SOFTWARE": "1", "TURTLEBOT3_MODEL": "burger" }, "volumes": [ "/tmp/.X11-unix:/tmp/.X11-unix", "/dev/shm:/dev/shm", "~/.gazebo:/root/.gazebo", "~/.cache:/root/.cache" ], "command": "tail -f /dev/null" } ], "steps": [ { "container": "nav2-slam", "mode": "execDetached", "delayMs": 0, "command": "ros2 launch gazebo_ros gzserver.launch.py world:=/opt/ros/humble/share/turtlebot3_gazebo/worlds/turtlebot3_world.world & ros2 launch turtlebot3_gazebo robot_state_publisher.launch.py use_sim_time:=true & sleep 6; ros2 launch turtlebot3_gazebo spawn_turtlebot3.launch.py x_pose:=-2.0 y_pose:=-0.5; wait", "logFile": "/tmp/turtlebot3_gazebo.log" }, { "container": "nav2-slam", "mode": "execDetached", "delayMs": 6000, "command": "ros2 launch slam_toolbox online_async_launch.py use_sim_time:=true", "logFile": "/tmp/slam_toolbox.log" }, { "container": "nav2-slam", "mode": "execDetached", "delayMs": 3000, "command": "ros2 launch nav2_bringup navigation_launch.py use_sim_time:=true autostart:=true", "logFile": "/tmp/nav2.log" } ] }, "bridge": { "name": "slam-ros-bridge", "image": "slam-ros-bridge", "buildContext": "resources/slam-ros-bridge", "port": 8765, "env": { "ODOM_TOPIC": "/odom", "SCAN_TOPIC": "/scan", "MAP_TOPIC": "/map", "CMD_VEL_TOPIC": "/cmd_vel", "NAVIGATE_TO_POSE_ACTION": "/navigate_to_pose" } } }'::jsonb, CURRENT_TIMESTAMP FROM "courses" AS course ON CONFLICT ("course_id", "week", "platform_key") DO UPDATE SET "profile_type" = EXCLUDED."profile_type", "name" = EXCLUDED."name", "config" = EXCLUDED."config", "enabled" = true, "updated_at" = CURRENT_TIMESTAMP;