diff --git a/.github/workflows/prod2025.yml b/.github/workflows/prod2025.yml index ce43f4c..46f5765 100644 --- a/.github/workflows/prod2025.yml +++ b/.github/workflows/prod2025.yml @@ -4,6 +4,7 @@ on: push: branches: - prod-2025 + workflow_dispatch: permissions: contents: read diff --git a/package.json b/package.json index 75f2c78..26fcd62 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,10 @@ "db:init:local": "docker-compose --env-file .local.env -f docker-compose.db.yml up -d", "db:save:local": "dotenv -e .local.env -- yarn prisma migrate dev", "db:save:dev": "dotenv -e .dev.env -- yarn prisma migrate dev", - "deploy:local": "dotenv -e .local.env -- node dist/main.js", - "deploy:dev": "dotenv -e .dev.env -- node dist/main.js", - "deploy:prod": "dotenv -e .prod.env -- node dist/main.js", - "deploy:prod2025": "dotenv -e .prod2025.env -- node dist/main.js", + "deploy:local": "dotenv -e .local.env -- node dist/src/main.js", + "deploy:dev": "dotenv -e .dev.env -- node dist/src/main.js", + "deploy:prod": "dotenv -e .prod.env -- node dist/src/main.js", + "deploy:prod2025": "dotenv -e .prod2025.env -- node dist/src/main.js", "seed:studyroom:local": "dotenv -e .local.env -- npx ts-node ./prisma/seeds/insert-studyrooms.ts", "seed:studyroom:dev": "dotenv -e .dev.env -- npx ts-node ./prisma/seeds/insert-studyrooms.ts", "seed:course:local": "dotenv -e .local.env -- npx ts-node ./prisma/seeds/insert-courses.ts", diff --git a/parser/lectures_2025-1.json b/parser/lectures_2025-1.json new file mode 100644 index 0000000..7862c7c --- /dev/null +++ b/parser/lectures_2025-1.json @@ -0,0 +1 @@ +[{"id": "011614", "school": "대양휴머니티칼리지", "name": "세종인을위한진로설계"}, {"id": "009045", "school": "대양휴머니티칼리지", "name": "창업과기업가정신1"}, {"id": "009067", "school": "대양휴머니티칼리지", "name": "문제해결을위한글쓰기와발표"}, {"id": "009068", "school": "대양휴머니티칼리지", "name": "서양철학:쟁점과토론"}, {"id": "009352", "school": "대양휴머니티칼리지", "name": "사고와표현1"}, {"id": "009353", "school": "대양휴머니티칼리지", "name": "사고와표현2"}, {"id": "009354", "school": "대양휴머니티칼리지", "name": "한국의언어와문화1"}, {"id": "009355", "school": "대양휴머니티칼리지", "name": "한국의언어와문화2"}, {"id": "009356", "school": "대양휴머니티칼리지", "name": "한국전통문화의이해"}, {"id": "009357", "school": "대양휴머니티칼리지", "name": "서양철학의이해"}, {"id": "010352", "school": "대양휴머니티칼리지", "name": "English Listening Practice 1"}, {"id": "010354", "school": "대양휴머니티칼리지", "name": "English Reading Practice 1"}, {"id": "011238", "school": "대양휴머니티칼리지", "name": "우주자연인간"}, {"id": "011304", "school": "대양휴머니티칼리지", "name": "대학영어"}, {"id": "006937", "school": "대양휴머니티칼리지", "name": "한국현대사"}, {"id": "011305", "school": "대양휴머니티칼리지", "name": "동서양의사상과윤리"}, {"id": "011306", "school": "대양휴머니티칼리지", "name": "성서와기독교"}, {"id": "011307", "school": "대양휴머니티칼리지", "name": "세계사"}, {"id": "011308", "school": "대양휴머니티칼리지", "name": "생명과학의이해"}, {"id": "011309", "school": "대양휴머니티칼리지", "name": "수의세계"}, {"id": "011310", "school": "대양휴머니티칼리지", "name": "지구환경과기후변화"}, {"id": "011311", "school": "대양휴머니티칼리지", "name": "현대과학으로의초대"}, {"id": "011312", "school": "대양휴머니티칼리지", "name": "경영학"}, {"id": "011313", "school": "대양휴머니티칼리지", "name": "경제학"}, {"id": "011314", "school": "대양휴머니티칼리지", "name": "미디어빅뱅과방송"}, {"id": "011315", "school": "대양휴머니티칼리지", "name": "현대사회와법"}, {"id": "011316", "school": "대양휴머니티칼리지", "name": "융합예술의이해"}, {"id": "011317", "school": "대양휴머니티칼리지", "name": "컴퓨터게임과메타버스"}, {"id": "011318", "school": "대양휴머니티칼리지", "name": "한국의문화와한류"}, {"id": "011319", "school": "대양휴머니티칼리지", "name": "현대예술의이해"}, {"id": "001357", "school": "대양휴머니티칼리지", "name": "미적분학1"}, {"id": "001362", "school": "대양휴머니티칼리지", "name": "미적분학2"}, {"id": "002638", "school": "대양휴머니티칼리지", "name": "일반물리학1"}, {"id": "002705", "school": "대양휴머니티칼리지", "name": "일반화학1"}, {"id": "011297", "school": "대양휴머니티칼리지", "name": "컴퓨터사고기반기초코딩"}, {"id": "011298", "school": "대양휴머니티칼리지", "name": "SW기초코딩"}, {"id": "011300", "school": "대양휴머니티칼리지", "name": "고급프로그래밍활용"}, {"id": "011526", "school": "대양휴머니티칼리지", "name": "사회과학수학"}, {"id": "004489", "school": "대양휴머니티칼리지", "name": "한국의전통과문화"}, {"id": "004558", "school": "대양휴머니티칼리지", "name": "채플1"}, {"id": "004777", "school": "대양휴머니티칼리지", "name": "채플3"}, {"id": "006270", "school": "대양휴머니티칼리지", "name": "동양의철학사상"}, {"id": "006279", "school": "대양휴머니티칼리지", "name": "정보사회의사이버윤리"}, {"id": "007210", "school": "대양휴머니티칼리지", "name": "역사와한국의영토"}, {"id": "007343", "school": "대양휴머니티칼리지", "name": "신화와예술"}, {"id": "007731", "school": "대양휴머니티칼리지", "name": "철학과인간이해"}, {"id": "008882", "school": "대양휴머니티칼리지", "name": "동서양의윤리"}, {"id": "008913", "school": "대양휴머니티칼리지", "name": "동양고전강독1"}, {"id": "008914", "school": "대양휴머니티칼리지", "name": "서양고전강독1"}, {"id": "008936", "school": "대양휴머니티칼리지", "name": "정치경제학의이해"}, {"id": "009031", "school": "대양휴머니티칼리지", "name": "한국현대사의이해"}, {"id": "009033", "school": "대양휴머니티칼리지", "name": "독서토론"}, {"id": "009057", "school": "대양휴머니티칼리지", "name": "서양고전강독3"}, {"id": "009489", "school": "대양휴머니티칼리지", "name": "세계사:인간과문명"}, {"id": "009738", "school": "대양휴머니티칼리지", "name": "서양종교의이해"}, {"id": "009740", "school": "대양휴머니티칼리지", "name": "동양의철학사상-노장과불교"}, {"id": "009775", "school": "대양휴머니티칼리지", "name": "동양종교의이해"}, {"id": "009780", "school": "대양휴머니티칼리지", "name": "고전특강"}, {"id": "009938", "school": "대양휴머니티칼리지", "name": "동서양고전문학강독"}, {"id": "010357", "school": "대양휴머니티칼리지", "name": "수요집현강좌"}, {"id": "010359", "school": "대양휴머니티칼리지", "name": "SHP고전강좌:Reading Intellectus 1"}, {"id": "010530", "school": "대양휴머니티칼리지", "name": "SHP고전강좌:Writing Intellectus 1"}, {"id": "010652", "school": "대양휴머니티칼리지", "name": "SHP고전강좌:Building Intellectus 1"}, {"id": "011878", "school": "대양휴머니티칼리지", "name": "역사속의기독교"}, {"id": "002074", "school": "대양휴머니티칼리지", "name": "여성학"}, {"id": "005991", "school": "대양휴머니티칼리지", "name": "스피치커뮤니케이션"}, {"id": "006422", "school": "대양휴머니티칼리지", "name": "성과문화"}, {"id": "007345", "school": "대양휴머니티칼리지", "name": "미술심리치료"}, {"id": "007565", "school": "대양휴머니티칼리지", "name": "우리차문화의이해"}, {"id": "007779", "school": "대양휴머니티칼리지", "name": "현대인의정신건강과자기발견"}, {"id": "008300", "school": "대양휴머니티칼리지", "name": "쟁점으로이해하는북한사회"}, {"id": "008884", "school": "대양휴머니티칼리지", "name": "국가와정체성"}, {"id": "008887", "school": "대양휴머니티칼리지", "name": "언어와문명"}, {"id": "009251", "school": "대양휴머니티칼리지", "name": "중급미술심리치료"}, {"id": "009252", "school": "대양휴머니티칼리지", "name": "영상스토리텔링"}, {"id": "009739", "school": "대양휴머니티칼리지", "name": "복지국가의이해"}, {"id": "009778", "school": "대양휴머니티칼리지", "name": "경영학의이해"}, {"id": "009785", "school": "대양휴머니티칼리지", "name": "공간마케팅"}, {"id": "010356", "school": "대양휴머니티칼리지", "name": "대학생을위한실용금융"}, {"id": "010358", "school": "대양휴머니티칼리지", "name": "행복한가정만들기"}, {"id": "010407", "school": "대양휴머니티칼리지", "name": "시사로보는경제이야기"}, {"id": "010412", "school": "대양휴머니티칼리지", "name": "K-MOOC:콘텐츠산업의비즈니스전략"}, {"id": "011257", "school": "대양휴머니티칼리지", "name": "시사미디어한국어"}, {"id": "011410", "school": "대양휴머니티칼리지", "name": "민속담화를통한다문화의이해"}, {"id": "003129", "school": "대양휴머니티칼리지", "name": "지구과학개론"}, {"id": "004763", "school": "대양휴머니티칼리지", "name": "과학사"}, {"id": "004921", "school": "대양휴머니티칼리지", "name": "미생물의세계"}, {"id": "005106", "school": "대양휴머니티칼리지", "name": "생명의미시적세계"}, {"id": "005541", "school": "대양휴머니티칼리지", "name": "식물의세계"}, {"id": "008885", "school": "대양휴머니티칼리지", "name": "과학기술과사회"}, {"id": "008890", "school": "대양휴머니티칼리지", "name": "천문학의세계"}, {"id": "008966", "school": "대양휴머니티칼리지", "name": "꽃의세계"}, {"id": "009936", "school": "대양휴머니티칼리지", "name": "Technical Writing기초"}, {"id": "010361", "school": "대양휴머니티칼리지", "name": "먹고즐기는자연"}, {"id": "010363", "school": "대양휴머니티칼리지", "name": "곤충의새로운가치"}, {"id": "010542", "school": "대양휴머니티칼리지", "name": "K-MOOC:일반인을위한물리코딩"}, {"id": "010543", "school": "대양휴머니티칼리지", "name": "K-MOOC:코딩과스토리텔링"}, {"id": "010668", "school": "대양휴머니티칼리지", "name": "자연지리학의이해"}, {"id": "011416", "school": "대양휴머니티칼리지", "name": "생활속의물리"}, {"id": "011616", "school": "대양휴머니티칼리지", "name": "향과학의이해"}, {"id": "011617", "school": "대양휴머니티칼리지", "name": "화학의세계"}, {"id": "011845", "school": "대양휴머니티칼리지", "name": "공학기술사"}, {"id": "012059", "school": "대양휴머니티칼리지", "name": "생활속의수학"}, {"id": "012061", "school": "대양휴머니티칼리지", "name": "물리학입문"}, {"id": "012062", "school": "대양휴머니티칼리지", "name": "통계학입문"}, {"id": "012063", "school": "대양휴머니티칼리지", "name": "미적분학입문"}, {"id": "006214", "school": "대양휴머니티칼리지", "name": "일본어1"}, {"id": "006844", "school": "대양휴머니티칼리지", "name": "Intensive English"}, {"id": "007207", "school": "대양휴머니티칼리지", "name": "기초중국어1"}, {"id": "007208", "school": "대양휴머니티칼리지", "name": "기초중국어2"}, {"id": "007214", "school": "대양휴머니티칼리지", "name": "글로벌잉글리쉬1"}, {"id": "007321", "school": "대양휴머니티칼리지", "name": "생활일본어"}, {"id": "007338", "school": "대양휴머니티칼리지", "name": "중국의정치경제와사회"}, {"id": "007401", "school": "대양휴머니티칼리지", "name": "영어발음연습"}, {"id": "007503", "school": "대양휴머니티칼리지", "name": "영어읽기연습"}, {"id": "007504", "school": "대양휴머니티칼리지", "name": "영어듣기연습"}, {"id": "008894", "school": "대양휴머니티칼리지", "name": "세계화와글로벌거버넌스"}, {"id": "008895", "school": "대양휴머니티칼리지", "name": "EU의정치경제와사회"}, {"id": "008898", "school": "대양휴머니티칼리지", "name": "영어로하는프리젠테이션"}, {"id": "009048", "school": "대양휴머니티칼리지", "name": "글로벌잉글리쉬1(심화)"}, {"id": "009073", "school": "대양휴머니티칼리지", "name": "English Speaking Strategies(TS) 1"}, {"id": "009259", "school": "대양휴머니티칼리지", "name": "K-Pop Music 1"}, {"id": "009265", "school": "대양휴머니티칼리지", "name": "K-Pop Dance 1"}, {"id": "009345", "school": "대양휴머니티칼리지", "name": "English Speaking Strategies(O) 1"}, {"id": "009981", "school": "대양휴머니티칼리지", "name": "English Listening"}, {"id": "009982", "school": "대양휴머니티칼리지", "name": "English Reading"}, {"id": "010353", "school": "대양휴머니티칼리지", "name": "English Listening Practice 2"}, {"id": "010531", "school": "대양휴머니티칼리지", "name": "세계시민정신"}, {"id": "010532", "school": "대양휴머니티칼리지", "name": "아시아공동체의이해"}, {"id": "011413", "school": "대양휴머니티칼리지", "name": "대학한국어글쓰기"}, {"id": "011613", "school": "대양휴머니티칼리지", "name": "English Speaking Strategies(IS) 1"}, {"id": "011615", "school": "대양휴머니티칼리지", "name": "지속가능발전목표(SDGs)의이해와실천"}, {"id": "005565", "school": "대양휴머니티칼리지", "name": "스쿼시"}, {"id": "006425", "school": "대양휴머니티칼리지", "name": "스포츠와대중매체"}, {"id": "006709", "school": "대양휴머니티칼리지", "name": "운동과다이어트"}, {"id": "006711", "school": "대양휴머니티칼리지", "name": "건강과삶"}, {"id": "007369", "school": "대양휴머니티칼리지", "name": "발레의이해와감상"}, {"id": "009061", "school": "대양휴머니티칼리지", "name": "웰빙레저스포츠"}, {"id": "009453", "school": "대양휴머니티칼리지", "name": "보컬트레이닝"}, {"id": "009937", "school": "대양휴머니티칼리지", "name": "공간과인간:인문,예술,공학의융합"}, {"id": "010666", "school": "대양휴머니티칼리지", "name": "세종인을위한오케스트라첫걸음"}, {"id": "011093", "school": "대양휴머니티칼리지", "name": "골프"}, {"id": "011324", "school": "대양휴머니티칼리지", "name": "K-MOOC:인공지능콘텐츠아트프로듀싱"}, {"id": "007221", "school": "대양휴머니티칼리지", "name": "진로설정과자기계발"}, {"id": "008364", "school": "대양휴머니티칼리지", "name": "세종사회봉사1"}, {"id": "008365", "school": "대양휴머니티칼리지", "name": "세종사회봉사2"}, {"id": "009035", "school": "대양휴머니티칼리지", "name": "창업의이론과실제"}, {"id": "009046", "school": "대양휴머니티칼리지", "name": "글로벌잉글리쉬1(진로탐색)"}, {"id": "010651", "school": "대양휴머니티칼리지", "name": "진로오딧세이"}, {"id": "012056", "school": "대양휴머니티칼리지", "name": "새잎창의세미나"}, {"id": "012072", "school": "대양휴머니티칼리지", "name": "애지헌채플"}, {"id": "501321", "school": "대양휴머니티칼리지", "name": "이민정책론"}, {"id": "501328", "school": "대양휴머니티칼리지", "name": "알기쉬운코딩"}, {"id": "501352", "school": "대양휴머니티칼리지", "name": "인류의문명과무기"}, {"id": "501356", "school": "대양휴머니티칼리지", "name": "그림속오페라"}, {"id": "501358", "school": "대양휴머니티칼리지", "name": "진짜뉴스를찾아라!미디어·AI리터러시"}, {"id": "501361", "school": "대양휴머니티칼리지", "name": "셀프코칭과리더십"}, {"id": "010418", "school": "대양휴머니티칼리지", "name": "자기주도창의전공Ⅰ"}, {"id": "010419", "school": "대양휴머니티칼리지", "name": "자기주도창의전공Ⅱ"}, {"id": "010420", "school": "대양휴머니티칼리지", "name": "자기주도창의전공Ⅲ"}, {"id": "010421", "school": "대양휴머니티칼리지", "name": "자기주도창의전공Ⅳ"}, {"id": "000486", "school": "대양휴머니티칼리지", "name": "교육사회"}, {"id": "007849", "school": "대양휴머니티칼리지", "name": "교직소양을위한독서강좌1"}, {"id": "008334", "school": "대양휴머니티칼리지", "name": "교육봉사활동1"}, {"id": "008336", "school": "대양휴머니티칼리지", "name": "교육봉사활동3"}, {"id": "012070", "school": "대양휴머니티칼리지", "name": "디지털교육"}, {"id": "011320", "school": "대양휴머니티칼리지", "name": "인공지능과빅데이터"}, {"id": "011322", "school": "대양휴머니티칼리지", "name": "고급인공지능활용"}, {"id": "007082", "school": "대양휴머니티칼리지", "name": "취업과진로"}, {"id": "000525", "school": "대양휴머니티칼리지", "name": "교육학개론"}, {"id": "011183", "school": "대양휴머니티칼리지", "name": "취창업과진로설계"}, {"id": "003353", "school": "대양휴머니티칼리지", "name": "통계학개론"}, {"id": "000464", "school": "대양휴머니티칼리지", "name": "교육과정"}, {"id": "000520", "school": "대양휴머니티칼리지", "name": "교육평가"}, {"id": "008283", "school": "대양휴머니티칼리지", "name": "교직실무"}, {"id": "010653", "school": "대양휴머니티칼리지", "name": "안보학"}, {"id": "010083", "school": "대양휴머니티칼리지", "name": "취업전략및사회진출"}, {"id": "008276", "school": "대양휴머니티칼리지", "name": "학교현장실습"}, {"id": "008910", "school": "대양휴머니티칼리지", "name": "생활지도및상담"}, {"id": "010655", "school": "대양휴머니티칼리지", "name": "조직리더십"}, {"id": "004559", "school": "인문과학대학", "name": "한국문학의이해"}, {"id": "010434", "school": "인문과학대학", "name": "의사소통교육론"}, {"id": "000588", "school": "인문과학대학", "name": "국어문법론"}, {"id": "008355", "school": "인문과학대학", "name": "한국한문학의이해"}, {"id": "004564", "school": "인문과학대학", "name": "한국현대소설론"}, {"id": "008353", "school": "인문과학대학", "name": "한국문학의장르론"}, {"id": "005329", "school": "인문과학대학", "name": "국어교육론"}, {"id": "000596", "school": "인문과학대학", "name": "국어의미론"}, {"id": "007832", "school": "인문과학대학", "name": "국어교과교육론"}, {"id": "008363", "school": "인문과학대학", "name": "시조가사론"}, {"id": "008370", "school": "인문과학대학", "name": "독서교육론"}, {"id": "008371", "school": "인문과학대학", "name": "한국문학과문화콘텐츠"}, {"id": "010111", "school": "인문과학대학", "name": "졸업연구및진로1"}, {"id": "008416", "school": "인문과학대학", "name": "일본사회의이해"}, {"id": "009495", "school": "인문과학대학", "name": "중국어입문"}, {"id": "011487", "school": "인문과학대학", "name": "언어이야기"}, {"id": "011639", "school": "인문과학대학", "name": "초급일본어1"}, {"id": "011745", "school": "인문과학대학", "name": "시장경제의원리"}, {"id": "011778", "school": "인문과학대학", "name": "언어학과디지털휴머니티"}, {"id": "005840", "school": "인문과학대학", "name": "영어속의논리"}, {"id": "011783", "school": "인문과학대학", "name": "영어문법의이해와활용"}, {"id": "007651", "school": "인문과학대학", "name": "영어듣기와말하기"}, {"id": "008373", "school": "인문과학대학", "name": "영미작가이야기"}, {"id": "007929", "school": "인문과학대학", "name": "영미소설의이해"}, {"id": "007819", "school": "인문과학대학", "name": "영국사회와문학"}, {"id": "007820", "school": "인문과학대학", "name": "영어음성음운론"}, {"id": "007833", "school": "인문과학대학", "name": "영어교과교육론"}, {"id": "008374", "school": "인문과학대학", "name": "문학과문화"}, {"id": "007933", "school": "인문과학대학", "name": "현대영어학의흐름"}, {"id": "010326", "school": "인문과학대학", "name": "언어과학"}, {"id": "011946", "school": "인문과학대학", "name": "디지털일본학과AI리터러시"}, {"id": "007909", "school": "인문과학대학", "name": "일본어문법"}, {"id": "010373", "school": "인문과학대학", "name": "일본어작문"}, {"id": "011638", "school": "인문과학대학", "name": "중급일본어"}, {"id": "005068", "school": "인문과학대학", "name": "일본의무대예술"}, {"id": "010379", "school": "인문과학대학", "name": "일본어학개론"}, {"id": "006408", "school": "인문과학대학", "name": "일본어능력시험"}, {"id": "008413", "school": "인문과학대학", "name": "현대일본과국제사회"}, {"id": "009100", "school": "인문과학대학", "name": "일본사상사"}, {"id": "009101", "school": "인문과학대학", "name": "일본언어문화론"}, {"id": "010387", "school": "인문과학대학", "name": "거시경제의이해"}, {"id": "010517", "school": "인문과학대학", "name": "동아시아근현대사"}, {"id": "007689", "school": "인문과학대학", "name": "중급중국어1"}, {"id": "009497", "school": "인문과학대학", "name": "중급중국어연습1"}, {"id": "008758", "school": "인문과학대학", "name": "중국어능력시험"}, {"id": "010518", "school": "인문과학대학", "name": "중한독해"}, {"id": "007749", "school": "인문과학대학", "name": "중국경제론"}, {"id": "008751", "school": "인문과학대학", "name": "중국정치사회론"}, {"id": "010519", "school": "인문과학대학", "name": "중한회화연습1"}, {"id": "011897", "school": "인문과학대학", "name": "중국의대외관계에대한머신러닝분석"}, {"id": "007964", "school": "인문과학대학", "name": "실용중국어작문"}, {"id": "010515", "school": "인문과학대학", "name": "한류와문화산업"}, {"id": "010524", "school": "인문과학대학", "name": "한중기업경영론"}, {"id": "004445", "school": "인문과학대학", "name": "인류의선사문화"}, {"id": "011672", "school": "인문과학대학", "name": "역사와역사콘텐츠의이해"}, {"id": "010253", "school": "인문과학대학", "name": "역사학개론"}, {"id": "001646", "school": "인문과학대학", "name": "서양고대사"}, {"id": "010252", "school": "인문과학대학", "name": "동아시아고대사"}, {"id": "010804", "school": "인문과학대학", "name": "서양근세문화사"}, {"id": "003518", "school": "인문과학대학", "name": "한국근세사"}, {"id": "007835", "school": "인문과학대학", "name": "역사교과교육론"}, {"id": "008321", "school": "인문과학대학", "name": "역사논리및논술"}, {"id": "010255", "school": "인문과학대학", "name": "동아시아근세사"}, {"id": "011900", "school": "인문과학대학", "name": "AI디지털기반역사문화자원큐레이션"}, {"id": "007746", "school": "인문과학대학", "name": "쟁점동양사"}, {"id": "008418", "school": "인문과학대학", "name": "쟁점서양사"}, {"id": "010258", "school": "인문과학대학", "name": "쟁점한국사"}, {"id": "008734", "school": "인문과학대학", "name": "교육학의탐구"}, {"id": "010808", "school": "인문과학대학", "name": "대학의이해"}, {"id": "005071", "school": "인문과학대학", "name": "교육철학의이해"}, {"id": "008737", "school": "인문과학대학", "name": "교육심리의이론과실제"}, {"id": "000451", "school": "인문과학대학", "name": "교수학습이론"}, {"id": "003402", "school": "인문과학대학", "name": "평생교육론"}, {"id": "007251", "school": "인문과학대학", "name": "인적자원개발론"}, {"id": "007813", "school": "인문과학대학", "name": "원격교육론"}, {"id": "007836", "school": "인문과학대학", "name": "교육학교과교육론"}, {"id": "010548", "school": "인문과학대학", "name": "진로상담의이해와실제"}, {"id": "006689", "school": "인문과학대학", "name": "학생상담의이론과실제"}, {"id": "007395", "school": "인문과학대학", "name": "최신학습설계이론"}, {"id": "007814", "school": "인문과학대학", "name": "교육평가론"}, {"id": "007856", "school": "인문과학대학", "name": "평생교육실습"}, {"id": "011679", "school": "인문과학대학", "name": "한국어회화연습"}, {"id": "011681", "school": "인문과학대학", "name": "한국어청취연습"}, {"id": "011688", "school": "인문과학대학", "name": "한국어학입문"}, {"id": "011689", "school": "인문과학대학", "name": "한국학입문"}, {"id": "011690", "school": "인문과학대학", "name": "한국어발음연습"}, {"id": "011746", "school": "인문과학대학", "name": "동아시아근현대사"}, {"id": "011747", "school": "인문과학대학", "name": "거시경제의이해"}, {"id": "011715", "school": "인문과학대학", "name": "Professional English"}, {"id": "011716", "school": "인문과학대학", "name": "Public Speech"}, {"id": "011717", "school": "인문과학대학", "name": "Comparative Culture"}, {"id": "011723", "school": "인문과학대학", "name": "Introduction to International Relations"}, {"id": "011724", "school": "인문과학대학", "name": "Understanding Globalization"}, {"id": "011643", "school": "사회과학대학", "name": "융합사회의정부와행정"}, {"id": "002527", "school": "사회과학대학", "name": "인사행정론"}, {"id": "009908", "school": "사회과학대학", "name": "조직론1"}, {"id": "003147", "school": "사회과학대학", "name": "지방행정론"}, {"id": "011841", "school": "사회과학대학", "name": "행정통계분석(기초)"}, {"id": "003000", "school": "사회과학대학", "name": "정책론"}, {"id": "000922", "school": "사회과학대학", "name": "도시행정론"}, {"id": "007596", "school": "사회과학대학", "name": "전자정부론"}, {"id": "007837", "school": "사회과학대학", "name": "일반사회교과교육론"}, {"id": "011644", "school": "사회과학대학", "name": "AI시대의행정철학과윤리"}, {"id": "000288", "school": "사회과학대학", "name": "공기업론"}, {"id": "007183", "school": "사회과학대학", "name": "공법연습"}, {"id": "008616", "school": "사회과학대학", "name": "정책분석평가론"}, {"id": "011840", "school": "사회과학대학", "name": "복지정책론"}, {"id": "004208", "school": "사회과학대학", "name": "인간커뮤니케이션"}, {"id": "004659", "school": "사회과학대학", "name": "커뮤니케이션조사방법론"}, {"id": "006691", "school": "사회과학대학", "name": "커뮤니케이션이론"}, {"id": "007396", "school": "사회과학대학", "name": "정보리터러시"}, {"id": "008427", "school": "사회과학대학", "name": "광고매체기획"}, {"id": "010261", "school": "사회과학대학", "name": "커뮤니케이션통계실무"}, {"id": "007404", "school": "사회과학대학", "name": "디지털미디어와사회"}, {"id": "007407", "school": "사회과학대학", "name": "대중문화론"}, {"id": "007519", "school": "사회과학대학", "name": "카피라이팅"}, {"id": "010470", "school": "사회과학대학", "name": "디지털저널리즘"}, {"id": "010471", "school": "사회과학대학", "name": "PR론"}, {"id": "011248", "school": "사회과학대학", "name": "스타인플루언서마케팅"}, {"id": "007408", "school": "사회과학대학", "name": "미디어법제"}, {"id": "010476", "school": "사회과학대학", "name": "IMC전략과소비자"}, {"id": "010477", "school": "사회과학대학", "name": "영상제작워크샵"}, {"id": "008802", "school": "사회과학대학", "name": "법학입문"}, {"id": "009600", "school": "사회과학대학", "name": "헌법1"}, {"id": "009602", "school": "사회과학대학", "name": "민법총칙"}, {"id": "009608", "school": "사회과학대학", "name": "형법총론"}, {"id": "008711", "school": "사회과학대학", "name": "상행위법"}, {"id": "009604", "school": "사회과학대학", "name": "채권총론"}, {"id": "000137", "school": "경영경제대학", "name": "경영학원론"}, {"id": "000398", "school": "경영경제대학", "name": "관리회계"}, {"id": "002869", "school": "경영경제대학", "name": "재무관리"}, {"id": "004008", "school": "경영경제대학", "name": "회계원리1"}, {"id": "004076", "school": "경영경제대학", "name": "마케팅관리"}, {"id": "005155", "school": "경영경제대학", "name": "경영통계학"}, {"id": "004010", "school": "경영경제대학", "name": "회계원리2"}, {"id": "009253", "school": "경영경제대학", "name": "경영과학"}, {"id": "009277", "school": "경영경제대학", "name": "비즈니스커뮤니케이션1"}, {"id": "002340", "school": "경영경제대학", "name": "원가회계"}, {"id": "006799", "school": "경영경제대학", "name": "보험과리스크"}, {"id": "006858", "school": "경영경제대학", "name": "서비스마케팅"}, {"id": "007186", "school": "경영경제대학", "name": "인적자원관리"}, {"id": "007187", "school": "경영경제대학", "name": "조직관리"}, {"id": "007253", "school": "경영경제대학", "name": "기업윤리와사회적책임"}, {"id": "007436", "school": "경영경제대학", "name": "마케팅커뮤니케이션"}, {"id": "007986", "school": "경영경제대학", "name": "비지니스인텔리전스"}, {"id": "007991", "school": "경영경제대학", "name": "서비스운영관리"}, {"id": "008437", "school": "경영경제대학", "name": "채권분석"}, {"id": "009255", "school": "경영경제대학", "name": "경영데이터관리"}, {"id": "009872", "school": "경영경제대학", "name": "중급회계1"}, {"id": "010391", "school": "경영경제대학", "name": "상업교과교육론"}, {"id": "010495", "school": "경영경제대학", "name": "상업교수법"}, {"id": "010615", "school": "경영경제대학", "name": "마케팅애널리틱스"}, {"id": "011386", "school": "경영경제대학", "name": "ESG경영"}, {"id": "011935", "school": "경영경제대학", "name": "회계AI애널리틱스"}, {"id": "003993", "school": "경영경제대학", "name": "회계감사"}, {"id": "006698", "school": "경영경제대학", "name": "공급사슬관리"}, {"id": "008486", "school": "경영경제대학", "name": "리더십"}, {"id": "009258", "school": "경영경제대학", "name": "브랜드관리"}, {"id": "010486", "school": "경영경제대학", "name": "금융위험관리"}, {"id": "010492", "school": "경영경제대학", "name": "비즈니스애널리틱스"}, {"id": "000200", "school": "경영경제대학", "name": "경제학원론"}, {"id": "001342", "school": "경영경제대학", "name": "미시경제학"}, {"id": "007768", "school": "경영경제대학", "name": "경제수학"}, {"id": "008419", "school": "경영경제대학", "name": "경제사"}, {"id": "000209", "school": "경영경제대학", "name": "계량경제학"}, {"id": "000659", "school": "경영경제대학", "name": "국제무역론"}, {"id": "003498", "school": "경영경제대학", "name": "한국경제론"}, {"id": "003954", "school": "경영경제대학", "name": "화폐금융론"}, {"id": "007699", "school": "경영경제대학", "name": "공공경제학"}, {"id": "000194", "school": "경영경제대학", "name": "경제학세미나"}, {"id": "000830", "school": "경영경제대학", "name": "노동경제학"}, {"id": "007379", "school": "경영경제대학", "name": "문화경제학"}, {"id": "011618", "school": "호텔관광대학", "name": "Hospitality경영원론"}, {"id": "003855", "school": "호텔관광대학", "name": "호텔경영론"}, {"id": "003998", "school": "호텔관광대학", "name": "회계원리"}, {"id": "009614", "school": "호텔관광대학", "name": "호텔관광마케팅"}, {"id": "000362", "school": "호텔관광대학", "name": "관광자원론"}, {"id": "009888", "school": "호텔관광대학", "name": "축제경영연구"}, {"id": "011607", "school": "호텔관광대학", "name": "AI와호스피탈리티산업"}, {"id": "011609", "school": "호텔관광대학", "name": "호텔관광조직행동론"}, {"id": "004074", "school": "호텔관광대학", "name": "호텔관광재무관리"}, {"id": "007439", "school": "호텔관광대학", "name": "여가공간계획론"}, {"id": "007509", "school": "호텔관광대학", "name": "문화관광경영론"}, {"id": "007839", "school": "호텔관광대학", "name": "관광교과교육론"}, {"id": "009632", "school": "호텔관광대학", "name": "호텔관광e-business전략"}, {"id": "009772", "school": "호텔관광대학", "name": "MICE산업론"}, {"id": "010825", "school": "호텔관광대학", "name": "HTM 스마트의사결정체계"}, {"id": "009901", "school": "호텔관광대학", "name": "호텔관광연구방법론"}, {"id": "009904", "school": "호텔관광대학", "name": "호텔관광사례연구세미나"}, {"id": "009905", "school": "호텔관광대학", "name": "호텔관광기업가정신과리더쉽"}, {"id": "010183", "school": "호텔관광대학", "name": "호텔관광의사결정방법론"}, {"id": "010184", "school": "호텔관광대학", "name": "호텔관광데이터분석"}, {"id": "010821", "school": "호텔관광대학", "name": "호텔관광가격전략"}, {"id": "004469", "school": "호텔관광대학", "name": "조리원리"}, {"id": "007385", "school": "호텔관광대학", "name": "외식산업경영론"}, {"id": "008453", "school": "호텔관광대학", "name": "기초한국조리실습"}, {"id": "011483", "school": "호텔관광대학", "name": "F&B영양과건강"}, {"id": "011481", "school": "호텔관광대학", "name": "외식서비스오퍼레이션"}, {"id": "007312", "school": "호텔관광대학", "name": "외국조리실습"}, {"id": "009515", "school": "호텔관광대학", "name": "외식경영통계"}, {"id": "009765", "school": "호텔관광대학", "name": "바리스타및음료실습2"}, {"id": "011479", "school": "호텔관광대학", "name": "주류개론및응용"}, {"id": "010684", "school": "호텔관광대학", "name": "SFM 외식데이터분석"}, {"id": "006463", "school": "호텔관광대학", "name": "레스토랑경영론"}, {"id": "010829", "school": "호텔관광대학", "name": "외식경영과학"}, {"id": "009113", "school": "호텔관광대학", "name": "경영학원론"}, {"id": "009114", "school": "호텔관광대학", "name": "마케팅원론"}, {"id": "009351", "school": "호텔관광대학", "name": "관광산업의이해"}, {"id": "009389", "school": "호텔관광대학", "name": "호텔외식경영실무1"}, {"id": "003093", "school": "호텔관광대학", "name": "주류학"}, {"id": "007445", "school": "호텔관광대학", "name": "식음료원가관리"}, {"id": "009397", "school": "호텔관광대학", "name": "호텔외식경영실무3"}, {"id": "011457", "school": "호텔관광대학", "name": "HRT디지털마케팅"}, {"id": "011823", "school": "호텔관광대학", "name": "온라인여행사경영론(OTA)"}, {"id": "009118", "school": "호텔관광대학", "name": "HRT프랜차이즈시스템및경영론"}, {"id": "009127", "school": "호텔관광대학", "name": "HRT소비자행동"}, {"id": "009399", "school": "호텔관광대학", "name": "호텔외식경영실무5"}, {"id": "009401", "school": "호텔관광대학", "name": "호텔외식경영실무7"}, {"id": "010192", "school": "호텔관광대학", "name": "음식관광기획론"}, {"id": "010856", "school": "호텔관광대학", "name": "호텔외식조리경영실무1"}, {"id": "011811", "school": "호텔관광대학", "name": "경영학이해"}, {"id": "010194", "school": "호텔관광대학", "name": "호텔연회기획과케이터링경영"}, {"id": "010554", "school": "호텔관광대학", "name": "고객커뮤니케이션전략"}, {"id": "010858", "school": "호텔관광대학", "name": "호텔외식조리경영실무3"}, {"id": "011847", "school": "호텔관광대학", "name": "식음료영양헬스케어"}, {"id": "009393", "school": "호텔관광대학", "name": "호텔외식마케팅"}, {"id": "009406", "school": "호텔관광대학", "name": "호텔외식인적자원관리"}, {"id": "010161", "school": "호텔관광대학", "name": "식품관련법규"}, {"id": "010860", "school": "호텔관광대학", "name": "호텔외식조리경영실무5"}, {"id": "009405", "school": "호텔관광대학", "name": "호텔외식마케팅조사론"}, {"id": "009408", "school": "호텔관광대학", "name": "호텔외식컨설팅"}, {"id": "010862", "school": "호텔관광대학", "name": "호텔외식조리경영실무7"}, {"id": "001938", "school": "호텔관광대학", "name": "식품재료학"}, {"id": "010837", "school": "호텔관광대학", "name": "조리기초실습"}, {"id": "011791", "school": "호텔관광대학", "name": "식품안전관리시스템(FSMS)"}, {"id": "501229", "school": "호텔관광대학", "name": "세계음식문화사"}, {"id": "011536", "school": "호텔관광대학", "name": "식재료구매유통관리론"}, {"id": "501335", "school": "호텔관광대학", "name": "고객커뮤니케이션전략"}, {"id": "010844", "school": "호텔관광대학", "name": "K-Food&세계요리실습"}, {"id": "010875", "school": "호텔관광대학", "name": "메뉴기획과푸드플레이팅"}, {"id": "009387", "school": "호텔관광대학", "name": "호텔외식원가관리"}, {"id": "010869", "school": "호텔관광대학", "name": "카페베이커리실습"}, {"id": "011631", "school": "자연과학대학", "name": "기초통계학"}, {"id": "006646", "school": "자연과학대학", "name": "해석학개론1"}, {"id": "007645", "school": "자연과학대학", "name": "선형대수학1"}, {"id": "011464", "school": "자연과학대학", "name": "고급통계학"}, {"id": "003064", "school": "자연과학대학", "name": "조합론"}, {"id": "004299", "school": "자연과학대학", "name": "현대대수학1"}, {"id": "002998", "school": "자연과학대학", "name": "정수론"}, {"id": "004029", "school": "자연과학대학", "name": "회귀분석1"}, {"id": "004081", "school": "자연과학대학", "name": "전산통계실습"}, {"id": "004102", "school": "자연과학대학", "name": "수치해석"}, {"id": "004302", "school": "자연과학대학", "name": "수리통계학1"}, {"id": "010880", "school": "자연과학대학", "name": "기계학습"}, {"id": "000870", "school": "자연과학대학", "name": "다변량통계분석"}, {"id": "002347", "school": "자연과학대학", "name": "위상수학1"}, {"id": "006469", "school": "자연과학대학", "name": "범주형자료분석"}, {"id": "006597", "school": "자연과학대학", "name": "보험수학"}, {"id": "002095", "school": "자연과학대학", "name": "역학1"}, {"id": "003677", "school": "자연과학대학", "name": "항성천문학"}, {"id": "001776", "school": "자연과학대학", "name": "수리물리1"}, {"id": "009700", "school": "자연과학대학", "name": "기초계산과학"}, {"id": "010884", "school": "자연과학대학", "name": "현대물리학및실험"}, {"id": "002035", "school": "자연과학대학", "name": "양자역학1"}, {"id": "002977", "school": "자연과학대학", "name": "전자기학2"}, {"id": "003182", "school": "자연과학대학", "name": "천체물리학"}, {"id": "007740", "school": "자연과학대학", "name": "항성대기"}, {"id": "010555", "school": "자연과학대학", "name": "고급계산과학"}, {"id": "011435", "school": "자연과학대학", "name": "기초회로와센서실험"}, {"id": "000283", "school": "자연과학대학", "name": "고체물리학1"}, {"id": "003335", "school": "자연과학대학", "name": "통계물리학"}, {"id": "007741", "school": "자연과학대학", "name": "현대우주론"}, {"id": "008044", "school": "자연과학대학", "name": "전파천문학"}, {"id": "009138", "school": "자연과학대학", "name": "디스플레이광학"}, {"id": "010886", "school": "자연과학대학", "name": "데이터분석기법"}, {"id": "002712", "school": "자연과학대학", "name": "일반화학2"}, {"id": "001476", "school": "자연과학대학", "name": "분석화학실험"}, {"id": "002354", "school": "자연과학대학", "name": "유기화학1"}, {"id": "002982", "school": "자연과학대학", "name": "정량분석화학"}, {"id": "007793", "school": "자연과학대학", "name": "향장화학"}, {"id": "008577", "school": "자연과학대학", "name": "화학을위한물리"}, {"id": "001114", "school": "자연과학대학", "name": "무기화학"}, {"id": "001283", "school": "자연과학대학", "name": "물리화학2"}, {"id": "004461", "school": "자연과학대학", "name": "기기분석화학"}, {"id": "006679", "school": "자연과학대학", "name": "전기화학"}, {"id": "006970", "school": "자연과학대학", "name": "유기메카니즘"}, {"id": "007840", "school": "자연과학대학", "name": "화학교과교육론"}, {"id": "004462", "school": "자연과학대학", "name": "생화학"}, {"id": "004309", "school": "자연과학대학", "name": "유기합성"}, {"id": "004587", "school": "자연과학대학", "name": "고체화학"}, {"id": "002351", "school": "생명과학대학", "name": "유기화학"}, {"id": "004283", "school": "생명과학대학", "name": "일반미생물학및실험"}, {"id": "005194", "school": "생명과학대학", "name": "생물리화학"}, {"id": "001957", "school": "생명과학대학", "name": "식품화학1"}, {"id": "007468", "school": "생명과학대학", "name": "식품공학"}, {"id": "006591", "school": "생명과학대학", "name": "기능성식품학"}, {"id": "007612", "school": "생명과학대학", "name": "향미화학및관능평가"}, {"id": "009829", "school": "생명과학대학", "name": "식품가공학및실험(종합설계)"}, {"id": "010196", "school": "생명과학대학", "name": "동물생명식품학"}, {"id": "010200", "school": "생명과학대학", "name": "식품기기분석"}, {"id": "010888", "school": "생명과학대학", "name": "식품위해미생물학및실험"}, {"id": "007611", "school": "생명과학대학", "name": "식품분석및실험"}, {"id": "008601", "school": "생명과학대학", "name": "식품효소공학"}, {"id": "009827", "school": "생명과학대학", "name": "식품냉동학"}, {"id": "011082", "school": "생명과학대학", "name": "향장식품소재공학"}, {"id": "006847", "school": "생명과학대학", "name": "일반생물학1"}, {"id": "004282", "school": "생명과학대학", "name": "생화학1"}, {"id": "004281", "school": "생명과학대학", "name": "유기화학"}, {"id": "008602", "school": "생명과학대학", "name": "일반생리학"}, {"id": "005212", "school": "생명과학대학", "name": "세포생물학1"}, {"id": "007818", "school": "생명과학대학", "name": "분자생물학1및실험"}, {"id": "005103", "school": "생명과학대학", "name": "유전학"}, {"id": "005622", "school": "생명과학대학", "name": "대사생화학"}, {"id": "005643", "school": "생명과학대학", "name": "생명의약학"}, {"id": "006920", "school": "생명과학대학", "name": "면역학2"}, {"id": "009679", "school": "생명과학대학", "name": "응용균학"}, {"id": "005211", "school": "생명과학대학", "name": "생물화학공학"}, {"id": "008065", "school": "생명과학대학", "name": "줄기세포학"}, {"id": "008604", "school": "생명과학대학", "name": "후성유전학"}, {"id": "009326", "school": "생명과학대학", "name": "단백질공학"}, {"id": "009687", "school": "생명과학대학", "name": "인지뇌과학"}, {"id": "011975", "school": "생명과학대학", "name": "바이오메디컬AI"}, {"id": "009697", "school": "생명과학대학", "name": "일반유전학"}, {"id": "009855", "school": "생명과학대학", "name": "재배학및실습"}, {"id": "005458", "school": "생명과학대학", "name": "생화학및실험"}, {"id": "008960", "school": "생명과학대학", "name": "식물생리학및실험"}, {"id": "005899", "school": "생명과학대학", "name": "식물병리학"}, {"id": "008974", "school": "생명과학대학", "name": "생물정보학"}, {"id": "008982", "school": "생명과학대학", "name": "유전체학개론"}, {"id": "011440", "school": "생명과학대학", "name": "자원식물학"}, {"id": "009681", "school": "생명과학대학", "name": "식물분자육종학및실험"}, {"id": "006319", "school": "생명과학대학", "name": "식물스트레스생물학"}, {"id": "008972", "school": "생명과학대학", "name": "종묘생산학"}, {"id": "009672", "school": "생명과학대학", "name": "생체신호전달"}, {"id": "009704", "school": "생명과학대학", "name": "약용식물학"}, {"id": "011122", "school": "생명과학대학", "name": "분자세포생물학및실험1"}, {"id": "011123", "school": "생명과학대학", "name": "식물생명과학및실습"}, {"id": "011124", "school": "생명과학대학", "name": "수산생물학및실험"}, {"id": "011130", "school": "생명과학대학", "name": "생명산업유전학"}, {"id": "011952", "school": "생명과학대학", "name": "머신러닝기초및실습"}, {"id": "011982", "school": "생명과학대학", "name": "기능성바이오소재탐색"}, {"id": "011128", "school": "생명과학대학", "name": "종자생명산업과학및실습1"}, {"id": "011135", "school": "생명과학대학", "name": "천연물화학및실험1"}, {"id": "004702", "school": "생명과학대학", "name": "면역학"}, {"id": "011134", "school": "생명과학대학", "name": "어류생리학"}, {"id": "011137", "school": "생명과학대학", "name": "곤충병리및선천성면역학"}, {"id": "009267", "school": "생명과학대학", "name": "빅데이터분석"}, {"id": "004474", "school": "전자정보공학대학", "name": "통신이론"}, {"id": "004600", "school": "전자정보공학대학", "name": "디지털신호처리"}, {"id": "004699", "school": "전자정보공학대학", "name": "데이터통신"}, {"id": "007453", "school": "전자정보공학대학", "name": "전자회로1"}, {"id": "007806", "school": "전자정보공학대학", "name": "기초반도체"}, {"id": "009649", "school": "전자정보공학대학", "name": "전자기1"}, {"id": "004475", "school": "전자정보공학대학", "name": "자동제어"}, {"id": "007235", "school": "전자정보공학대학", "name": "랜덤프로세스"}, {"id": "008076", "school": "전자정보공학대학", "name": "기초광학및실험"}, {"id": "002505", "school": "전자정보공학대학", "name": "인공지능"}, {"id": "006558", "school": "전자정보공학대학", "name": "무선통신공학"}, {"id": "006562", "school": "전자정보공학대학", "name": "초고주파공학"}, {"id": "007585", "school": "전자정보공학대학", "name": "통신시스템설계"}, {"id": "009654", "school": "전자정보공학대학", "name": "전자정보통신공학특강A"}, {"id": "009655", "school": "전자정보공학대학", "name": "광통신공학"}, {"id": "009661", "school": "전자정보공학대학", "name": "반도체공정실험"}, {"id": "009662", "school": "전자정보공학대학", "name": "전자회로설계"}, {"id": "009663", "school": "전자정보공학대학", "name": "멀티미디어설계"}, {"id": "009666", "school": "전자정보공학대학", "name": "마이크로컴퓨터실험"}, {"id": "009947", "school": "전자정보공학대학", "name": "캡스톤디자인A"}, {"id": "011351", "school": "전자정보공학대학", "name": "반도체특화연구및실험2"}, {"id": "006152", "school": "전자정보공학대학", "name": "전자기학"}, {"id": "007722", "school": "전자정보공학대학", "name": "전자회로2"}, {"id": "011358", "school": "전자정보공학대학", "name": "반도체소자공학"}, {"id": "011359", "school": "전자정보공학대학", "name": "나노소재의합성및응용"}, {"id": "011368", "school": "전자정보공학대학", "name": "시스템반도체설계"}, {"id": "004310", "school": "소프트웨어융합대학", "name": "운영체제"}, {"id": "003281", "school": "소프트웨어융합대학", "name": "컴퓨터그래픽스"}, {"id": "007219", "school": "소프트웨어융합대학", "name": "데이터베이스"}, {"id": "007313", "school": "소프트웨어융합대학", "name": "프로그래밍언어의개념"}, {"id": "010881", "school": "소프트웨어융합대학", "name": "딥러닝"}, {"id": "011921", "school": "소프트웨어융합대학", "name": "최신기술콜로키움1"}, {"id": "F01230", "school": "소프트웨어융합대학", "name": "신호및시스템"}, {"id": "009960", "school": "소프트웨어융합대학", "name": "Capstone디자인(산학협력프로젝트)"}, {"id": "006132", "school": "소프트웨어융합대학", "name": "영상처리"}, {"id": "006135", "school": "소프트웨어융합대학", "name": "정보보호개론"}, {"id": "006478", "school": "소프트웨어융합대학", "name": "무선통신"}, {"id": "011771", "school": "소프트웨어융합대학", "name": "K-MOOC:생성형인공지능입문"}, {"id": "011924", "school": "소프트웨어융합대학", "name": "최신기술콜로키움3"}, {"id": "011932", "school": "소프트웨어융합대학", "name": "Human-AI Interaction"}, {"id": "012001", "school": "소프트웨어융합대학", "name": "지능형엣지시스템"}, {"id": "009520", "school": "소프트웨어융합대학", "name": "공개키암호론"}, {"id": "009986", "school": "소프트웨어융합대학", "name": "운영체제및보안"}, {"id": "011566", "school": "소프트웨어융합대학", "name": "K-MOOC:데이터베이스보안"}, {"id": "011971", "school": "소프트웨어융합대학", "name": "K-MOOC:메모리익스플로잇:해킹과방어"}, {"id": "011973", "school": "소프트웨어융합대학", "name": "정보보호연구입문"}, {"id": "008662", "school": "소프트웨어융합대학", "name": "디지털포렌식"}, {"id": "012008", "school": "소프트웨어융합대학", "name": "AI기반악성코드분석"}, {"id": "009957", "school": "소프트웨어융합대학", "name": "오픈소스SW개론"}, {"id": "006476", "school": "소프트웨어융합대학", "name": "게임프로그래밍"}, {"id": "011911", "school": "소프트웨어융합대학", "name": "연구실인턴쉽1"}, {"id": "006208", "school": "소프트웨어융합대학", "name": "가상현실"}, {"id": "010000", "school": "소프트웨어융합대학", "name": "기계학습"}, {"id": "011904", "school": "소프트웨어융합대학", "name": "생성형AI"}, {"id": "010563", "school": "소프트웨어융합대학", "name": "의사결정분석"}, {"id": "010566", "school": "소프트웨어융합대학", "name": "시계열분석및예측"}, {"id": "010237", "school": "소프트웨어융합대학", "name": "AI로봇설계"}, {"id": "010238", "school": "소프트웨어융합대학", "name": "딥러닝시스템"}, {"id": "003284", "school": "소프트웨어융합대학", "name": "컴퓨터네트워크"}, {"id": "004599", "school": "소프트웨어융합대학", "name": "통신시스템"}, {"id": "010227", "school": "소프트웨어융합대학", "name": "컴퓨터구조및운영체제"}, {"id": "011910", "school": "소프트웨어융합대학", "name": "스마트UX&UI디자인1"}, {"id": "000703", "school": "소프트웨어융합대학", "name": "그래픽디자인1"}, {"id": "010329", "school": "소프트웨어융합대학", "name": "프로덕트&모빌리티디자인1"}, {"id": "010333", "school": "소프트웨어융합대학", "name": "스마트프로덕트디자인1"}, {"id": "010335", "school": "소프트웨어융합대학", "name": "3D그래픽어플리케이션"}, {"id": "010336", "school": "소프트웨어융합대학", "name": "인포그래픽"}, {"id": "005764", "school": "소프트웨어융합대학", "name": "졸업작품(P/NP)"}, {"id": "005662", "school": "소프트웨어융합대학", "name": "아이덴티티디자인1"}, {"id": "006641", "school": "소프트웨어융합대학", "name": "제품시스템디자인1"}, {"id": "009214", "school": "소프트웨어융합대학", "name": "제품및운송기기디자인3"}, {"id": "010343", "school": "소프트웨어융합대학", "name": "디지털미디어프로젝트1"}, {"id": "012035", "school": "소프트웨어융합대학", "name": "제너레이티브디자인"}, {"id": "011090", "school": "소프트웨어융합대학", "name": "디지털코믹스제작1"}, {"id": "011191", "school": "소프트웨어융합대학", "name": "비주얼디벨롭먼트1"}, {"id": "011088", "school": "소프트웨어융합대학", "name": "이펙트디자인1"}, {"id": "011089", "school": "소프트웨어융합대학", "name": "디지털애니메이션디렉팅1"}, {"id": "011187", "school": "소프트웨어융합대학", "name": "애니메이션워크숍1"}, {"id": "011189", "school": "소프트웨어융합대학", "name": "3D제작도구고급1"}, {"id": "010298", "school": "소프트웨어융합대학", "name": "애니메이션코어스튜디오1"}, {"id": "010300", "school": "소프트웨어융합대학", "name": "웹툰커넥션프로덕션1"}, {"id": "011092", "school": "소프트웨어융합대학", "name": "영상캡스톤디자인"}, {"id": "011261", "school": "소프트웨어융합대학", "name": "딥러닝개론"}, {"id": "011173", "school": "소프트웨어융합대학", "name": "자연어처리"}, {"id": "011262", "school": "소프트웨어융합대학", "name": "인공지능과사이버보안"}, {"id": "011181", "school": "소프트웨어융합대학", "name": "강화학습"}, {"id": "004114", "school": "인공지능융합대학", "name": "전기회로"}, {"id": "008622", "school": "인공지능융합대학", "name": "확률및랜덤변수"}, {"id": "011678", "school": "인공지능융합대학", "name": "기초전자물리"}, {"id": "000307", "school": "인공지능융합대학", "name": "공업수학2"}, {"id": "004268", "school": "인공지능융합대학", "name": "데이터구조론"}, {"id": "005611", "school": "인공지능융합대학", "name": "디지털논리회로"}, {"id": "008621", "school": "인공지능융합대학", "name": "MATLAB프로그래밍"}, {"id": "007330", "school": "인공지능융합대학", "name": "확률및통계"}, {"id": "009912", "school": "인공지능융합대학", "name": "C프로그래밍및실습"}, {"id": "011345", "school": "인공지능융합대학", "name": "반도체개론"}, {"id": "011346", "school": "인공지능융합대학", "name": "연구실인턴"}, {"id": "011441", "school": "인공지능융합대학", "name": "기초물리전자공학"}, {"id": "010571", "school": "인공지능융합대학", "name": "디지털회로설계"}, {"id": "011347", "school": "인공지능융합대학", "name": "반도체재료공학"}, {"id": "004118", "school": "인공지능융합대학", "name": "디지털시스템"}, {"id": "009913", "school": "인공지능융합대학", "name": "고급C프로그래밍및실습"}, {"id": "009952", "school": "인공지능융합대학", "name": "자료구조및실습"}, {"id": "006237", "school": "인공지능융합대학", "name": "웹프로그래밍"}, {"id": "009914", "school": "인공지능융합대학", "name": "공학설계기초(산학프로젝트입문)"}, {"id": "009992", "school": "인공지능융합대학", "name": "문제해결및실습:C++"}, {"id": "011251", "school": "인공지능융합대학", "name": "K-MOOC:모두를위한머신러닝"}, {"id": "009173", "school": "인공지능융합대학", "name": "정보보호와보안의기초"}, {"id": "009955", "school": "인공지능융합대학", "name": "이산수학및프로그래밍"}, {"id": "005619", "school": "인공지능융합대학", "name": "멀티미디어프로그래밍"}, {"id": "010206", "school": "인공지능융합대학", "name": "일반물리및시뮬레이션"}, {"id": "011259", "school": "인공지능융합대학", "name": "기계학습개론"}, {"id": "011321", "school": "인공지능융합대학", "name": "인공지능활용"}, {"id": "011494", "school": "인공지능융합대학", "name": "고급인공지능활용"}, {"id": "010224", "school": "인공지능융합대학", "name": "창의SW기초설계"}, {"id": "011488", "school": "인공지능융합대학", "name": "자료구조및실습"}, {"id": "004642", "school": "인공지능융합대학", "name": "동역학"}, {"id": "004864", "school": "인공지능융합대학", "name": "메카트로닉스"}, {"id": "011428", "school": "인공지능융합대학", "name": "AI로봇프로그래밍"}, {"id": "005119", "school": "인공지능융합대학", "name": "만화기초1"}, {"id": "008785", "school": "인공지능융합대학", "name": "애니메이션기초1"}, {"id": "010306", "school": "인공지능융합대학", "name": "2D디자인"}, {"id": "010307", "school": "인공지능융합대학", "name": "3D디자인"}, {"id": "003313", "school": "인공지능융합대학", "name": "타이포그래피"}, {"id": "006225", "school": "인공지능융합대학", "name": "기초렌더링"}, {"id": "010310", "school": "인공지능융합대학", "name": "프로덕트디자인2"}, {"id": "010312", "school": "인공지능융합대학", "name": "비주얼커뮤니케이션디자인2"}, {"id": "010314", "school": "인공지능융합대학", "name": "디자인인모션1"}, {"id": "008790", "school": "인공지능융합대학", "name": "콘텐츠기획1"}, {"id": "008792", "school": "인공지능융합대학", "name": "만화제작1"}, {"id": "010279", "school": "인공지능융합대학", "name": "내러티브워크숍"}, {"id": "010281", "school": "인공지능융합대학", "name": "애니메이션액팅1"}, {"id": "010580", "school": "인공지능융합대학", "name": "3D제작도구1"}, {"id": "011185", "school": "인공지능융합대학", "name": "프리비즈워크숍1"}, {"id": "008764", "school": "인공지능융합대학", "name": "군사학개론"}, {"id": "000304", "school": "공과대학", "name": "공업수학1"}, {"id": "011277", "school": "공과대학", "name": "건축기초설계"}, {"id": "006153", "school": "공과대학", "name": "건축구조시스템"}, {"id": "006154", "school": "공과대학", "name": "건축환경개론"}, {"id": "011276", "school": "공과대학", "name": "건축공학개론"}, {"id": "004321", "school": "공과대학", "name": "구조역학2"}, {"id": "007663", "school": "공과대학", "name": "건축환경설계"}, {"id": "008180", "school": "공과대학", "name": "건설관리및경영"}, {"id": "004431", "school": "공과대학", "name": "건축일반구조"}, {"id": "006479", "school": "공과대학", "name": "건축기계설비시스템"}, {"id": "007662", "school": "공과대학", "name": "철근콘크리트공학"}, {"id": "012037", "school": "공과대학", "name": "건축공학AI"}, {"id": "007812", "school": "공과대학", "name": "공학설계A"}, {"id": "004704", "school": "공과대학", "name": "철골구조"}, {"id": "006574", "school": "공과대학", "name": "건축전기설비시스템"}, {"id": "006932", "school": "공과대학", "name": "건축실무"}, {"id": "008182", "school": "공과대학", "name": "구조계획및설계"}, {"id": "006930", "school": "공과대학", "name": "스튜디오7"}, {"id": "007177", "school": "공과대학", "name": "공간과사회"}, {"id": "006157", "school": "공과대학", "name": "스튜디오1"}, {"id": "006776", "school": "공과대학", "name": "건축디지털디자인1"}, {"id": "011280", "school": "공과대학", "name": "표현과매체"}, {"id": "011525", "school": "공과대학", "name": "건축디자인"}, {"id": "006481", "school": "공과대학", "name": "스튜디오3"}, {"id": "007828", "school": "공과대학", "name": "건축시공및관리"}, {"id": "011286", "school": "공과대학", "name": "현대건축사"}, {"id": "011283", "school": "공과대학", "name": "디지털패브리케이션"}, {"id": "006577", "school": "공과대학", "name": "건축법제도"}, {"id": "006663", "school": "공과대학", "name": "스튜디오5"}, {"id": "008642", "school": "공과대학", "name": "프로젝트관리론"}, {"id": "006765", "school": "공과대학", "name": "도시계획"}, {"id": "010273", "school": "공과대학", "name": "건축구조역학"}, {"id": "004280", "school": "공과대학", "name": "응용역학및연습1"}, {"id": "004674", "school": "공과대학", "name": "환경공학및실험"}, {"id": "010657", "school": "공과대학", "name": "유체역학및연습"}, {"id": "007153", "school": "공과대학", "name": "기초창의설계"}, {"id": "010589", "school": "공과대학", "name": "콘크리트공학"}, {"id": "010590", "school": "공과대학", "name": "구조공학"}, {"id": "004852", "school": "공과대학", "name": "측량학"}, {"id": "010591", "school": "공과대학", "name": "재해방지공학"}, {"id": "010593", "school": "공과대학", "name": "강구조공학"}, {"id": "011426", "school": "공과대학", "name": "스마트수처리시스템공학"}, {"id": "004710", "school": "공과대학", "name": "해안및항만공학"}, {"id": "004898", "school": "공과대학", "name": "내진설계"}, {"id": "005222", "school": "공과대학", "name": "도로공학"}, {"id": "010598", "school": "공과대학", "name": "건설CAD"}, {"id": "011425", "school": "공과대학", "name": "도시수재해공학"}, {"id": "011963", "school": "공과대학", "name": "AI기반건설환경데이터분석"}, {"id": "007664", "school": "공과대학", "name": "수질관리"}, {"id": "009618", "school": "공과대학", "name": "환경공학개론"}, {"id": "009815", "school": "공과대학", "name": "기상대기과학"}, {"id": "011434", "school": "공과대학", "name": "환경정보공학"}, {"id": "003989", "school": "공과대학", "name": "환경화학"}, {"id": "011436", "school": "공과대학", "name": "환경해양학"}, {"id": "010902", "school": "공과대학", "name": "졸업논문연구"}, {"id": "007738", "school": "공과대학", "name": "토양지하수환경학"}, {"id": "009638", "school": "공과대학", "name": "환경에너지전문가과정연습"}, {"id": "006486", "school": "공과대학", "name": "로보틱스"}, {"id": "007465", "school": "공과대학", "name": "연소공학개론"}, {"id": "009193", "school": "공과대학", "name": "시스템해석"}, {"id": "009850", "school": "공과대학", "name": "종합설계A"}, {"id": "006886", "school": "공과대학", "name": "시뮬레이션시스템설계"}, {"id": "007164", "school": "공과대학", "name": "응용공기역학및설계"}, {"id": "007268", "school": "공과대학", "name": "항법전자시스템설계"}, {"id": "008115", "school": "공과대학", "name": "로켓공학및설계"}, {"id": "009249", "school": "공과대학", "name": "항공우주공학연구1"}, {"id": "009835", "school": "공과대학", "name": "종합설계1"}, {"id": "010922", "school": "공과대학", "name": "자율비행체시스템설계2"}, {"id": "005217", "school": "공과대학", "name": "재료열역학1"}, {"id": "006682", "school": "공과대학", "name": "결정구조및X-선회절"}, {"id": "002641", "school": "공과대학", "name": "일반물리학2"}, {"id": "007160", "school": "공과대학", "name": "전기전자공학"}, {"id": "009196", "school": "공과대학", "name": "공학기초설계실험"}, {"id": "011917", "school": "공과대학", "name": "AI재료열역학1"}, {"id": "005650", "school": "공과대학", "name": "고체물리"}, {"id": "006683", "school": "공과대학", "name": "확산및상변태"}, {"id": "010079", "school": "공과대학", "name": "금속과세라믹기초설계및실험"}, {"id": "000264", "school": "공과대학", "name": "고분자화학"}, {"id": "005918", "school": "공과대학", "name": "반도체재료"}, {"id": "007315", "school": "공과대학", "name": "기초물리화학"}, {"id": "008495", "school": "공과대학", "name": "에너지재료"}, {"id": "009837", "school": "공과대학", "name": "바이오재료공학"}, {"id": "006314", "school": "공과대학", "name": "박막공학"}, {"id": "006686", "school": "공과대학", "name": "재료분석학"}, {"id": "007274", "school": "공과대학", "name": "고분자합성"}, {"id": "007460", "school": "공과대학", "name": "초미립소재학"}, {"id": "007461", "school": "공과대학", "name": "자성체재료"}, {"id": "009017", "school": "공과대학", "name": "산학협동강좌1"}, {"id": "009020", "school": "공과대학", "name": "반도체나노소자"}, {"id": "010081", "school": "공과대학", "name": "나노신소재창의연구1(종합설계)"}, {"id": "000787", "school": "공과대학", "name": "기초역학"}, {"id": "004714", "school": "공과대학", "name": "열역학"}, {"id": "006889", "school": "공과대학", "name": "기계제도및CAD"}, {"id": "007620", "school": "공과대학", "name": "기초설계"}, {"id": "006312", "school": "공과대학", "name": "기계제작법"}, {"id": "005233", "school": "공과대학", "name": "유체기계"}, {"id": "006492", "school": "공과대학", "name": "재료거동학"}, {"id": "006891", "school": "공과대학", "name": "기계진동학"}, {"id": "007698", "school": "공과대학", "name": "응용기계설계"}, {"id": "006164", "school": "공과대학", "name": "무인항공기설계1"}, {"id": "011073", "school": "공과대학", "name": "모의비행실습2"}, {"id": "011574", "school": "공과대학", "name": "지구자원시스템개론"}, {"id": "008141", "school": "공과대학", "name": "석유가스공학개론"}, {"id": "009541", "school": "공과대학", "name": "전산프로그래밍및실습"}, {"id": "010916", "school": "공과대학", "name": "위성원격탐사개론및실습"}, {"id": "011796", "school": "공과대학", "name": "지구시스템의이해"}, {"id": "011798", "school": "공과대학", "name": "기초역학개론"}, {"id": "006657", "school": "공과대학", "name": "지질공학및실습"}, {"id": "009540", "school": "공과대학", "name": "이산자료처리및실습"}, {"id": "009565", "school": "공과대학", "name": "선광제련공학개론"}, {"id": "011392", "school": "공과대학", "name": "핵심광물융합탐사"}, {"id": "008163", "school": "공과대학", "name": "환경지구화학"}, {"id": "010908", "school": "공과대학", "name": "화약발파및터널굴착설계"}, {"id": "011941", "school": "공과대학", "name": "지오빅데이터활용"}, {"id": "008991", "school": "공과대학", "name": "원자및핵물리"}, {"id": "011390", "school": "공과대학", "name": "원자력공학개론1"}, {"id": "009568", "school": "공과대학", "name": "원자로열역학"}, {"id": "010924", "school": "공과대학", "name": "양자공학개론"}, {"id": "009002", "school": "공과대학", "name": "원자로열수력학"}, {"id": "009571", "school": "공과대학", "name": "방사선계측및실험"}, {"id": "009846", "school": "공과대학", "name": "원자력발전소구조해석"}, {"id": "010929", "school": "공과대학", "name": "원자로해석실무"}, {"id": "010930", "school": "공과대학", "name": "원자로동역학및보호계통"}, {"id": "012019", "school": "공과대학", "name": "미래형원전재료"}, {"id": "009590", "school": "공과대학", "name": "원자력안전과물리적방호"}, {"id": "011360", "school": "공과대학", "name": "우주항공입문"}, {"id": "004510", "school": "공과대학", "name": "고체역학"}, {"id": "006885", "school": "공과대학", "name": "비행동역학"}, {"id": "004715", "school": "공과대학", "name": "진동학"}, {"id": "004756", "school": "공과대학", "name": "압축성유체역학"}, {"id": "007054", "school": "공과대학", "name": "우주궤도역학"}, {"id": "010662", "school": "공과대학", "name": "메카트로닉스종합설계"}, {"id": "011352", "school": "공과대학", "name": "항공우주AI기초"}, {"id": "006770", "school": "공과대학", "name": "전기전자공학개론"}, {"id": "011406", "school": "공과대학", "name": "항공우주제도및CAD"}, {"id": "011829", "school": "공과대학", "name": "자율비행체공학개론"}, {"id": "011378", "school": "공과대학", "name": "비행기초역학1"}, {"id": "009530", "school": "공과대학", "name": "관숙비행"}, {"id": "009532", "school": "공과대학", "name": "비행역학"}, {"id": "011067", "school": "공과대학", "name": "모의비행이론및실습"}, {"id": "011070", "school": "공과대학", "name": "항공안전관리론"}, {"id": "011369", "school": "공과대학", "name": "우주기술운용체계1"}, {"id": "008920", "school": "공과대학", "name": "해전사"}, {"id": "010211", "school": "공과대학", "name": "장교직무교육세미나1"}, {"id": "010936", "school": "공과대학", "name": "해양체육"}, {"id": "004508", "school": "공과대학", "name": "디지털시스템및실험"}, {"id": "008766", "school": "공과대학", "name": "군대윤리"}, {"id": "010213", "school": "공과대학", "name": "장교직무교육세미나3"}, {"id": "008768", "school": "공과대학", "name": "선형대수와시스템이론"}, {"id": "010215", "school": "공과대학", "name": "장교직무교육세미나5"}, {"id": "010932", "school": "공과대학", "name": "고급프로그래밍언어"}, {"id": "008922", "school": "공과대학", "name": "무기체계공학"}, {"id": "010217", "school": "공과대학", "name": "장교직무교육세미나7"}, {"id": "008929", "school": "공과대학", "name": "로봇공학"}, {"id": "009207", "school": "공과대학", "name": "전자전"}, {"id": "010939", "school": "공과대학", "name": "통신공학"}, {"id": "000806", "school": "예체능대학", "name": "기초한국화1"}, {"id": "005681", "school": "예체능대학", "name": "기초회화1"}, {"id": "004120", "school": "예체능대학", "name": "기초소묘"}, {"id": "008659", "school": "예체능대학", "name": "서양미술사1"}, {"id": "000773", "school": "예체능대학", "name": "기초서양화1"}, {"id": "009543", "school": "예체능대학", "name": "인물화"}, {"id": "001798", "school": "예체능대학", "name": "수묵화"}, {"id": "002398", "school": "예체능대학", "name": "유화기법1"}, {"id": "003561", "school": "예체능대학", "name": "한국미술사"}, {"id": "005105", "school": "예체능대학", "name": "컴퓨터드로잉1"}, {"id": "011236", "school": "예체능대학", "name": "공간연구"}, {"id": "002390", "school": "예체능대학", "name": "유화1"}, {"id": "004127", "school": "예체능대학", "name": "한국화1"}, {"id": "001643", "school": "예체능대학", "name": "서.현대회화"}, {"id": "003810", "school": "예체능대학", "name": "현대미술론"}, {"id": "004129", "school": "예체능대학", "name": "한.현대회화"}, {"id": "007843", "school": "예체능대학", "name": "미술교과교육론"}, {"id": "012021", "school": "예체능대학", "name": "멀티미디어와회화"}, {"id": "001629", "school": "예체능대학", "name": "서.종합실기1"}, {"id": "004747", "school": "예체능대학", "name": "한.종합실기1"}, {"id": "001634", "school": "예체능대학", "name": "서.종합실기3"}, {"id": "004748", "school": "예체능대학", "name": "한.종합실기3"}, {"id": "005624", "school": "예체능대학", "name": "사진과회화"}, {"id": "008505", "school": "예체능대학", "name": "기초패턴설계"}, {"id": "008506", "school": "예체능대학", "name": "패션디자인"}, {"id": "010942", "school": "예체능대학", "name": "텍스타일캡스톤디자인"}, {"id": "004466", "school": "예체능대학", "name": "서양복식사"}, {"id": "004744", "school": "예체능대학", "name": "모델드로잉"}, {"id": "010943", "school": "예체능대학", "name": "고급패턴종합설계"}, {"id": "010948", "school": "예체능대학", "name": "패션과색채"}, {"id": "008513", "school": "예체능대학", "name": "고급패션드레이핑"}, {"id": "007845", "school": "예체능대학", "name": "의상교과교육론"}, {"id": "010946", "school": "예체능대학", "name": "기초니트종합설계"}, {"id": "010947", "school": "예체능대학", "name": "디지털패션종합설계"}, {"id": "009941", "school": "예체능대학", "name": "창작패턴종합설계"}, {"id": "010015", "school": "예체능대학", "name": "패션소재기획종합설계"}, {"id": "010016", "school": "예체능대학", "name": "패션컬렉션종합설계"}, {"id": "002894", "school": "예체능대학", "name": "전공실기1"}, {"id": "004134", "school": "예체능대학", "name": "연주1"}, {"id": "010640", "school": "예체능대학", "name": "오케스트라및앙상블1"}, {"id": "010949", "school": "예체능대학", "name": "기초악전및컴퓨터사보"}, {"id": "001045", "school": "예체능대학", "name": "딕션1"}, {"id": "003647", "school": "예체능대학", "name": "합창1"}, {"id": "007140", "school": "예체능대학", "name": "현악합주1"}, {"id": "007427", "school": "예체능대학", "name": "관악합주1"}, {"id": "007706", "school": "예체능대학", "name": "피아노교수법"}, {"id": "009471", "school": "예체능대학", "name": "체임버뮤직1"}, {"id": "009482", "school": "예체능대학", "name": "시창청음1"}, {"id": "002112", "school": "예체능대학", "name": "연주3"}, {"id": "002902", "school": "예체능대학", "name": "전공실기3"}, {"id": "003464", "school": "예체능대학", "name": "피아노문헌1"}, {"id": "009469", "school": "예체능대학", "name": "딕션3"}, {"id": "004135", "school": "예체능대학", "name": "합창3"}, {"id": "007424", "school": "예체능대학", "name": "현악합주3"}, {"id": "007678", "school": "예체능대학", "name": "관악합주3"}, {"id": "009473", "school": "예체능대학", "name": "체임버뮤직3"}, {"id": "009479", "school": "예체능대학", "name": "화성법및대위법1"}, {"id": "009750", "school": "예체능대학", "name": "레코딩실습1"}, {"id": "009754", "school": "예체능대학", "name": "리듬연구"}, {"id": "009928", "school": "예체능대학", "name": "컴퓨터음악2"}, {"id": "010642", "school": "예체능대학", "name": "오케스트라및앙상블3"}, {"id": "002907", "school": "예체능대학", "name": "전공실기5"}, {"id": "004137", "school": "예체능대학", "name": "합창5"}, {"id": "004139", "school": "예체능대학", "name": "연주5"}, {"id": "001738", "school": "예체능대학", "name": "성악문헌1"}, {"id": "002455", "school": "예체능대학", "name": "음악사1"}, {"id": "007679", "school": "예체능대학", "name": "현악합주5"}, {"id": "007785", "school": "예체능대학", "name": "관악합주5"}, {"id": "007846", "school": "예체능대학", "name": "음악교과교육론"}, {"id": "009475", "school": "예체능대학", "name": "체임버뮤직5"}, {"id": "009486", "school": "예체능대학", "name": "관현악문헌"}, {"id": "010645", "school": "예체능대학", "name": "오케스트라및앙상블5"}, {"id": "002432", "school": "예체능대학", "name": "음악분석"}, {"id": "002911", "school": "예체능대학", "name": "전공실기7"}, {"id": "007789", "school": "예체능대학", "name": "현악합주7"}, {"id": "008187", "school": "예체능대학", "name": "관악합주7"}, {"id": "009477", "school": "예체능대학", "name": "체임버뮤직7"}, {"id": "009484", "school": "예체능대학", "name": "오페라워크샾1"}, {"id": "009760", "school": "예체능대학", "name": "DJing"}, {"id": "009930", "school": "예체능대학", "name": "반주문헌1"}, {"id": "010394", "school": "예체능대학", "name": "연주7"}, {"id": "010643", "school": "예체능대학", "name": "오케스트라및앙상블7"}, {"id": "003220", "school": "예체능대학", "name": "체육원리"}, {"id": "010952", "school": "예체능대학", "name": "기초종목실기"}, {"id": "011094", "school": "예체능대학", "name": "운동과건강"}, {"id": "002552", "school": "예체능대학", "name": "인체해부학"}, {"id": "006902", "school": "예체능대학", "name": "체육철학"}, {"id": "003224", "school": "예체능대학", "name": "체육측정평가"}, {"id": "010953", "school": "예체능대학", "name": "구기스포츠"}, {"id": "002329", "school": "예체능대학", "name": "운동생리학"}, {"id": "003218", "school": "예체능대학", "name": "체육심리학"}, {"id": "001844", "school": "예체능대학", "name": "스포츠특강1"}, {"id": "006900", "school": "예체능대학", "name": "레저스포츠1"}, {"id": "007847", "school": "예체능대학", "name": "체육교과교육론"}, {"id": "005765", "school": "예체능대학", "name": "졸업시험(P/NP)"}, {"id": "007384", "school": "예체능대학", "name": "스포츠마케팅"}, {"id": "004337", "school": "예체능대학", "name": "코우치학"}, {"id": "006903", "school": "예체능대학", "name": "스포츠과학실험법"}, {"id": "010958", "school": "예체능대학", "name": "스포츠재활"}, {"id": "003768", "school": "예체능대학", "name": "현대무용기본1"}, {"id": "004495", "school": "예체능대학", "name": "발레테크닉1"}, {"id": "004497", "school": "예체능대학", "name": "워크샵1"}, {"id": "005656", "school": "예체능대학", "name": "발레레파토리1"}, {"id": "008823", "school": "예체능대학", "name": "모던댄스1"}, {"id": "008827", "school": "예체능대학", "name": "한국전통무용기교훈련1"}, {"id": "008829", "school": "예체능대학", "name": "한국창작무용기교훈련1"}, {"id": "008902", "school": "예체능대학", "name": "한국전통무용기본1"}, {"id": "007217", "school": "예체능대학", "name": "발레기초1"}, {"id": "007550", "school": "예체능대학", "name": "전통타악실기"}, {"id": "004736", "school": "예체능대학", "name": "발레테크닉3"}, {"id": "004738", "school": "예체능대학", "name": "워크샵3"}, {"id": "005657", "school": "예체능대학", "name": "발레레파토리3"}, {"id": "008825", "school": "예체능대학", "name": "모던댄스3"}, {"id": "008834", "school": "예체능대학", "name": "전통춤기교실습1"}, {"id": "008836", "school": "예체능대학", "name": "창작춤기교실습1"}, {"id": "004894", "school": "예체능대학", "name": "즉흥"}, {"id": "008843", "school": "예체능대학", "name": "한국무용표현법"}, {"id": "010270", "school": "예체능대학", "name": "발레사"}, {"id": "005660", "school": "예체능대학", "name": "고급현대무용1"}, {"id": "008838", "school": "예체능대학", "name": "발레테크닉5"}, {"id": "008849", "school": "예체능대학", "name": "컨템포러리댄스1"}, {"id": "008853", "school": "예체능대학", "name": "한국전통무용안무1"}, {"id": "008855", "school": "예체능대학", "name": "한국창작무용안무1"}, {"id": "008868", "school": "예체능대학", "name": "발레레파토리5"}, {"id": "008857", "school": "예체능대학", "name": "춤과미디어"}, {"id": "008858", "school": "예체능대학", "name": "한국무용창작기법"}, {"id": "009226", "school": "예체능대학", "name": "컨템포러리발레1"}, {"id": "009228", "school": "예체능대학", "name": "발레워크샵1"}, {"id": "010967", "school": "예체능대학", "name": "무용교수학습방법"}, {"id": "005661", "school": "예체능대학", "name": "고급현대무용3"}, {"id": "008840", "school": "예체능대학", "name": "발레테크닉7"}, {"id": "008851", "school": "예체능대학", "name": "컨템포러리댄스3"}, {"id": "008864", "school": "예체능대학", "name": "한국전통무용작품분석1"}, {"id": "008866", "school": "예체능대학", "name": "한국창작무용작품분석1"}, {"id": "008870", "school": "예체능대학", "name": "발레레파토리7"}, {"id": "001206", "school": "예체능대학", "name": "무용미학"}, {"id": "008872", "school": "예체능대학", "name": "무용표현심리학"}, {"id": "009230", "school": "예체능대학", "name": "캐릭터발레1"}, {"id": "005627", "school": "예체능대학", "name": "영화개론"}, {"id": "007034", "school": "예체능대학", "name": "무대매커니즘1"}, {"id": "008673", "school": "예체능대학", "name": "스토리텔링"}, {"id": "008674", "school": "예체능대학", "name": "영상표현기초"}, {"id": "008675", "school": "예체능대학", "name": "연극의이해"}, {"id": "009976", "school": "예체능대학", "name": "기초연기1(근육과감각훈련)"}, {"id": "009977", "school": "예체능대학", "name": "개별창의연구1"}, {"id": "010970", "school": "예체능대학", "name": "호흡과발성1"}, {"id": "004522", "school": "예체능대학", "name": "작품분석"}, {"id": "006171", "school": "예체능대학", "name": "촬영조명1"}, {"id": "007285", "school": "예체능대학", "name": "세계연극사"}, {"id": "010054", "school": "예체능대학", "name": "텍스트와연기실습1"}, {"id": "010056", "school": "예체능대학", "name": "공연프로덕션실습1"}, {"id": "006174", "school": "예체능대학", "name": "영화제작WS1"}, {"id": "008692", "school": "예체능대학", "name": "뮤지컬넘버플레이1"}, {"id": "010053", "school": "예체능대학", "name": "편집"}, {"id": "010972", "school": "예체능대학", "name": "신체언어훈련1"}, {"id": "004725", "school": "예체능대학", "name": "다큐영화제작"}, {"id": "008687", "school": "예체능대학", "name": "동양영화사"}, {"id": "004732", "school": "예체능대학", "name": "영화제작WS3"}, {"id": "007848", "school": "예체능대학", "name": "연극영화교과교육론"}, {"id": "008686", "school": "예체능대학", "name": "매체연기실습1"}, {"id": "008700", "school": "예체능대학", "name": "영화기술고급"}, {"id": "010058", "school": "예체능대학", "name": "연기실습콜라보레이션1"}, {"id": "006177", "school": "예체능대학", "name": "영화제작WS5"}, {"id": "008695", "school": "예체능대학", "name": "장편영화기획"}, {"id": "008696", "school": "예체능대학", "name": "공연제작Project1"}, {"id": "010059", "school": "예체능대학", "name": "영화장르연구"}, {"id": "010061", "school": "예체능대학", "name": "다중매체연기연구"}, {"id": "003706", "school": "기타", "name": "행정법1"}, {"id": "009607", "school": "기타", "name": "형사소송법"}, {"id": "008724", "school": "기타", "name": "노동법"}, {"id": "008732", "school": "기타", "name": "금융법"}, {"id": "012046", "school": "기타", "name": "LEET추리논증연습1"}, {"id": "008726", "school": "기타", "name": "불법행위법"}, {"id": "011204", "school": "기타", "name": "공정거래법"}, {"id": "012043", "school": "기타", "name": "LEET언어이해연습1"}, {"id": "010704", "school": "기타", "name": "디지털필름메이킹1"}, {"id": "011229", "school": "기타", "name": "모션그래픽스1:원리"}, {"id": "011231", "school": "기타", "name": "3D모션디자인"}, {"id": "010727", "school": "기타", "name": "영상디자인스튜디오1"}, {"id": "012010", "school": "기타", "name": "문화축제기획프로젝트"}, {"id": "012038", "school": "기타", "name": "인공지능과스토리텔링"}, {"id": "011998", "school": "기타", "name": "미디어문화와플랫폼"}, {"id": "010999", "school": "기타", "name": "대중문화연구"}, {"id": "010748", "school": "기타", "name": "럭셔리브랜드큐레이션"}, {"id": "011007", "school": "기타", "name": "가죽제품설계"}, {"id": "011010", "school": "기타", "name": "럭셔리브랜드스튜디오"}, {"id": "010782", "school": "기타", "name": "퍼포밍아트1(무대디자인과제작)"}, {"id": "011020", "school": "기타", "name": "융합캡스톤디자인4"}, {"id": "011970", "school": "기타", "name": "GMSW-AI영상편집"}, {"id": "010504", "school": "기타", "name": "GMSW-미디어네트워크분석"}, {"id": "010483", "school": "기타", "name": "GMSW-글로벌시장과일본문화콘텐츠"}, {"id": "010605", "school": "기타", "name": "디자인씽킹"}, {"id": "012050", "school": "기타", "name": "창업재무"}, {"id": "010977", "school": "기타", "name": "창업마케팅"}, {"id": "010981", "school": "기타", "name": "실전창업동아리3"}, {"id": "011029", "school": "기타", "name": "BA 빅데이터통계분석론"}, {"id": "011076", "school": "기타", "name": "인공지능기초수학"}, {"id": "011113", "school": "기타", "name": "빅데이터로보는세상"}, {"id": "009715", "school": "기타", "name": "ES-문화예술과ICT"}, {"id": "012052", "school": "기타", "name": "ES-인공지능과디지털아트"}, {"id": "009730", "school": "기타", "name": "ES-창업을위한디지털패션디자인"}, {"id": "011222", "school": "기타", "name": "시스템생명공학캡스톤디자인Ⅰ"}, {"id": "010113", "school": "기타", "name": "SM-소셜미디어개론"}, {"id": "010114", "school": "기타", "name": "SM-소셜미디어마케팅"}, {"id": "010120", "school": "기타", "name": "SM-소셜웹프로그래밍"}, {"id": "010629", "school": "기타", "name": "ST-데이터분석및시각화"}, {"id": "011101", "school": "기타", "name": "문화현장실습"}, {"id": "011213", "school": "기타", "name": "예술융합캡스톤디자인"}, {"id": "011121", "school": "기타", "name": "금융보험애널리틱스2"}, {"id": "011901", "school": "인공지능융합대학", "name": "지능사물인터넷개론"}, {"id": "011990", "school": "인공지능융합대학", "name": "지능IoT플랫폼"}, {"id": "012027", "school": "인공지능융합대학", "name": "사물강화학습"}] \ No newline at end of file diff --git a/parser/lectures_2025-1.xlsx b/parser/lectures_2025-1.xlsx new file mode 100644 index 0000000..c80fde2 Binary files /dev/null and b/parser/lectures_2025-1.xlsx differ diff --git a/parser/parse.py b/parser/parse.py new file mode 100644 index 0000000..6769b5a --- /dev/null +++ b/parser/parse.py @@ -0,0 +1,39 @@ +import pandas as pd +import json + +FILE_NAME = 'lectures_2025-1' +INVALID_SCHOOLS = ['유형2', '-', '대학', '연계전공'] + + +raw_lectures = pd.read_excel(f'{FILE_NAME}.xlsx') +schools = [school for school in raw_lectures['개설대학'].unique() if school not in INVALID_SCHOOLS] + +lectures = dict() + +for _, row in raw_lectures.iterrows(): + if row['학수번호'] not in lectures: + if row['개설대학'] in INVALID_SCHOOLS: + school = '기타' + else: + school = row['개설대학'] + + lectures[row['학수번호']] = { + 'id': row['학수번호'], + 'school': school, + 'name': row['교과목명'], + } + else: + if lectures[row['학수번호']]['school'] == '기타': + if row['개설대학'] not in INVALID_SCHOOLS: + lectures[row['학수번호']]['school'] = row['개설대학'] + + +result = [x for x in lectures.values()] + + +with open(f'{FILE_NAME}.json', 'w', encoding='utf-8') as f: + json.dump(result, f, ensure_ascii=False) + + + + diff --git a/prisma/migrations/20250122110201_add_is_active_to_studyroom/migration.sql b/prisma/migrations/20250122110201_add_is_active_to_studyroom/migration.sql new file mode 100644 index 0000000..899f4d3 --- /dev/null +++ b/prisma/migrations/20250122110201_add_is_active_to_studyroom/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "studyroom" ADD COLUMN "is_active" BOOLEAN NOT NULL DEFAULT true; diff --git a/prisma/schema/studyroom/studyroom.prisma b/prisma/schema/studyroom/studyroom.prisma index 38cd8df..3caff70 100644 --- a/prisma/schema/studyroom/studyroom.prisma +++ b/prisma/schema/studyroom/studyroom.prisma @@ -6,6 +6,7 @@ model Studyroom { minUsers Int @map("min_users") maxUsers Int @map("max_users") isCinema Boolean @map("is_cinema") + isActive Boolean @default(true) @map("is_active") createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") deletedAt DateTime? @map("deleted_at") diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9662d91..25781a1 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -11,6 +11,7 @@ import { UserModule } from 'src/user/user.module'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { configModule } from './modules/config.module'; +import { BatchModule } from 'src/batch/batch.module'; @Module({ imports: [ @@ -23,6 +24,7 @@ import { configModule } from './modules/config.module'; RestaurantModule, GodokModule, NoticeModule, + BatchModule, ], controllers: [AppController], providers: [AppService], diff --git a/src/attendance/attendance.controller.ts b/src/attendance/attendance.controller.ts index dfb6a93..0a17d7f 100644 --- a/src/attendance/attendance.controller.ts +++ b/src/attendance/attendance.controller.ts @@ -24,6 +24,9 @@ import { AssignmentAttendanceListDto } from './dto/assignment-attendance.dto'; import { CourseAttendanceListDto } from './dto/course-attendace-list.dto'; import { CourseAttendanceDto } from './dto/course-attendance.dto'; import { LectureAttendanceListDto } from './dto/lecture-attendance.dto'; +import { LectureListDto } from './dto/lecture-list.dto'; + +import * as LECTURES from '../../parser/lectures_2025-1.json'; @ApiTags('출석 API') @Controller('attendance') @@ -93,4 +96,12 @@ export class AttendanceController { ): Promise { return this.attendanceService.getAssignmentAttendance(user); } + + @Version('1') + @Get('lecture/list') + @ApiOperation({ summary: '강의 목록' }) + @ApiCreatedResponse({ type: LectureListDto }) + async getLectureList(): Promise { + return LectureListDto.from(LECTURES); + } } diff --git a/src/attendance/dto/lecture-list.dto.ts b/src/attendance/dto/lecture-list.dto.ts new file mode 100644 index 0000000..cc49731 --- /dev/null +++ b/src/attendance/dto/lecture-list.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { LectureInfo } from '../types/lecture-info'; + +export class LectureDto { + @ApiProperty({ + description: '학수번호', + type: String, + }) + id!: string; + + @ApiProperty({ + description: '강의명', + type: String, + }) + name!: string; + + @ApiProperty({ + description: '주관 학과', + type: String, + }) + school!: string; + + static from(lecture: LectureInfo) { + return { + id: lecture.id, + name: lecture.name, + school: lecture.school, + }; + } +} + +export class LectureListDto { + @ApiProperty({ + description: '강의 목록', + type: [LectureDto], + }) + lectures!: LectureDto[]; + + static from(lectures: LectureInfo[]) { + return { + lectures: lectures.map((lecture) => LectureDto.from(lecture)), + }; + } +} diff --git a/src/attendance/types/lecture-info.d.ts b/src/attendance/types/lecture-info.d.ts new file mode 100644 index 0000000..97097ab --- /dev/null +++ b/src/attendance/types/lecture-info.d.ts @@ -0,0 +1,5 @@ +export type LectureInfo = { + id: string; + name: string; + school: string; +}; diff --git a/src/batch/batch.controller.ts b/src/batch/batch.controller.ts new file mode 100644 index 0000000..dfca0a3 --- /dev/null +++ b/src/batch/batch.controller.ts @@ -0,0 +1,103 @@ +import { Body, Controller, Get, Post, UseGuards, Version } from '@nestjs/common'; +import { ApiHeader, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { BatchService } from './batch.service'; +import { StudyroomBatchInfoDto } from './dto/studyroomBatchInfo.dto'; +import { CronTimePayload } from './payload/cron-time.payload'; +import { AdminApiGuard } from 'src/auth/guard/admin.guard'; + +@ApiTags('배치 API') +@Controller('batch') +export class BatchController { + constructor(private readonly batchService: BatchService) {} + + @Version('1') + @ApiOperation({ + summary: '[어드민] 스터디룸 슬롯 크롤러 배치 정보 조회 API', + }) + @ApiOkResponse({ type: StudyroomBatchInfoDto }) + @UseGuards(AdminApiGuard) + @ApiHeader({ + name: 'admin-api-key', + description: 'API key for admin access', + required: true, + }) + @Get('studyroom') + async getStudyroom() { + return this.batchService.getStudyroomBatchInfo(); + } + + @Version('1') + @ApiOperation({ + summary: '[어드민] 스터디룸 슬롯 크롤러 배치 활성화 API', + }) + @UseGuards(AdminApiGuard) + @ApiHeader({ + name: 'admin-api-key', + description: 'API key for admin access', + required: true, + }) + @Post('studyroom/activate') + async activateStudyroomSlotCrawler() { + return this.batchService.activateStudyroomSlotCrawler(); + } + + @Version('1') + @ApiOperation({ + summary: '[어드민] 스터디룸 슬롯 크롤러 배치 비활성화 API', + }) + @UseGuards(AdminApiGuard) + @ApiHeader({ + name: 'admin-api-key', + description: 'API key for admin access', + required: true, + }) + @Post('studyroom/deactivate') + async deactivateStudyroomSlotCrawler() { + return this.batchService.deactivateStudyroomSlotCrawler(); + } + + @Version('1') + @ApiOperation({ + summary: '[어드민] 스터디룸 슬롯 크롤러 배치 헬스 체크 활성화 API', + }) + @UseGuards(AdminApiGuard) + @ApiHeader({ + name: 'admin-api-key', + description: 'API key for admin access', + required: true, + }) + @Post('studyroom/health-check/activate') + async activateStudyroomSlotCrawlerHealthCheck() { + return this.batchService.activateStudyroomSlotCrawlerHealthCheck(); + } + + @Version('1') + @ApiOperation({ + summary: '[어드민] 스터디룸 슬롯 크롤러 배치 헬스 체크 비활성화 API', + }) + @UseGuards(AdminApiGuard) + @ApiHeader({ + name: 'admin-api-key', + description: 'API key for admin access', + required: true, + }) + @Post('studyroom/health-check/deactivate') + async deactivateStudyroomSlotCrawlerHealthCheck() { + return this.batchService.deactivateStudyroomSlotCrawlerHealthCheck(); + } + + @Version('1') + @ApiOperation({ + summary: '[어드민] 스터디룸 슬롯 크롤러 배치 크론 시간 변경 API', + }) + @UseGuards(AdminApiGuard) + @ApiHeader({ + name: 'admin-api-key', + description: 'API key for admin access', + required: true, + }) + @Post('studyroom/cron-time') + async changeStudyroomSlotCrawlerCronTime(@Body() body: CronTimePayload) { + return this.batchService.changeStudyroomSlotCrawlerCronTime(body.cronTime); + } +} diff --git a/src/batch/batch.module.ts b/src/batch/batch.module.ts new file mode 100644 index 0000000..84c91e4 --- /dev/null +++ b/src/batch/batch.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { BatchController } from './batch.controller'; +import { BatchService } from './batch.service'; +import { AuthModule } from 'src/auth/auth.module'; + +@Module({ + imports: [AuthModule], + controllers: [BatchController], + providers: [BatchService] +}) +export class BatchModule {} diff --git a/src/batch/batch.service.ts b/src/batch/batch.service.ts new file mode 100644 index 0000000..1ae7282 --- /dev/null +++ b/src/batch/batch.service.ts @@ -0,0 +1,71 @@ +import { Injectable } from '@nestjs/common'; +import { SchedulerRegistry } from '@nestjs/schedule'; +import { StudyroomBatchInfoDto } from './dto/studyroomBatchInfo.dto'; +import { CronTime } from 'cron'; + +@Injectable() +export class BatchService { + constructor(private schedulerRegistry: SchedulerRegistry) {} + + async getStudyroomBatchInfo(): Promise { + const cronJob = this.schedulerRegistry.getCronJob( + 'studyroomSlotCrawler' + ); + + return { + isRunning: cronJob.running, + cronTime: cronJob.cronTime.source.toString(), + lastFiredAt: cronJob.lastDate(), + }; + } + + async activateStudyroomSlotCrawler() { + const cronJob = this.schedulerRegistry.getCronJob( + 'studyroomSlotCrawler' + ); + const healthCheckJob = this.schedulerRegistry.getCronJob( + 'studyroomSlotCrawlerHealthCheck' + ); + + cronJob.start(); + healthCheckJob.start(); + } + + async deactivateStudyroomSlotCrawler() { + const cronJob = this.schedulerRegistry.getCronJob( + 'studyroomSlotCrawler' + ); + const healthCheckJob = this.schedulerRegistry.getCronJob( + 'studyroomSlotCrawlerHealthCheck' + ); + + cronJob.stop(); + healthCheckJob.stop(); + } + + async activateStudyroomSlotCrawlerHealthCheck() { + const healthCheckJob = this.schedulerRegistry.getCronJob( + 'studyroomSlotCrawlerHealthCheck' + ); + + healthCheckJob.start(); + } + + async deactivateStudyroomSlotCrawlerHealthCheck() { + const healthCheckJob = this.schedulerRegistry.getCronJob( + 'studyroomSlotCrawlerHealthCheck' + ); + + healthCheckJob.stop(); + } + + async changeStudyroomSlotCrawlerCronTime(rawCronTime: string) { + const cronJob = this.schedulerRegistry.getCronJob( + 'studyroomSlotCrawler' + ); + + const cronTime = new CronTime(rawCronTime); + + cronJob.setTime(cronTime); + } +} diff --git a/src/batch/dto/studyroomBatchInfo.dto.ts b/src/batch/dto/studyroomBatchInfo.dto.ts new file mode 100644 index 0000000..2909874 --- /dev/null +++ b/src/batch/dto/studyroomBatchInfo.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { StudyroomInfoListDto } from "src/studyroom/dto/studyroom-infp.dto"; + +export class StudyroomBatchInfoDto { + @ApiProperty({ + description: '배치 실행 여부', + type: Boolean, + }) + isRunning: boolean; + + @ApiProperty({ + description: '배치 Cron 표현식', + type: String, + }) + cronTime: string; + + @ApiProperty({ + description: '배치 실행 시간', + type: Date, + }) + lastFiredAt: Date; +} diff --git a/src/batch/payload/cron-time.payload.ts b/src/batch/payload/cron-time.payload.ts new file mode 100644 index 0000000..ce9eedd --- /dev/null +++ b/src/batch/payload/cron-time.payload.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CronTimePayload { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'Cron 표현식', + example: '*/2 * * * * *', + }) + cronTime: string; +} diff --git a/src/notice/notice.controller.ts b/src/notice/notice.controller.ts index 92c27ba..cb50053 100644 --- a/src/notice/notice.controller.ts +++ b/src/notice/notice.controller.ts @@ -16,11 +16,11 @@ import { ApiOperation, ApiTags, } from '@nestjs/swagger'; +import { AdminApiGuard } from 'src/auth/guard/admin.guard'; +import { NoticePreviewDto } from './dto/notice-preview.dto'; import { NoticeDto } from './dto/notice.dto'; import { NoticeService } from './notice.service'; import { CreateUpdateNoticePayload } from './payload/create-update-notice.payload'; -import { NoticePreviewDto } from './dto/notice-preview.dto'; -import { AdminApiGuard } from 'src/auth/guard/admin.guard'; @ApiTags('공지사항 API') @Controller('notice') @@ -39,7 +39,7 @@ export class NoticeController { @Version('1') @ApiOperation({ - summary: '팝업 공지사항 등록 API', + summary: '[어드민] 팝업 공지사항 등록 API', description: '팝업 공지사항을 등록합니다.', }) @UseGuards(AdminApiGuard) @@ -55,7 +55,7 @@ export class NoticeController { @Version('1') @ApiOperation({ - summary: '공지사항 생성 API', + summary: '[어드민] 공지사항 생성 API', description: '공지사항을 생성합니다.', }) @UseGuards(AdminApiGuard) @@ -73,7 +73,7 @@ export class NoticeController { @Version('1') @ApiOperation({ - summary: '공지사항 수정 API', + summary: '[어드민] 공지사항 수정 API', description: '공지사항을 수정합니다.', }) @UseGuards(AdminApiGuard) @@ -92,7 +92,7 @@ export class NoticeController { @Version('1') @ApiOperation({ - summary: '공지사항 삭제 API', + summary: '[어드민] 공지사항 삭제 API', description: '공지사항을 삭제합니다.', }) @UseGuards(AdminApiGuard) diff --git a/src/studyroom/dto/studyroom-infp.dto.ts b/src/studyroom/dto/studyroom-infp.dto.ts new file mode 100644 index 0000000..3976e9c --- /dev/null +++ b/src/studyroom/dto/studyroom-infp.dto.ts @@ -0,0 +1,94 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { StudyroomInfo } from '../types/studyroomInfo.type'; + +export class StudyroomInfoDto { + @ApiProperty({ + description: '스터디룸 id', + type: Number, + }) + id!: number; + + @ApiProperty({ + description: '스터디룸 이름', + type: String, + }) + name!: string; + + @ApiProperty({ + description: '위치', + type: String, + }) + location!: string; + @ApiProperty({ + description: '최소 인원', + type: Number, + }) + minUsers!: number; + + @ApiProperty({ + description: '최대 인원', + type: Number, + }) + maxUsers!: number; + + @ApiProperty({ + description: '스터디룸 유형', + type: Boolean, + }) + isCinema!: boolean; + + @ApiProperty({ + description: '운영 시간', + type: String, + }) + operatingHours!: string; + + @ApiProperty({ + description: '스터디룸 태그', + type: [String], + }) + tags!: string[]; + + @ApiProperty({ + description: '스터디룸 활성화 여부', + type: Boolean, + }) + isActive!: boolean; + + @ApiProperty({ + description: '스터디룸 마지막 업데이트 시간', + type: Date, + }) + lastUpdatedAt!: Date; + + static from(studyroom: StudyroomInfo): StudyroomInfoDto { + return { + id: studyroom.id, + name: studyroom.name, + location: studyroom.location, + minUsers: studyroom.minUsers, + maxUsers: studyroom.maxUsers, + isCinema: studyroom.isCinema, + operatingHours: studyroom.operatingHours, + tags: studyroom.tags, + isActive: studyroom.isActive, + lastUpdatedAt: studyroom.lastUpdatedAt, + }; + } +} + +export class StudyroomInfoListDto { + @ApiProperty({ + description: '스터디룸 목록', + type: [StudyroomInfoDto], + }) + studyrooms: StudyroomInfoDto[]; + + static from(studyrooms: StudyroomInfo[]): StudyroomInfoListDto { + return { + studyrooms: studyrooms.map((studyroom) => { + return StudyroomInfoDto.from(studyroom); + }), + }; + } +} diff --git a/src/studyroom/payload/studyroomUpdate.payload.ts b/src/studyroom/payload/studyroomUpdate.payload.ts new file mode 100644 index 0000000..5cb1ae5 --- /dev/null +++ b/src/studyroom/payload/studyroomUpdate.payload.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean } from 'class-validator'; + +export class StudyroomUpdatePayload { + @ApiProperty({ + description: '활성화 여부', + example: 'true', + }) + @IsBoolean() + isActive!: boolean; +} diff --git a/src/studyroom/studyroom.controller.ts b/src/studyroom/studyroom.controller.ts index 584f605..39018bc 100644 --- a/src/studyroom/studyroom.controller.ts +++ b/src/studyroom/studyroom.controller.ts @@ -3,6 +3,8 @@ import { Controller, Get, Param, + ParseIntPipe, + Patch, Post, Query, UseGuards, @@ -12,6 +14,7 @@ import { ApiBadRequestResponse, ApiBearerAuth, ApiCreatedResponse, + ApiHeader, ApiNotFoundResponse, ApiOkResponse, ApiOperation, @@ -19,15 +22,18 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { CurrentUser } from 'src/auth/decorator/user.decorator'; +import { AdminApiGuard } from 'src/auth/guard/admin.guard'; import { JwtAuthGuard } from 'src/auth/guard/jwt-auth.guard'; import { PasswordPayload } from 'src/auth/payload/password.payload'; import { PasswordValidationPipe } from 'src/auth/pipes/signup-validation.pipe'; import { UserInfo } from 'src/auth/types/user-info.type'; +import { StudyroomInfoListDto } from './dto/studyroom-infp.dto'; import { StudyroomReservationListDto } from './dto/studyroom-reservation.dto'; import { StudyroomDto, StudyroomListDto } from './dto/studyroom.dto'; import { UserPidDto } from './dto/userPid.dto'; import { StudyroomCancelPayload } from './payload/studyroomCancel.payload'; import { StudyroomReservePayload } from './payload/studyroomReserve.payload'; +import { StudyroomUpdatePayload } from './payload/studyroomUpdate.payload'; import { StudyroomUserPayload } from './payload/studyroomUserPayload.payload'; import { StudyroomQuery } from './query/studyroom.query'; import { StudyroomDateQuery } from './query/studyroomDateQuery.query'; @@ -170,4 +176,48 @@ export class StudyroomController { ): Promise { return this.studyroomService.checkUserAvailablity(user.studentId, payload); } + + @Version('1') + @ApiOperation({ + summary: '[어드민] 스터디룸 정보 업데이트 API', + description: '스터디룸 정보를 업데이트 합니다.', + }) + @ApiOkResponse({ + description: '스터디룸 정보 업데이트 성공', + }) + @ApiNotFoundResponse({ + description: '해당 id의 스터디룸을 찾을 수 없습니다.', + }) + @UseGuards(AdminApiGuard) + @ApiHeader({ + name: 'admin-api-key', + description: 'API key for admin access', + required: true, + }) + @Patch('info/:id') + async updateStudyroom( + @Param('id', ParseIntPipe) id: number, + @Body() payload: StudyroomUpdatePayload, + ): Promise { + return this.studyroomService.updateStudyroom(id, payload); + } + + @Version('1') + @ApiOperation({ + summary: '[어드민] 스터디룸 정보 목록 조회 API', + description: '스터디룸 정보 목록을 조회합니다.', + }) + @ApiOkResponse({ + description: '스터디룸 정보 목록 조회 성공', + }) + @UseGuards(AdminApiGuard) + @ApiHeader({ + name: 'admin-api-key', + description: 'API key for admin access', + required: true, + }) + @Get('info/all') + async getAllStudyroomInfo(): Promise { + return this.studyroomService.getAllStudyroomInfo(); + } } diff --git a/src/studyroom/studyroom.module.ts b/src/studyroom/studyroom.module.ts index ae4523f..5cebbd3 100644 --- a/src/studyroom/studyroom.module.ts +++ b/src/studyroom/studyroom.module.ts @@ -6,9 +6,10 @@ import { StudyroomController } from './studyroom.controller'; import { StudyroomRepository } from './studyroom.repository'; import { StudyroomService } from './studyroom.service'; import { UserService } from 'src/user/user.service'; +import { AuthModule } from 'src/auth/auth.module'; @Module({ - imports: [ScheduleModule.forRoot()], + imports: [ScheduleModule.forRoot(), AuthModule], controllers: [StudyroomController], providers: [ StudyroomService, diff --git a/src/studyroom/studyroom.repository.ts b/src/studyroom/studyroom.repository.ts index 3bbe060..a1ecb8c 100644 --- a/src/studyroom/studyroom.repository.ts +++ b/src/studyroom/studyroom.repository.ts @@ -1,11 +1,13 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { StudyroomReservation as PrismaStudyroomReservation } from '@prisma/client'; import * as _ from 'lodash'; import { PrismaService } from 'src/common/services/prisma.service'; +import { StudyroomUpdatePayload } from './payload/studyroomUpdate.payload'; import { StudyroomQuery } from './query/studyroom.query'; import { StudyroomDateQuery } from './query/studyroomDateQuery.query'; import { ReservationResponse } from './types/reservationResponse.type'; import { Studyroom } from './types/studyroom.type'; +import { StudyroomInfo } from './types/studyroomInfo.type'; import { StudyroomReservationInfo } from './types/studyroomReservationInfo.type'; @Injectable() @@ -17,9 +19,91 @@ export class StudyroomRepository { return hour + ':00'; } + async getAllStudyroomInfo(): Promise { + const studyrooms = await this.prismaService.studyroom.findMany({ + where: { + deletedAt: null, + }, + select: { + id: true, + name: true, + location: true, + minUsers: true, + maxUsers: true, + isCinema: true, + operatingHours: true, + tags: true, + isActive: true, + }, + }); + + const lastUpdatedSlots = await this.prismaService.studyroomSlot.groupBy({ + by: ['studyroomId'], + _max: { + updatedAt: true, + }, + where: { + studyroomId: { + in: studyrooms.map((studyroom) => studyroom.id), + }, + }, + }); + + return studyrooms.map((studyroom) => { + return { + ...studyroom, + lastUpdatedAt: lastUpdatedSlots.find( + (slot) => slot.studyroomId === studyroom.id, + )?._max.updatedAt, + }; + }); + } + + async getStudyroomInfoById(id: number): Promise { + const studyroomInfo = await this.prismaService.studyroom.findUnique({ + where: { + id: id, + deletedAt: null, + }, + select: { + id: true, + name: true, + location: true, + minUsers: true, + maxUsers: true, + isCinema: true, + operatingHours: true, + tags: true, + isActive: true, + }, + }); + + if (!studyroomInfo) { + throw new NotFoundException('스터디룸이 존재하지 않습니다.'); + } + + const lastUpdatedSlot = await this.prismaService.studyroomSlot.findFirst({ + where: { + studyroomId: id, + }, + orderBy: { + updatedAt: 'desc', + }, + select: { + updatedAt: true, + }, + }); + + return { + ...studyroomInfo, + lastUpdatedAt: lastUpdatedSlot.updatedAt, + }; + } + async getAllStudyroomIds(): Promise { const studyrooms = await this.prismaService.studyroom.findMany({ where: { + isActive: true, deletedAt: null, }, select: { @@ -32,6 +116,7 @@ export class StudyroomRepository { async getAllStudyrooms(query: StudyroomQuery): Promise { const studyrooms = await this.prismaService.studyroom.findMany({ where: { + isActive: true, deletedAt: null, }, include: { @@ -375,4 +460,18 @@ export class StudyroomRepository { } } } + + async updateStudyroom( + id: number, + payload: StudyroomUpdatePayload, + ): Promise { + await this.prismaService.studyroom.update({ + where: { + id, + }, + data: { + isActive: payload.isActive, + }, + }); + } } diff --git a/src/studyroom/studyroom.service.ts b/src/studyroom/studyroom.service.ts index 9ccab40..5af5ab0 100644 --- a/src/studyroom/studyroom.service.ts +++ b/src/studyroom/studyroom.service.ts @@ -11,11 +11,13 @@ import { DiscordService } from 'src/common/services/discord.service'; import { PrismaService } from 'src/common/services/prisma.service'; import { UserRepository } from 'src/user/user.repository'; import { UserService } from 'src/user/user.service'; +import { StudyroomInfoListDto } from './dto/studyroom-infp.dto'; import { StudyroomReservationListDto } from './dto/studyroom-reservation.dto'; import { StudyroomDto, StudyroomListDto } from './dto/studyroom.dto'; import { UserPidDto } from './dto/userPid.dto'; import { StudyroomCancelPayload } from './payload/studyroomCancel.payload'; import { StudyroomReservePayload } from './payload/studyroomReserve.payload'; +import { StudyroomUpdatePayload } from './payload/studyroomUpdate.payload'; import { StudyroomUserPayload } from './payload/studyroomUserPayload.payload'; import { StudyroomQuery } from './query/studyroom.query'; import { StudyroomDateQuery } from './query/studyroomDateQuery.query'; @@ -46,15 +48,20 @@ export class StudyroomService { return parseInt(time.split(':')[0]); } - @Cron('*/2 * * * * *') + @Cron('*/2 * * * * *', { + name: 'studyroomSlotCrawler', + }) async handleCron() { if (this.configService.get('NODE_ENV') !== 'prod') { return; } - if (!this.studyroomIds.length) { + const now = new Date(); + + if (this.studyroomIds.length === 0 || now.getMinutes() === 0) { console.log('fetching studyroom ids'); this.studyroomIds = await this.studyroomRepository.getAllStudyroomIds(); + this.currentIndex = 0; } const roomId = this.studyroomIds[this.currentIndex]; @@ -94,10 +101,17 @@ export class StudyroomService { } } - @Cron('*/1 * * * *') + @Cron('*/1 * * * *', { + name: 'studyroomSlotCrawlerHealthCheck', + }) async healthCheck() { const recentStudyroomSlot = await this.prismaService.studyroomSlot.findFirst({ + where: { + studyroom: { + isActive: true, + }, + }, orderBy: { updatedAt: 'desc', }, @@ -198,4 +212,19 @@ export class StudyroomService { payload.cancelReason, ); } + + async updateStudyroom( + id: number, + payload: StudyroomUpdatePayload, + ): Promise { + const studyroomInfo = + await this.studyroomRepository.getStudyroomInfoById(id); + + await this.studyroomRepository.updateStudyroom(studyroomInfo.id, payload); + } + + async getAllStudyroomInfo(): Promise { + const studyrooms = await this.studyroomRepository.getAllStudyroomInfo(); + return StudyroomInfoListDto.from(studyrooms); + } } diff --git a/src/studyroom/types/studyroomInfo.type.ts b/src/studyroom/types/studyroomInfo.type.ts new file mode 100644 index 0000000..7711dc0 --- /dev/null +++ b/src/studyroom/types/studyroomInfo.type.ts @@ -0,0 +1,12 @@ +export type StudyroomInfo = { + id: number; + name: string; + location: string; + minUsers: number; + maxUsers: number; + isCinema: boolean; + operatingHours: string; + tags: string[]; + isActive: boolean; + lastUpdatedAt: Date; +}; diff --git a/tsconfig.json b/tsconfig.json index 95f5641..2b0c0e1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,7 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "resolveJsonModule": true } }