diff --git a/1_16.json b/1_16.json new file mode 100644 index 0000000..9b80967 --- /dev/null +++ b/1_16.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 47}, {"rank": 2, "user_id": 43}] \ No newline at end of file diff --git a/2_2.json b/2_2.json new file mode 100644 index 0000000..0fc431b --- /dev/null +++ b/2_2.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 83}] \ No newline at end of file diff --git a/2_7.json b/2_7.json new file mode 100644 index 0000000..0fc431b --- /dev/null +++ b/2_7.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 83}] \ No newline at end of file diff --git a/2_8.json b/2_8.json new file mode 100644 index 0000000..3f8028f --- /dev/null +++ b/2_8.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 83}, {"rank": 2, "user_id": 21}] \ No newline at end of file diff --git a/3_2.json b/3_2.json new file mode 100644 index 0000000..4e9bcee --- /dev/null +++ b/3_2.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 84}, {"rank": 2, "user_id": 18}, {"rank": 3, "user_id": 22}, {"rank": 4, "user_id": 49}] \ No newline at end of file diff --git a/3_7.json b/3_7.json new file mode 100644 index 0000000..f7db5a1 --- /dev/null +++ b/3_7.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 84}, {"rank": 2, "user_id": 18}, {"rank": 3, "user_id": 49}] \ No newline at end of file diff --git a/4_13.json b/4_13.json new file mode 100644 index 0000000..4202ce7 --- /dev/null +++ b/4_13.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 19}] \ No newline at end of file diff --git a/4_8.json b/4_8.json new file mode 100644 index 0000000..4202ce7 --- /dev/null +++ b/4_8.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 19}] \ No newline at end of file diff --git a/6_8.json b/6_8.json new file mode 100644 index 0000000..9931084 --- /dev/null +++ b/6_8.json @@ -0,0 +1 @@ +[{"rank": 1, "user_id": 87}] \ No newline at end of file diff --git a/build.gradle b/build.gradle index f72d8cc..296619c 100644 --- a/build.gradle +++ b/build.gradle @@ -24,14 +24,33 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' + implementation 'mysql:mysql-connector-java:8.0.23' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'org.springframework.boot:spring-boot-starter-security' + testImplementation 'org.springframework.security:spring-security-test' + + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + testAnnotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'com.googlecode.json-simple:json-simple:1.1.1' //웹소켓 implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'org.modelmapper:modelmapper:3.1.0' + + //파일 읽기 + implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.2' + implementation 'org.apache.poi:poi-ooxml:5.2.3' + + } tasks.named('test') { diff --git a/contestkorea_contest_data.json b/contestkorea_contest_data.json index ced8298..4a60167 100644 --- a/contestkorea_contest_data.json +++ b/contestkorea_contest_data.json @@ -1 +1 @@ -[{"contest_title": "2024 산림빅데이터 활용 해커톤", "contest_host": "한국임업진흥원, 산림빅데이터거래소/한국임업진흥원", "contest_target_participants": "누구나 , 유치원 , 초등학생 , 중학생 , 고등학생 , 대학생 , 대학원생 , 일반인 , 외국인", "contest_reception_period": "2024.02.01 ~ 2024.02.25", "contest_decision_period": "2024.03.15 ~ 2024.03.16", "contest_compatition_area": "온라인", "contest_award": "기타 :시상 및 포상", "contest_homepage": "http://bigdata-forest.kr/frn/support/pr/detail/27", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202402051053096465811.png", "contest_detail_text": "2024년산림빅데이터활용해커톤_공고문_제안서_개인정보활용동의서.hwp\n대회명\n『2024년도 산림빅데이터 활용 해커톤\n해커톤 주제\n산림빅데이터 및 인공지능 데이터 활용 사례 발굴 및 비즈니스 아이디어 구상\n운영기간\n2024.03.15(금) ~ 16(토)/2일간\n참가대상\n- (우선 참여대상) 산림빅데이터 활용 및 사업계획서 교육 참여자\n- 빅데이터, 인공지능 기반 비즈니스 모델 개발에 관심있는 누구나\n* 최대 9개팀 본선 진출 선정(팀당 1명 이상 자유롭게 구)\n활용 데이터\n- 산림빅데이터거래소 내 데이터 (https://www.bigdata-forest.kr)\n- 통합 데이터 지도 (https://www.bigdata-map.kr)\n- 공공데이터 포탈 내 데이터 (https://www.data.go.kr)\n운영방식\n온라인(ZOOM)\n신청접수\n2024.02.01(목) ~ 25(일)/자정까지\n신청방법\n- 해커톤 신청 방법 : 산림빅데이터 거래소(https://www.bigdata-forest.kr)\n[ 커뮤니티 > 생태계프로그램 > 게시글 확인 > 해커톤 신청 ]\n- 해커톤 신청 바로가기 : https://www.bigdataforest.kr/frn/support/pr/detail/27\n문 의\n- 운영팀 매니저 010-5497-3356\n- 운영팀 이메일 : ai@impentacle.com"}, {"contest_title": "양천구 스마트도시 문제해결 기업공모전", "contest_host": "양천구청/양천구청 스마트정보과", "contest_target_participants": "기타 (스마트 기술을 보유한 기업)", "contest_reception_period": "2024.02.01 ~ 2024.02.29", "contest_decision_period": "2024.03.01 ~ 2024.05.31", "contest_compatition_area": "서 울", "contest_award": "상금(총상금 : 1,000만원 / 1위 : 500만원)", "contest_homepage": "http://url.kr/3s85rq", "contest_how_to_apply": "이메일접수, 우편접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202402011410235835279.jpg", "contest_detail_text": "공고문(기업공모).hwp\n양천구 스마트도시 문제 해결 기업 공모 공고\n서울특별시 양천구에서는 지속가능한 스마트도시 양천 조성을 위해\n스마트도시 문제 해결 기업을 공모를 진행하고 있습니다.\n창의적인 아이디어를 가진 스마트기업의 많은 신청 바랍니다.\n공모분야\n양천구에서 발생하고 있는 각종 도시 문제 해결 또는 개선을 위해 스마트기술(IOT,빅데이터, AI, 기계학습 등)을 접목한 실효성 있는 아이디어\n예시) AI기반 빅데이터 수집, 재난관리 대피시스템, 스마트 교육 플랫폼,\n로봇을 활용한 효율적 환경청소, 스마트 횡단보도, 스마트헬스케어 등\n(IOT,빅데이터, AI,드론, 앱 등 스마트 기술 활용)\n참가자격\n양천구 스마트 도시 문제 해결에 관심 있는 기업\n접수기간\n2024.02.01 ~2024.02.29\n제출서류\n참여신청서, 사업계획서, 개인정보 수집 이용동의서\n양천구홈페이지내 구민참여-공지사항의 공고문 내용 을 참고바람\n제출방법\n이메일 : roseprince@yangcheon.go.kr\n우편 : (08095) 서울특별시 양천구 목동동로 105(신정동) 8층, 스마트정보과\n시상\n최우수상 : 500만원\n우수상 :300만원\n장려상 :200만원\n※ 공모상금 제세공과금 (4.4%) 수상자 부담\n문 의\n02-2620-3234"}, {"contest_title": "도배 하자 질의 응답 처리 : 한솔데코 시즌2 AI 경진대회", "contest_host": "한솔데코/데이콘", "contest_target_participants": "누구나 , 유치원 , 초등학생 , 중학생 , 고등학생 , 대학생 , 대학원생 , 일반인 , 외국인", "contest_reception_period": "2024.01.29 ~ 2024.03.11", "contest_decision_period": "2024.01.29 ~ 2024.03.11", "contest_compatition_area": "온라인", "contest_award": "상금(총상금 : 1,000만원 / 1위 : 6,000만원)", "contest_homepage": "http://dacon.io/competitions/official/236216/overview/description", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202401181850138406525.jpg", "contest_detail_text": "대회명\n도배 하자 질의 응답 처리 : 한솔데코 시즌2 AI 경진대회\n주제\n도배 하자 질의 응답 AI 모델 개발\n개인 또는 팀 참여 규칙\n개인 또는 팀을 이루어 참여할 수 있습니다.\n개인 참가 방법 : 팀 신청 없이, 자유롭게 제출탭에서 제출 가능\n팀 참가 방법 : 팀 탭에서 가능, 상세 내용은 팀 탭에서 팀 병합 정책 확인\n팀 구성 방법: 팀 페이지에서 팀 구성 안내 확인\n팀 최대 인원: 5 명\n동일인이 개인 또는 복수팀에 중복하여 등록 불가\nAPI, 외부 데이터 및 사전 학습 모델\n사용에 법적 제약이 없으며, 누구나 변경, 재배포할 수 있는 공개된 외부 데이터 사용 가능\n사용에 법적 제약이 없으며, 오픈소스로 공개된 사전 학습 모델(Pre-trained Model) 사용 가능\nAPI를 통한 외부데이터 수집, 데이터 전처리는 가능하나, API를 통한 추론은 불가능합니다. (Ex. ChatGPT API를 통한 추론 등 불가능)\n반드시 언어 모델 학습의 과정이 존재해야하며, 학습된 언어 모델을 바탕으로 추론이 이루어져야합니다.\n코드 및 PPT 제출 규칙\n대회 종료 후 2차 평가 대상자는 아래의 양식에 맞추어 코드와 PPT를 dacon@dacon.io 메일로 기한 내에 제출\n제출한 코드는 Private Score 복원이 가능해야 함\n유의 사항\n1일 최대 제출 횟수: 3 회\n사용 가능 언어: Python\n모든 csv 형식의 데이터와 제출 파일은 UTF-8 인코딩을 적용합니다.\n모델 학습과 추론에서 평가 데이터셋 정보 활용(Data Leakage)시 수상 제외\n모든 학습, 추론의 과정 그리고 추론의 결과물들은 정상적인 코드를 바탕으로 이루어져야하며, 비정상적인 방법으로 얻은 제출물들은 적발 시 규칙 위반에 해당됩니다.\n최종 순위는 선택된 파일 중에서 채점되므로 참가자는 제출 창에서 자신이 최종적으로 채점 받고 싶은 파일을 2개를 선택해야 함\n대회 직후 공개되는 Private 랭킹은 최종 순위가 아니며 코드 검증 후 수상자가 결정됨\n데이콘은 부정 제출 행위를 금지하고 있으며 데이콘 대회 부정 제출 이력이 있는 경우 평가가 제한됩니다. 자세한 사항은 아래의 링크를 참고해 주시기 바랍니다.\n세부일정\n- 대회 기간 : 2024년 01월 29일 10:00 ~ 2024년 03월 11일 10:00\n- 팀 병합 마감 : 2024년 03월 04일 23:59\n- 대회 종료 : 2024년 03월 11일 10:00\n- 코드 및 PPT 제출 마감 : 2024년 03월 14일 23:59\n- 코드 검증 및 2차 평가 : 2024년 03월 15일 00:00 ~ 2024년 03월 24일 23:59\n- 최종 수상자 발표 : 2024년 03월 25일 10:00\n상금\n[총 상금 1,000만 원]\n1위 - 600만 원\n2위 - 300만 원\n3위 - 100만 원\n주최\n한솔데코\n참가자격\n일반인, 학생 등 누구나 대회 참가 가능"}, {"contest_title": "24년상반기 SW개발공모전(SW개발아이디어 및 결과물)", "contest_host": "전북IT기업협회 회원사 한국스마트정보교육원", "contest_target_participants": "대학생 , 대학원생 , 일반인", "contest_reception_period": "2024.01.31 ~ 2024.06.28", "contest_decision_period": "2024.07.01 ~ 2024.07.05", "contest_compatition_area": "전 북", "contest_award": "상금+상품(총상금 : 100만원 / 1위 : 50만원 / 상품 : 상금을 문화 상품권으로 대처 가능)", "contest_homepage": "http://ksmart.or.kr/?c=5/9&type=notice&mod=view&type=notice&idx=bbs_content_24013000003", "contest_how_to_apply": "이메일접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202401311424280133713.jpg", "contest_detail_text": "담당자 TIP\n아이디어만 제출 하실 수도 있지만 결과물을 함께 제출하면 유리합니다.\n24년1회차_참가자명(팀명)__공모전신청서(마감일_24년06월28일)_한국스마트정보교육원_제출일자.doc\n공모전명\n24년상반기 SW개발공모전(SW개발아이디어 및 결과물)\n참가자격\n- 대학 재학생 / 졸업생 개인 또는 팀\n- 일반인 개인 또는 팀\n접수기간\n24년01월31일~24년06월28일까지\n주제\n1) 생성형AI 응용 SW (웹,어플 결과물)\n2) H/W연계 가정 또는 구현한 IOT 응용 SW (웹,어플 결과물)\n3) 정부,기업 Open API 응용 SW (웹,어플 결과물)\n4) 기타 응용 SW (웹,어플 결과물)\n참가 분류1\n1)아이디어만 제출\n2)아이디어 및 결과물(실행 가능 방법 및 개발문서,DB,소스코드 압축)\n참가 분류2\n1)개인\n2)팀\n참가신청 방법 및 마감\n- 2024년 6월 28일까지 (신청서 접수 시 아이디어 및 결과물 최종 제출)\n\n- 제출방법(이메일) : sugang@ksmart.or.kr\n- 제출시 제목 : 24년1회차_참가자명(팀명)__공모전신청서(마감일_24년06월28일)_한국스마트정보교육원_제출일자\n- 첨부파일 : 1. 공통 :공모전서 2. 결과물 : 압축파일 하나로 제출\n\n\n1)아이디어만 제출 또는 2)아이디어 및 결과물 공통 내용\n* 공통 : 프로젝트명 / 사용자(개인,기업,정부) / 목적(적합성,독창성,사업화가능성,서비스확장성,기타) / 기대효과 / 주요기능 / 보완해야 할 점\n\n2)아이디어 및 결과물 추가 제출\n* 실행 가능 경로(실행방법 소개) / SW개발환경 / 개발문서 / 소스코드 / DB(ERD,백업파일)\n평가 기준\n1)필수작성 적합성-목적-10점\n2)필수작성 독창성-목적-10점\n3)필수작성 사업성-기대-10점\n4)필수작성 서비스확장성- 기대-10점\n5)필수작성 강점-10점\n6)필수작성 주요기능-10점\n7)결과물 제출 필수작성 SW개발환경 -10점\n8)결과물 제출 필수작성 개발문서 등 -10점\n9)결과물 제출 필수작성 소스코드 -20점\n10)선택사항 보완해야 할 점\n평가 방법 및 평가위원\n- 평가 방법 : 평가 배점 기준으로 평가 함\n- 평가 위원 : 전북IT기업협회 회원사 대표 및 임원진\n시상\n- 상장 : 1등(최우수상) / 2등(우수상) / 3등(장려상)\n- 상금(상품) :\n1등(최우수상 1명 또는 1팀) : 50만원 (상금 또는 상품)\n2등(우수상 1명 또는 1팀) : 20만원 (상금 또는 상품)\n3등(장려상 3명 또는 3팀) : 10만원 (상금 또는 상품)\n* 전북IT기업협회 회원사 후원금 추가 지급\n공모전 참가자 및 당선인 특전\n- 한국스마트정보교육원 무료교육 기회 제공\n- 전북IT기업협회 회원사 취업 기회 제공\n주관\n전북IT기업협회 회원사 한국스마트정보교육원\n주최·주관사 SNS 채널\n한국스마트정보교육원"}, {"contest_title": "2024년 광진구 빅데이터 분석 공모전", "contest_host": "서울특별시 광진구", "contest_target_participants": "누구나 , 유치원 , 초등학생 , 중학생 , 고등학생 , 대학생 , 대학원생 , 일반인 , 외국인", "contest_reception_period": "2024.04.01 ~ 2024.05.03", "contest_decision_period": "2024.05.13 ~ 2024.06.14", "contest_compatition_area": "전 국", "contest_award": "상금(총상금 : 590만원 / 1위 : 300만원)", "contest_homepage": "http://gwangjin.go.kr/portal/bbs/B0000003/view.do?nttId=6206346&menuNo=200192", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202402221527345433746.jpg", "contest_detail_text": "[공고문] 2024년 광진구 빅데이터 분석 공모전.pdf\n[붙임1] 공모전 제출 서식.hwpx\n[붙임2] 민간데이터 반출 정책 및 서식(2024년 광진구 빅데이터 분석 공모전).pdf\n공모전명\n2024년 광진구 빅데이터 분석 공모전\n공모주제\n광진구 현안 관련 자유주제\n참가자격\n광진구에 관심 있는 전 국민\n응모형식\nPDF 형식으로 20매 이내의 분석결과서 및 1매의 요약본 제출\n접수기간\n2024. 4. 1.(월) ~ 5. 3.(금) 23:59\n응모방법\n온라인 접수(광진구 홈페이지 ▶ 온라인정책방 ▶ 제안하기 ▶ 특별공모) 로만 가능하며, 마감 시간 이후 접수는 어떠한 경우에도 불가능합니다.\n민간데이터 제공\n대 상 : 유동인구 및 카드매출 데이터\n신청기간 : 2024. 3. 11. 10:00 ~ 3. 17. 23:59\n신청방법 : 온라인 신청(선착순)\n광진구청 홈페이지 ▶ 참여소통 ▶ 온라인접수\n※ 민간데이터 제공은 반출정책을 준수해야하며, 자세한 사항은 광진구 홈페이지에서 확인하시기 바랍니다.\n결과 발표\n예선심사 결과 발표 : 2024. 5. 30.\n본선심사 결과 발표 및 시상 : 2024. 6. 21.\n시상내역\n대상 1팀 300만원\n최우수상 2팀 100만원\n우수상 3팀 30만원\n문의처\n서울특별시 광진구청 스마트정보담당관\n이 메 일 : jeongho96@gwangjin.go.kr\n전화번호 : 02-450-7234\n광진구청 홈페이지 → 새소식 및 홈페이지 → 정보광장 → 스마트광진 (https://www.gwangjin.go.kr/gooddata/) → 공지사항을 참고하시기 바랍니다."}, {"contest_title": "제2회 IT 코딩 발명 아이디어 경진대회", "contest_host": "사단법인 행복일자리운동본부", "contest_target_participants": "초등학생 , 중학생 , 고등학생", "contest_reception_period": "2024.03.01 ~ 2024.03.31", "contest_decision_period": "2024.04.01 ~ 2024.04.30", "contest_compatition_area": "온라인", "contest_award": "없음", "contest_homepage": "http://www.happydreamjob.kr/board/board_view.php?bbs_id=notice&kbbs_doc_num=62", "contest_how_to_apply": "이메일접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202402221201463927508.jpg", "contest_detail_text": "1. 공고문_ 제2회 IT코딩발명 아이디어경진대회.pdf\n2. 제출서류(양식)_ 제2회 IT코딩발명 아이디어경진대회.hwp\n대회명\n제2회 IT 코딩 발명 아이디어 경진대회\n목적\n장애에 대한 인식을 개선하고 장애인의 일상생활에 대한 이해도를 높이기 위하여 IT 코딩에 기반한 발명을 통해\n1) 장애인이 일상 속에서 겪는 불편함을 해소하거나\n2) 후천적 장애인이 발생하는 것을 막기 위함\n참가대상\n전국 초, 중, 고교 재학생 또는 이에 준하는 청소년 개인 또는 팀 (5명 이내)\n공모내용\nIT 코딩 발명 아이디어 (제품 또는 서비스)\n* 시제품제작이 불가한 경우 수상할 수 없음\n대회일정\n참가신청 : 온라인 3.1 (금)~3.31(일)\n결과발표 : 개별공지 24년 4월 중\n시상식 : 오프라인 4.20(토) 장애인의 날\n시상내역\n대상 2팀 / 금상 3팀 / 은상 6팀 / 동상 15팀 / 장려상 총 30팀 이내\n접수기간\n2024년 3월 1일 (금) ~ 3월 31일 (일) 24시까지\n접수방법\n홈페이지에서 참가신청서 다운로드 후 이메일 접수\n접수 이메일 happydream1030@naver.com\n주최\n사단법인 행복일자리운동본부\n문의\n02-769-1030"}, {"contest_title": "제2회 청소년 IT경시대회", "contest_host": "한국정보기술진흥원", "contest_target_participants": "초등학생 , 중학생 , 고등학생", "contest_reception_period": "2024.02.26 ~ 2024.03.14", "contest_decision_period": "2024.03.16 ~ 2024.03.24", "contest_compatition_area": "온라인", "contest_award": "상품(상장, 상패, 공기청정기, 무선이어폰, 문화상품권 등)", "contest_homepage": "http://kitpa.org/contest-2nd/", "contest_how_to_apply": "온라인접수", "contest_fee": "유료 접수 (1인 40,000원) 부터", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202402231542518163888.png", "contest_detail_text": "담당자 TIP\n하단 정보와 후기에 기출문제를 포함한 예시 공개문제 (고등부 기준)를 다운받아 참고하시면 도움이 됩니다.\n\nㅇ 기출문제\n- [제1회 대회 기출문제] 프로그래밍 언어 (C언어)\n- [제1회 대회 기출문제] 프로그래밍 언어 (Python)\n- [ 1회 대회] 점수 분포도\n\nㅇ 공개문제\n- [공개문제] C언어\n- [공개문제] Python\n- [공개문제] 알고리즘\n- [공개문제] 데이터분석 (준비중)\n제2회 청소년 IT경시대회 대회요강 (240219).pdf\n제2회 청소년 IT경시대회\n프로그래밍 언어 (C언어), 프로그래밍 언어 (Python), 알고리즘, 데이터분석 총 4개 부문이 서로 다른 시간에 개최되기 때문에 동시에 나갈 수 있습니다. (알고리즘 부문과 데이터분석 부문은 동시 개최입니다.)\n참가자\n전국 초, 중, 고등학생 또는 그에 준하는 청소년\n(비재학생의 경우, 출생년도에 따라 부문 결정)\n접수기간\n2024. 2. 26 (월) ~ 3. 10 (일)\n추가접수: 2024. 3. 11 (월) ~ 3. 14 (목)\n접수부문\n프로그래밍 언어 (C언어), 프로그래밍 언어 (Python), 알고리즘, 데이터분석 (Python)\n대회일시\n2023년 9월 16일 (토) (예정)\n- 프로그래밍 언어 (C언어) 부문 (초.중.고) – 9:00 ~ 11:00\n- 프로그래밍 언어 (Python) 부문 (초.중.고) – 11:30 ~ 13:30\n- 알고리즘 부문 (초.중.고) – 14:00 ~ 17:00\n- 데이터분석 부문 (중.고) – 14:00 ~ 15:30\n*장소 : 온라인 개최\n참가신청\n홈페이지 원서 접수\n*전형료 40,000원 (추가접수 기간 50,000원)\n결과발표\n2024년 3월 25일 (월) 오후 6시 이후\n시상\n- 대상 1명\n- 금상 2명\n- 은상 3명\n- 동상 상위 5%\n- 장려상 상위 20%\n- 단체대상 3개단체\n\n(개인)대상 수상자 무선 이어폰, USB, 에코백 등 / 은상 이상 수상자 USB, 에코백, 문화상품권 등 / 장려상 이상 수상자 USB, 에코백 등\n(단체)대상 수상 단체 공기청정기\n문의\n한국정보기술진흥원 교육기획본부 대회운영팀 메일 (contest@kitpa.org)\n관련 정보 및 후기\n[ 1회 대회 기출문제] C언어\n한국정보기술진흥원\n[ 1회 대회 기출문제] Python\n한국정보기술진흥원\n[ 1회 대회] 점수 분포도\n한국정보기술진흥원\n[공개문제] C언어\n한국정보기술진흥원\n[공개문제] Python\n한국정보기술진흥원\n[공개문제] 알고리즘\n한국정보기술진흥원"}] \ No newline at end of file +[{"contest_title": "대전충청권 직장인 발로란트 대회 개최", "contest_host": "대전정보문화산업진흥원", "contest_target_participants": "일반인 , 기타 (직장인)", "contest_reception_period": "2024.05.22 ~ 2024.06.04", "contest_decision_period": "2024.06.08 ~ 2024.06.15", "contest_compatition_area": "대 전", "contest_award": "상금(총상금 : 30만원 / 1위 : 20만원)", "contest_homepage": "http://www.instagram.com/p/C7QeUQNN4a0/?utm_source=ig_web_copy_link&igsh=MzRlODBiNWFlZA==", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405221512062235982.jpg", "contest_detail_text": "■ 대전충청권 직장인 발로란트 대회\n직장인 VAL 대전\n■ 참가대상\n충청권 소재지 직장인(근로자)\n■ 접수기간\n2024.5.22.(수)~2024.6.4.(화)\n■ 참가인원\n최대 8팀 선착순 마감(팀당 최대 6명)\n■ 대회일정\n예선 : 2024.6.8.(토) - 온라인\n결승 : 2024.6.15.(토) - 오프라인(대전이스포츠경기장에서 진행)\n■ 상금\n우승 - 20만원\n준우승 - 10만원\n■ 문의\n대회 공식 인스타그램 DM\n■ 주최·주관사 SNS 채널\n"}, {"contest_title": "제3회 ETRI 휴먼이해 인공지능 논문경진대회", "contest_host": "ETRI/과학기술정보통신부 / 국가과학기술연구회 / AIFactory", "contest_target_participants": "대학생 , 대학원생 , 일반인", "contest_reception_period": "2024.04.18 ~ 2024.06.07", "contest_decision_period": "2024.06.28 ~ 2024.09.10", "contest_compatition_area": "온라인", "contest_award": "상금(총상금 : 690만원 / 1위 : 200만원)", "contest_homepage": "http://aifactory.space/task/2790/overview", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202404161500592657292.jpg", "contest_detail_text": "■ 공모전명\n제3회 ETRI 휴먼이해 인공지능 논문경진대회\n■ 주제\n- 라이프로그 데이터를 이용한 수면, 감정, 스트레스 인식 및 추론\n- 데이터셋 개요: 실생활 환경에서의 일상 행동을 정량적으로 측정한 ETRI 라이프로그 데이터셋\n■ 참가대상\n- 국내 학교(대학교, 대학원 등) 및 기업 등에 소속된 개인 및 연구팀 또는 일반 성인\n- 팀 대표자는 상금 수령이 가능한 국내 계좌를 보유하고 있는 대한민국 국적자여야 합니다.\n- 1인 팀으로도 참가 가능하며 팀 구성 시 인원 4인 이내 제한\n■ 기간 및 일정\n- 참가신청 : 4월 18일(목) ~ 6월 7일(금), 18:00 까지\n- 온라인 사전설명회 : 4월 25일(금), 17:00\n- 결과물 제출 마감: 6월 28일(금)\n- 논문 접수 마감일: 6월 28일(금)\n- 논문 채택 통지(예정): 9월 10일(화)\n- 포스터 발표 및 시상식: 10월 16일(수)\n※ 대회의 원활한 진행을 위하여 상세일정은 변경될 수 있음을 사전 안내드립니다.\n■ 상금(총 상금 690만원, 특전)\n- 대상(1팀): 200만원(과학기술정보통신부 장관상)\n- 최우수상(1팀): 150만원(과학기술정보통신부 장관상)\n- 우수상(1팀): 100만원(과학기술정보통신부 장관상)\n- 장려상(4팀): 60만원(한국전자통신연구원장상)\n■ 문의\n- 인공지능팩토리 운영사무국: cs@aifactory.page\n"}, {"contest_title": "코드게이트 AI 아이디어랩", "contest_host": "사단법인코드게이트보안포럼, 금융보안원/사단법인코드게이트보안포럼, 금융보안원", "contest_target_participants": "누구나 , 유치원 , 초등학생 , 중학생 , 고등학생 , 대학생 , 대학원생 , 일반인 , 외국인", "contest_reception_period": "2024.04.29 ~ 2024.06.07", "contest_decision_period": "2024.04.29 ~ 2024.08.29", "contest_compatition_area": "온라인", "contest_award": "상금(총상금 : 2,000만원 / 1위 : 1,000만원)", "contest_homepage": "http://www.xn--ai-h41ir8ydiaw0lto5awzac9ida382tyjj.com/", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405021016062493120.jpg", "contest_detail_text": "■ 공모전명\n코드게이트 AI 아이디어랩\n■ 참가 자격\n- 제한 없음. AI에 관심 있는 누구나\n* 접수일 기준 만 14세 미만 미성년자는 법정대리인의 동의 필요\n- 개인 또는 팀 구성(4인 이내), 자유 참가\n■ 공모 주제\n- 보안 문제 해결을 위한 AI 활용 기술 또는 서비스 아이디어\n- 산업, 문화, 경제 등 사회 전반적인 보안 문제\n- 현재 또는 미래에 발생 가능성이 있는 문제\n- 공격·방어 기술 모두 가능\n(예시1) 보이스피싱, 딥페이크·딥보이스, 전자상거래, SNS 투자사기, 디지털 범죄, 웜GPT 등 새로운 유형의 사이버 위협에 대하여 AI를 활용한 예방 대책\n(예시2) AI 시스템 보안을 위한 AI 활용 방안\n■ 시상 내역\n1등 1,000만원\n총 2,000만원\n이외 포스터 이미지 및 공식 홈페이지 참고\n■ 공모 일정\n(1차) 서류제출 및 기획서 평가\n접수일정 4.29.(월)~6.7.(금) 17:00 마감\n결과발표 6.17.(월)\n\n(2차) 예선\n접수일정 6.18.(화)~7.19.(금) 17:00 마감\n결과발표 7.29.(월)\n\n(3차) 본선\n접수일정 8.29.(목)\n결과발표 8.29.(목)\n\n시상식 8.30.(금)\n\n※ 일정은 신청 접수 현황에 따라 다소 변동될 수 있음\n※ 신청 마감일에는 접수 지연이 발생할 수 있으므로 사전에 접수 권장\n■ 제출 형식 및 심사 방법\n[심사방식]\n\n① (1차) 서류 및 기획서 평가 ∣ 온라인 응모페이지 제출\n\n- 기획서 5pg 이내, 지정 양식의 항목에 따라 작성\n- 기획서는 PDF로 저장하여 제출\n- 제출 서류 : 참가자서약서, 기획서\n- 본선 진출 팀의 3배수(30팀) 선발\n\n※ 1차는 블라인드 평가로 진행\n※ 기획서 양식에 이름, 팀명, 소속 등 참가자를 식별할 수 있는 정보는 기재하지 말 것\n\n② (2차) 예선심사 1차 기획서 통과 팀에 한함 ∣ 온라인 응모페이지 제출\n- 1차 기획서 구체화 영상 제출\n- 5분 이내 영상, 응모사이트에 업로드 (mp4, 최대 500mb)\n- 최종 본선 진출 10팀 선발\n\n※ 기획서 구체화 영상의 시간 길이를 반드시 준수해야 하며, 미준수 시 탈락 처리\n\n③ (3차) 본선 발표∣서울, 코엑스 그랜드볼룸 101호\n- 오프라인 PT 발표평가\n- 발표 15분, Q&A 10분\n\n[심사위원]\n- 정보보호 및 AI 분야 전문가 7인\n\n[심사기준]\n창의적이고 우수한 아이디어 선발을 위한 종합평가\n■ 접수 방법\n- 「코드게이트 AI 아이디어랩 공모전」응모페이지를 통한 온라인 신청\n(응모페이지: www.코드게이트ai아이디어랩.com)\n\n※ 신청 접수 시 ‘공모전 참가 확인서’는 별도 발급하지 않음\n■ 문의 사항\n- ˹코드게이트 AI 아이디어랩 공모전˼ 운영사무국\n- 연락처: 031-212-2027\n- 이메일: idea.codegate@gmail.com\n"}, {"contest_title": "제22회 임베디드 소프트웨어 경진대회", "contest_host": "산업통상자원부/임베디드소프트웨어·시스템산업협회(KESSIA)", "contest_target_participants": "초등학생 , 중학생 , 고등학생 , 대학생 , 대학원생 , 일반인", "contest_reception_period": "2024.05.02 ~ 2024.06.07", "contest_decision_period": "2024.06.10 ~ 2024.11.29", "contest_compatition_area": "온라인", "contest_award": "상금(총상금 : 4,270만원 / 1위 : 500만원)", "contest_homepage": "http://www.eswcontest.or.kr/about/summary.php", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405131414158374092.jpg", "contest_detail_text": "■ 대회명\n제22회 임베디드 소프트웨어 경진대회\n■ 대회목적\n창의적이고 혁신적인 임베디드 SW 개발 아이디어 획득\n임베디드 SW와 산업 간의 협업 아이디어 제공\n임베디드 SW 분야 우수 인력 발굴 및 양성\n임베디드 SW에 대한 범 국민적 인식 제고 및 글로벌 저변 확대\n■ 참가자격\n일반 부문은 누구나 참여가 가능하지만, 주니어 부문은 청소년만 참여가 가능합니다.\n■ 대회일정\n01. 6월 예선 (2024.05.02 ~ 2024.06.07 14:00)\n장비 지원 분야(webOS, 자동차/모빌리티, 메이커 히어로즈, 지능형 휴머노이드, 자율주행 레이싱) 참가 신청 및 개발계획서 접수 기간입니다.\n기한 내에 접수가 가능하니, 반드시 날짜를 확인바랍니다.\n\n02. 교육 (07~09월)\n결선진출팀(webOS, 자동차/모빌리티, 메이커 히어로즈, 지능형 휴머노이드, 자율주행 레이싱) 대상 각 부문별 기술 지원 교육이 월 1회 진행됩니다.\n(상기 일정 및 교육 횟수는 변동될 수 있습니다.)\n\n03. 9월 예선 (2024.05.02 ~ 2024.09.05)\n자유공모, 틴 스타트업 부문 개발완료보고서 접수입니다.\n기한 내에 접수가 가능하니, 반드시 날짜를 확인 바랍니다.\n\n04. 결선 (11월)\n전 부문 결선진출팀 대상으로 발표(전시) 및 경기 심사를 통해 우승팀을 선정하는 과정입니다.\n\n05. 시상식 (11월)\n결선 심사에서 우수한 성적을 거둔 학생들의 시상식입니다.\n반년 동안 임베디드SW경진대회를 위해 노력한 학생들의 축제입니다.\n■ 주최\n산업통상자원부\n■ 주관\n임베디드소프트웨어·시스템산업협회(KESSIA)\n■ 문의\n임베디드SW경진대회 사무국\nTel. 02-2046-1435\nE-mail. contest@fkii.org\n"}, {"contest_title": "K-디지털 챌린지 : 2024년 메타버스 개발자 경진대회", "contest_host": "과학기술정보통신부/한국전파진흥협회, 한국메타버스산업협회, 정보통신산업진흥원", "contest_target_participants": "초등학생 , 중학생 , 고등학생 , 대학생 , 일반인", "contest_reception_period": "2024.05.07 ~ 2024.06.07", "contest_decision_period": "2024.06.10 ~ 2024.09.13", "contest_compatition_area": "온라인", "contest_award": "상금+상품(총상금 : 32,800만원 / 1위 : 2,000만원 / 상품 : 장관상 및 후원사 대표상, 인턴십 기회, 공동사업화 기회)", "contest_homepage": "http://www.metaversedev.kr/", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405241454148714753.jpg", "contest_detail_text": "■ 담당자 TIP\nMR 서비스나 프로그램이 소비자에게 전달할 수 있는 효용에 대한 고민이 필요합니다.\nSpatial Computing/ MR 환경만이 해결할 수 있는 문제 상황을 탐색하는 것부터 시작해 보세요.\n■ 2024 메타버스 개발자 경진대회: 위에이알 지정과제\n애플 비전 프로가 출시되며 XR 생태계는 한층 더 확장했습니다.\n올해 6월, 비전 프로의 한국 출시 확정 소식에 따라 여러 선제적인 움직임이 시작되고 있습니다.\n다가올 거대한 XR 미래를 위에이알과 함께 도전해 보세요.\n\n(본 공고는 과학기술정보통신부에서 주관하는 2024 메타버스 개발자 경진대회에서 위에이알의 과제만 안내합니다.\n이 점 양지 부탁드립니다.)\n■ 참가자격\n메타버스 서비스·콘텐츠 개발에 관심 있는 성인 (대학(원)생, 일반인)\n※ 대학(원)생 및 일반인 1인 또는 팀 단위 신청 모두 가능(최대 5인), 법인 참여 불가\n■ 참가분야\n취업과제, 창업과제, 자유과제로 구분\n※ 상세 내용은 홈페이지 참조\n■ 참가혜택\n참가팀 대상으로 개발장비, 교육, 교육훈련비 지원\n\n< 참가팀 지원내역 >\nㅇ (개발장비) Apple Vision Pro(2), Meta Quest2(20), Meta Quest(4), HTC VIVE(2), HTC VIVE CosMos(1), 노트북(30), 피앤씨솔루션 Metalense2(10), 퀄컴 스냅드래곤스페이시스 DevKit(10), 모션캡쳐 시스템(1) 등 80개\n※ 개발 장비는 한정된 수량에 따라 팀당 1개만 신청 가능하며, 개발계획서 검토 후 선별지원 예정\nㅇ (교육지원) 메타버스 아카데미 교육강좌, 유니티 인증 시험 교육강좌(UNITY ALP코스 UCA), 3D콘텐츠 크리에이터(입문) 및 3D 게임 제작(기초) 등 교육영상\nㅇ (교육훈련비) 성인부 ‘메타버스 아카데미’ 분야 2차 합격자에게 1개월간 최대 100만원 지원\n※ 참가팀은 별도 온라인 교육 수강 및 멘토링 보고서(2회) 제출, 3차 평가용 결과물을 제출해야 하며, 외국인은 교육훈련비 지원 불가\n■ 추진일정\n참가 신청·접수(5.7.~ 6.7.) > 오리엔테이션(6.12.) > 교육·기술지원(6.7.~ 10.18.) > 출품작 접수 마감(8.7.) > 1차 평가(서면)(8.9.) > 2차 평가(발표/시연) (8.14.) > 멘토링(8.19.~ 9.9.) > 3차 평가(발표/시연)(9.12.) > 최종 평가(발표/시연)(9.13.) > 시상식/후속지원\n(10.18. / 10.1.~ 12.31.)\n■ 후속지원\n○ 대회 수상팀의 역량강화 및 성과확산을 위한 후속지원\n① (인턴십) 대회 취업과제 수상팀 중 후원기업 인턴십 희망자를 대상으로 면접 평가하여 인턴십 지원(10~12월)\n- (지원내용) 인턴십(세전 월 급여 250만원) 및 취업 컨설팅\n※ (지원기간) 3개월(10월 1일~ 12월 31일) / (지원조건) 월별 출근부 제출\n② (창업컨설팅) 대회 창업과제 수상팀 중 창업 희망자를 대상으로 창업컨설팅 및 창업훈련비 등 창업지원(10~12월)\n- (지원내용) 창업훈련비(1인 최대 월 100만원) 제공 및 창업컨설팅(IR 자료 제작, 멘토링, 교육, 법인설립 지원 등), 개발 공간(메타버스 아카데미) 제공\n※ (지원기간) 3개월(10월 1일~12월 31일) / (지원조건) 월간 보고서, 메타버스 아카데미 성과보고회 전시 참가, 창업 증빙 서류 제출(사업자등록증), 외국인은 창업훈련비 지원 불가\n③ (K-디지털 그랜드 챔피언십) 대회 최우수 수상팀에게 ‘K-디지털 그랜드 챔피언십’ 참여기회 제공(11~12월)\n④ (성과확산) 수상자에게는 후원기업과 공동사업화 기회 제공 및 취업 혜택 제공, 결과물 홍보(홈페이지, 우수사례 모음집 제작)\n■ 신청방법\n참가 접수 : 2024.05.07.(화) ~ 2024.06.07.(금)\n접수방법 : 2024년 메타버스 개발자 경진대회 홈페이지 ‘온라인 접수’\n제출물: 개발 계획서\n■ 주최\n과학기술정보통신부\n■ 주관\n한국전파진흥협회, 한국메타버스산업협회, 정보통신산업진흥원\n■ 문의\n2024년 메타버스 개발자 경진대회 운영사무국\nTel : 02-305-5002\nE-mail : contest2024@metaversedev.kr\n카카오톡 플러스친구에서 ‘2024년 메타버스 개발자 경진대회‘ 검색\n■ 주최·주관사 SNS 채널\n"}, {"contest_title": "코드게이트 AI 아이디어랩", "contest_host": "사단법인코드게이트보안포럼, 금융보안원/사단법인코드게이트보안포럼, 금융보안원", "contest_target_participants": "중학생 , 고등학생 , 대학생 , 대학원생 , 일반인", "contest_reception_period": "2024.04.29 ~ 2024.06.07", "contest_decision_period": "2024.06.07 ~ 2024.08.29", "contest_compatition_area": "전 국", "contest_award": "상금(총상금 : 2,000만원 / 1위 : 1,000만원)", "contest_homepage": "http://www.xn--ai-h41ir8ydiaw0lto5awzac9ida382tyjj.com/", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405131127001014619.png", "contest_detail_text": "■ 코드게이트 AI 아이디어랩\n코드게이트 AI 아이디어랩\n■ 참가자격\n- 제한 없음. AI에 관심 있는 누구나\n* 접수일 기준 만 14세 미만 미성년자는 법정대리인의 동의 필요\n- 개인 또는 팀 구성(4인 이내), 자유 참가\n■ 공모 주제\n- 보안 문제 해결을 위한 AI 활용 기술 또는 서비스 아이디어\n- 산업, 문화, 경제 등 사회 전반적인 보안 문제\n- 현재 또는 미래에 발생 가능성이 있는 문제\n- 공격·방어 기술 모두 가능\n(예시1) 보이스피싱, 딥페이크·딥보이스, 전자상거래, SNS 투자사기, 디지털 범죄, 웜GPT 등 새로운 유형의 사이버 위협에 대하여 AI를 활용한 예방 대책\n(예시2) AI 시스템 보안을 위한 AI 활용 방안\n■ 시상 내역\n- 총상금 2,000만원\n- 1위 : 1팀 / 1,000만원 / 금융보안원장상 / 상장 및 상금\n- 2위 : 1팀 / 500만원 / 금융보안원장상 / 상장 및 상금\n- 3위 : 1팀 / 300만원 / 금융보안원장상 / 상장 및 상금\n- 인기상 : 2팀 / 각 100만원 / 금융보안원장상 / 상장 및 상금\n* 인기상은 본선 진출작을 대상으로 공모전의 시상식이 개최되는 8.30(금) 「코드게이트 2024」\n행사 당일 일반참가자의 투표로 선정하며, 당일 폐회식에서 별도 시상\n** 인기상 선정을 위해 본선 진출작은 「코드게이트 2024」에 참석한 일반참가자에게 공개됨\n■ 공모일\n- (1차)서류제출 및 기획서 평가 : 4.29.(월)~6.7.(금) 17:00 마감 / 결과발표 6.17.(월)\n- (2차)예선 : 6.18.(화)~7.19.(금) 17:00 마감 / 결과발표 7.29.(월)\n- (3차)본선 : 8.29.(목) / 결과발표 동일\n- 시상식 : 8.30.(금)\n※ 일정은 신청 접수 현황에 따라 다소 변동될 수 있음\n※ 신청 마감일에는 접수 지연이 발생할 수 있으므로 사전에 접수 권장\n■ 제출 형식 및 심사 방법\n[심사방식]\n① (1차) 서류 및 기획서 평가 ∣ 온라인 응모페이지 제출\n- 기획서 5pg 이내, 지정 양식의 항목에 따라 작성\n- 기획서는 PDF로 저장하여 제출\n- 제출 서류 : 참가자서약서, 기획서\n- 본선 진출 팀의 3배수(30팀) 선발\n※ 1차는 블라인드 평가로 진행\n※ 기획서 양식에 이름, 팀명, 소속 등 참가자를 식별할 수 있는 정보는 기재하지 말 것\n\n② (2차) 예선심사 1차 기획서 통과 팀에 한함 ∣ 온라인 응모페이지 제출\n- 1차 기획서 구체화 영상 제출\n- 5분 이내 영상, 응모사이트에 업로드 (mp4, 최대 500mb)\n- 최종 본선 진출 10팀 선발\n※ 기획서 구체화 영상의 시간 길이를 반드시 준수해야 하며, 미준수 시 탈락 처리\n\n③ (3차) 본선 발표∣서울, 코엑스 그랜드볼룸 101호\n- 오프라인 PT 발표평가\n- 발표 15분, Q&A 10분\n■ 접수 방법\n- 「코드게이트 AI 아이디어랩 공모전」응모페이지를 통한 온라인 신청\n(응모페이지: www.코드게이트ai아이디어랩.com)\n※ 신청 접수 시 ‘공모전 참가 확인서’는 별도 발급하지 않음\n■ 문의 사항\n- ˹코드게이트 AI 아이디어랩 공모전˼ 운영사무국\n- 연락처: 031-212-2027\n- 이메일: idea.codegate@gmail.com\n"}, {"contest_title": "2024 혁신성장 데이터 챌린지 DX BOOT CAMP", "contest_host": "부산광역시/(재)부산테크노파크", "contest_target_participants": "대학생 , 대학원생 , 일반인", "contest_reception_period": "2024.05.14 ~ 2024.06.07", "contest_decision_period": "2024.06.11 ~ 2024.06.14", "contest_compatition_area": "부 산", "contest_award": "기타 :4개팀 총 600만원(트랙별 우수상 200만원/준우승 100만원)", "contest_homepage": "http://www.dxchallenge.co.kr/programs", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405141939311207460.jpg", "contest_detail_text": "■ 모집개요\nDX BOOT CAMP란?\n지역의 디지털 전환 및 데이터 산업 활성화를 위해 최고의 전문가와 함께하는 데이터 인재 인큐베이팅 프로그램입니다.\n■ 프로그램목표\n1. 아이디어 기획, 디자인, 개발, 출시 및 그로스해킹 세션을 통해출시한 서비스의 성장까지 경험\n2. 지속적인 확장 가능성을 보유한 서비스 출시로 데모데이 등을 통해 사업화 및 투자연계\n3. 데이터 융합 및 모델링을 통해 BM 기반의 서비스를 개발할 수 있는 전문 인재로 양성\n■ 개발주제\n데이터를 분석·활용하여 많은 사람에게 도움을 주는 모바일 어플리케이션 또는 서비스 개발 및 출시,\n데이터 분석을 통한 현안 문제해결 정책 제안 등\n■ 모집일정\n5. 14.(화) ~ 6. 7.(금) 18:00 까지\n■ 접수방법\n구글폼 https://forms.gle/WvUb33QKUe3egyq98 을 통한 신청 접수\n자세한 사항은 DX CHALLENGE https://www.dxchallenge.co.kr/ 홈페이지를 참고해주시기 바랍니다.\n■ 참가자격\n데이터 및 IT 산업에 관심이 많은 대학생/직장인/일반인\n■ 지원혜택\n1.역량강화\n- 현업 멘토진과 함께 하는 자기 주도적 학습과 전담 멘토링\n- 데이터 기반의 아이디어 개발부터 사업화까지 전 단계 지원\n- 예비/초기창업팀 특화 멘토 및 데이터 전문가 등 분야별 전문가 상주를 통한 집중적인 개발 지원\n- 데이터 분석 전문가와 4주간 집중 데이터 분석 교육 실시\n\n2.공간/활동지원\n- 4K모니터, 회의실, 화상 미팅룸 등이 마련된 입주 시설 제공\n- 서비스 기획부터 앱 출시 후 마케팅까지 상시 지원\n- 이달의 우수팀 시상\n\n3.네트워킹\n-매주 현업 전문가와 함께 하는 밋업과 포럼, 개발자 커뮤니티 프로그램\n-우수 수료 팀 VC 연계 네트워킹 제공\n-각종 지원 프로그램 연계\n■ 시상규모\n4개팀 총 600만원(트랙별 우수상 200만원/준우승 100만원)\n■ 주최 및 주관\n주최: 부산광역시\n주관: (재)부산테크노파크\n■ 프로그램일정\n*내부 사정으로 인해 일정이 변동될 수 있습니다.\n모집: 5. 14.(화) ~ 6. 7.(금) 18:00 까지\n인터뷰: 6. 11.(화) ~ 6. 14.(금)\n*신청서에 본인 인터뷰 가능 시간 입력 필수\n**인터뷰는 대면으로 진행하여 대면 심사가 불가할 경우 온라인 인터뷰 실시\n합격자 발표: 6. 18.(화) 예정\n오리엔테이션 : 6. 19.(수) 16:00 예정\n캠프 기간: 6. 19.(수) ~ 12. 10.(화)\n■ 문 의\n운영사무국: 070-4618-1565\n■ 주최·주관사 SNS 채널\n"}, {"contest_title": "2024년 국방 공공데이터 활용 경진대회", "contest_host": "국방부·병무청·방위사업청", "contest_target_participants": "누구나 , 유치원 , 초등학생 , 중학생 , 고등학생 , 대학생 , 대학원생 , 일반인 , 외국인", "contest_reception_period": "2024.04.22 ~ 2024.06.10", "contest_decision_period": "2024.06.21 ~ 2024.07.05", "contest_compatition_area": "전 국", "contest_award": "상금(총상금 : 1,230만원 / 1위 : 300만원)", "contest_homepage": "http://tinyurl.com/22jso5su", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202404251048394856046.png", "contest_detail_text": "■ 공모전명\n[국방부·병무청·방위사업청] 2024년 국방 공공데이터 활용 경진대회\n■ 공모 부문\n① 아이디어 기획\n국방분야 공공데이터를 활용한 창의적 아이디어 기획\n\n② 서비스 개발\n국방분야 공공데이터를 활용한 모바일 앱/웹 등 제품 및 서비스 개발\n■ 참가자격\n대한민국 국민 누구나 참여 가능 (팀은 대표자 포함 최대 4명까지 구성 가능)\n■ 접수기간\n- 출품작 접수: 2024년 4월 22일(월) ~ 2024년 6월 10일(월)\n- 1차 서류 평가: 2024년 6월 21일(금)\n- 2차 발표 평가: 2024년 7월 5일(금)\n- 결과발표: 2027년 7월 12일(금)\n■ 접수방법\n국방부 공공데이터포털(data.mnd.go.kr) 접속 > 경진대회 > 출품작 접수게시판\n■ 시상 내역\n① 아이디어 기획\n대 상 국방부장관 상장 및 상금 150만원\n최우수 병무청장 상장 및 상금 130만원\n우 수 병무청장 상장 및 상금 110만원\n장 려 병무청장 상장 및 상금 90만원\n\n② 서비스 개발\n대 상 국방부장관 상장 및 상금 300만원\n최우수 방위사업청장 상장 및 상금 200만원\n우 수 방위사업청장 상장 및 상금 150만원\n장 려 방위사업청장 상장 및 상금 100만원\n■ 문의\n국방 공공데이터 활용 경진대회 운영사무국\n02-2079-6787 02-748-5934\n"}, {"contest_title": "제3회 메타버스 서울 3D 모델링 공모전", "contest_host": "서울디지털재단", "contest_target_participants": "대학생 , 대학원생 , 일반인", "contest_reception_period": "2024.04.16 ~ 2024.06.13", "contest_decision_period": "2024.06.17 ~ 2024.08.31", "contest_compatition_area": "서 울", "contest_award": "상금(총상금 : 700만원 / 1위 : 300만원)", "contest_homepage": "http://sdf.seoul.kr/business-announce/2430", "contest_how_to_apply": "이메일접수, 온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202404171039037382318.png", "contest_detail_text": "■ 담당자 TIP\n저작도구로 만들어진 작품이 <메타버스 서울> 앱에 어떤 모습으로 탑재되는지 미리 테스트하면서 작품을 만들어가시길 추천드립니다.\n■ 공모 주제\n메타서울펫 에셋 3D 모델링 & 활용 아이디어\nㅇ 메타서울펫의 장착 아이템, 퀘스틑 기능 개발을 위한 훈련 놀이터 등 메타서울펫을 고도화하는 데 활용하기 위한 에셋 3D 모델링\nㅇ 창작한 에셋을 활용한 퀘스트, 커스텀 기능 등 메타서울펫 서비스 고도화 아이디어 제안\n■ 참가 자격\n메타버스 콘텐츠 창작, 3D 모델링 등에 관심 있는 만 19세 이상 누구나\n■ 공모 기간\nㅇ 작품 업로드 테스트: 2024.4.16.(화)~2024.6.7.(금)\nㅇ 최종 작품 접수: 2024.6.11.(화) 오전 10시 ~ 2024.6.13.(금) 오후 2시\n■ 작품 제작 규격< 3D 창작물 규격 >\n1. 모델 규격\n단일 모델의 정점 (vertices) 수 5000 개 이하로 제한\n단일 모델 내에 적용된 머터리얼 5 종 이하\n모델의 애니메이션은 지원되지 않음\n모델 텍스처 파일의 가로 및 세로 512 px 이하로 제한\n가로 및 세로가 2 의 배수 (Power of Two)를 유지하여야 함\n텍스처 파일까지 포함하여 , 단일 모델 파일 크기 5 MB 이하\n\n2. 모델 확장자\nobj, fbx 확장자로 제한\n텍스처의경우 , jpg, png 확장자로 제한\n■ 제출 방법\n- ① 공모전 저작 도구 업로드: 메타버스 서울 공모전 저작 도구로 작품 업로드 한 내공간 생성 후 제출\n※ 자세한 사항은 서울디지털재단 홈페이지 > 자료실 > ‘메타버스 서울’ 교육 교재\n(https://sdf.seoul.kr/dataroom/2406) > 심화편 교재 및 영상 참조\n- ②~⑤ 메일 제출: mingyeong@sdf.seoul.kr\n※ 최종 작품 접수 기간 6.11(화) 오전 10시~6.13(금) 오후 2시에 제출한 서류에 대해서만 접수되며 6.28(금)까지 접수 확인 회신메일 발송 예정\n\n①작품 전시\n②3D 모델링 파일\n③캡처 이미지\n④작품 제안서\n⑤동의서\n■ 시상 규모\n총 700만원\n대상(1팀) 300만원, 우수상(2팀) 각 100만원, 장려상(4팀) 각 50만원\n■ 문의 사항\n- 사업 담당자 스마트라이프팀(02-570-4646, mingyeong@sdf.seoul.kr)\n- 카카오톡 오픈채팅방(https://open.kakao.com/o/gQc71Tjg)을 통해서도 문의 가능\n"}, {"contest_title": "2024년 제3회 영천시 공공데이터 활용 경진대회", "contest_host": "영천시 /포항테크노파크, 경북SW진흥본부", "contest_target_participants": "누구나 , 유치원 , 초등학생 , 중학생 , 고등학생 , 대학생 , 대학원생 , 일반인 , 외국인", "contest_reception_period": "2024.04.15 ~ 2024.06.14", "contest_decision_period": "2024.06.26 ~ 2024.07.10", "contest_compatition_area": "경 남", "contest_award": "상금(총상금 : 1,300만원 / 1위 : 200만원)", "contest_homepage": "http://www.ycdatacon.kr/summary", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202404171753247447902.jpg", "contest_detail_text": "■ 대회명\n2024년 제3회 영천시 공공데이터 활용 경진대회\n■ 대회일정\n참가팀 접수 : 2024.04.15.(월) ~ 06.14.(금)\n※ 홈페이지를 통한 온라인 접수\n예선 평가 : 2024.06.26.(수) ~ 06.27.(목)\n결선 평가 : 2024.07.10.(수), 발표평가\n시상 : 2024.07.24.(수)\n■ 참가부문\n참가부문 1 : 아이디어 기획\n공공데이터를 활용한 창의적 아이디어\n참가부문 2 : 데이터 시각화\n공공데이터 분석 및 시각화\n■ 신청자격\n대한민국 국민 누구나(개인 또는 4명 이내의 팀)\n■ 신청방법\n홈페이지 접수\n■ 신청서류\n공통(온라인) : 참가 신청서, 개인정보 수집·이용 동의서, 참가자 서약서\n아이디어 기획 : 아이디어 기획서\n데이터 시각화 : 데이터 시각화 분석 보고서, 시각화 작품\n■ 시상내역\n1) 아이디어 기획\n최우수 : 영천시장상, 200만원, 1명\n우수 : 영천시장상 / 포항테크노파크 원장상, 150만원, 2명\n장려 : 영천시장상 / 포항테크노파크 원장상 / 빅데이터혁신융합대학 사업단장상, 100만원, 3명\n\n2) 데이터 시각화\n최우수 : 영천시장상, 150만원, 1명\n우수 : 영천시장상 / 포항테크노파크 원장상, 100만원, 2명\n장려 : 영천시장상 / 포항테크노파크 원장상 / 빅데이터혁신융합대학 사업단장상, 50만원, 3명\n■ 주최\n영천시\n■ 주관\n포항테크노파크, 경북SW진흥본부\n■ 문의\nTel : 054-223-2185\n"}, {"contest_title": "2024 대전광역시 공공데이터 활용 경진대회", "contest_host": "대전광역시, 동구, 중구, 서구, 유성구, 대덕구, 대전교통공사, 대전도시공사, 대전광역시시설관리공단/대전정보문화산업진흥원", "contest_target_participants": "중학생 , 고등학생 , 대학생 , 대학원생 , 일반인", "contest_reception_period": "2024.05.16 ~ 2024.06.15", "contest_decision_period": "2024.07.09", "contest_compatition_area": "전 국", "contest_award": "상금(총상금 : 1,650만원 / 1위 : 500만원)", "contest_homepage": "http://pms.dicia.or.kr/comm/main/main.do", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405211450003938669.jpg", "contest_detail_text": "■ 참가대상\n▶공공데이터에 관심이 있는 만14세 이상 대한민국 국민 누구나\n※개인 또는 팀 구성(대표자 포함 5인 이내)으로 참가 가능\n※만14세 ~ 19세로 구성된 팀의 경우 원활한 대회 안내를 위해 지도교사 참여 필요\n■ 참가신청 방법\n▶참가신청서 온라인 제출\n- https://pms.dicia.or.kr/mgmt/mjgg/mjggMgmtView.do\n- 참가신청서(서식1)\n- 개인정보 수집·이용 동의서 및 참가서약서 (서식2 / 팀원 전원 서명)\n■ 참여분야\n▶아이디어 기획\n- (공공데이터 활용 아이디어 기획) 공공데이터를 활용한 사회문제해결, 또는 창업 아이디어 제안\n- 아이디어의 혁신성\n▶제품 및 서비스 개발\n- (공공데이터 활용 서비스 제안) 공공데이터를 활용한 제품·서비스를 제안하고 시제품 시연\n- 사업화 가능성\n■ 시상내역\n총 상금 1,650만원 / 아이디어 기획(6점),제품 및 서비스 개발(6점) 총 12점\n▶제품 및 서비스 개발\n- 대 상(1) / 상장·상금 500만원 / 대전광역시장\n- 최우수상(1) / 상장·상금 300만원 / 대전광역시장\n- 우수상(4) / 상장·상금 100만원 / 공사(공단)이사장\n▶아이디어 기획\n- 최우수상(1) / 상장·상금 200만원 / 대전광역시장\n- 우수상(5) / 상장·상금 50만원 / 구청장\n- 부문별 대상 및 최우수상 3개 대전광역시장상 시상\n- 제품 및 서비스 개발 우수상 4개 기관장상(대전교통공사, 대전도시공사, 대전시설관리공간, 한국장학재단) 시상\n- 아이디어 기획 우수상 5개 구청장상(동구, 중구, 서구, 유성구, 대덕구) 시상\n■ 일정안내\n▶신청기간 : 2024. 5. 16.(목) ~ 2024. 6. 15.(토)\n▶교육일정 :\n- 아이디어 기획 / 2024. 6. 8.(토) 13:00~17:00(4시간)\n- 제품 및 서비스 개발 / 2024. 6. 15.(토) 13:00~17:00(4시간)\n※교육장소 : 대전 빅데이터 오픈랩 (충남대학교 정보화본부교육관 N2-1 2층 데이터안심구역)\n※우대사항 : 공공데이터 활용 교육 참여시 심사평가 가산점 부여\n▶제출기간 : 2024. 6. 17.(화) ~ 2024. 6. 28.(금)\n▶심사일자 : 2024. 7. 9.(화) / 대면 또는 온라인 발표평가\n※심사방법 : 전문가 평가단 공모제안서 평가\n※심사기준은 참가신청자 대상 별도 안내\n■ 후속지원\n▶본선추천 : 아이디어 기획 최수우상, 제품 및 서비스개발 대상은 ‘제12회 범정부 공공데이터 활용 창업경진대회’ 전국통합본선 참여 기회 제공\n▶통합본선 공모제안서 고도화, 발표자료 제작, 시제품 개선, 발표력 향상을 위한 멘토링 등 추가 지원\n■ 유의사항\n- 응모작은 참가자 순수 창작물에 한함\n- 개인(팀)당 한 부문만 응모 가능(아이디어 부문, 제품 및 서비스개발 부문 중복 응모 불가)\n- 수상작으로 선정되어 상금을 지급받은 이후에도 타 공모전 중복수상, 표절 시비 등 문제가 발생하는 경우에는 수상이 취소 될 수 있으며, 수상자가 지급받은 상금은 전액 환수함\n- 공모전에 출품된 모든 자료의 지식재산권과 책임은 응모자에게 있으며 출품작에 대한 저작권으로 인하여 발생하는 민・형사상 책임은 출품자에게 있음\n- 추후 별도의 합의를 통해 수상자로부터 별도의 허락을 받아 2차 저작물을 작성할 수 있으며 누리집 및 공식 SNS 등을 통해 홍보물로 배포 및 활용될 수 있음\n- 수상자가 팀인 경우 상금은 팀의 대표에게 지급하며, 주최·주관기관은 상금의 배분에 관여하지 않음\n- 수상작 상금에 대한 세금(4.4%) 사전 공제 후 지급함\n- 본 공고에 대하여 미숙지로 발생하는 불이익과 그에 따른 책임은 본 사업 신청자에게 있음\n■ 문의처\n(재)대전정보문화산업진흥원 디지털기반지원단 042-479-4162 / jhoh@dicia.or.k\n"}, {"contest_title": "2024 MATLAB 대학생 AI 경진대회", "contest_host": "매스웍스코리아", "contest_target_participants": "대학생 , 대학원생", "contest_reception_period": "2024.04.25 ~ 2024.06.20", "contest_decision_period": "2024.06.20 ~ 2024.07.01", "contest_compatition_area": "전 국", "contest_award": "상금(총상금 : 350만원 / 1위 : 200만원)", "contest_homepage": "http://bit.ly/44fnres", "contest_how_to_apply": "온라인접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405021540474908592.png", "contest_detail_text": "■ 공모주제\n- 인공지능 기술을 우리 실생활에 새롭게 적용하거나 산업에서 기존의 프로세스를 개선할 수 있는 아이디어를 MATLAB으로 구현\n*MATLAB 및 MATLAB toolbox 활용 필수\n■ 기간 및 일정\n- 예선: 2024. 6. 20(목) 오후 6시 참가 신청 접수 마감\n- 본선: 2024. 7. 31(수) 오후 6시 영상 접수 마감\n- 수상자 발표: 2024. 9. 25(수)\n■ 지원자격\n- 대학교 재학 중인 학부생 또는 대학원생 (개인 또는 팀)\n■ 접수방법 및 심사기준\n- 대회 홈페이지 참고 (https://bit.ly/44fnres)\n■ 제출내용\n- 예선: MATLAB을 활용한 AI모델 개요 및 기대효과 제출\n- 본선: 예선 통과자는 본선 심사를 위한 AI 모델 동영상 제출\n*예선 통과 후 본선에서 동영상을 제출한 모든 팀에 커피 기프티콘을 제공합니다.\n■ 시상내역\n- 1등 200만원 (1팀)\n- 2등 100만원 (1팀)\n- 3등 50만원 (1팀)\n■ MATLAB 설치 및 이용\n- 본인 소속 학교 MATLAB 캠퍼스 라이선스 이용\n- 소속 학교에 MATLAB 캠퍼스 라이선스가 없는 경우 대회 홈페이지에서 경진대회용 라이선스 신청\n■ 문의\nmarketing_korea@mathworks.com\n"}, {"contest_title": "KT희망나눔재단과 함께 <제 1회 스마트 AI 콘텐츠 공모전>", "contest_host": "KT그룹 희망나눔재단", "contest_target_participants": "초등학생 , 중학생 , 고등학생", "contest_reception_period": "2024.07.01 ~ 2024.07.12", "contest_decision_period": "2024.07.15 ~ 2024.07.29", "contest_compatition_area": "전 국", "contest_award": "상금(총상금 : 250만원 / 1위 : 100만원)", "contest_homepage": "http://iaae.ai/notice/?idx=24238734&bmode=view", "contest_how_to_apply": "우편접수", "contest_fee": "무료 접수", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405311352385977633.jpg", "contest_detail_text": "■ 공모명\nKT희망나눔재단과 함께 <제 1회 스마트 AI 콘텐츠 공모전>\n■ 접수 기간\n2024.7.1 ~ 2024.7.12\n■ 공모 주제(선택 1)\n1. 인간과 로봇이 조화롭게 살아가는 미래 모습\n2. 100년 뒤, 2124년의 대한민국의 풍경 및 첨단 도시\n3. 역사의 주요 장면 및 동화책의 장면\n■ 공모 자격 및 분야\n- (초등학생) 포스터 그리기\n- (중고등학생) 생성형 AI 프로그램 사용 포스터 그리기 *무료 tool만 활용가능\n■ 접수 형식\n- (포스터 부문) 첨부의 참가 신청서 작성 후 직접 그린 4절(394mm*545mm)과 함께 우편 제출\n- (AI 프로그램) 온라인 참가 신청서 작성 후 작품 첨부(5MB 이하) (접수용 구글폼 제공예정)\n■ 접수 방법\n- (포스터 부문) 우편접수 : 서울시 종로구 창경궁로 136, 14층(보령빌딩) kt그룹 희망나눔재단 공모전 담당\n- (AI 프로그램) : 온라인 접수용 구글폼 링크 7월 1일 오픈 예정\n■ 시상\n- KT그룹 희망나눔재단 이사장상 : 1명 (100만원)\n- 한국지능정보사회진흥원장상 : 1명 (문화 상품권 50만원)\n- 한국교육학술정보원장상 : 1명 (문화상품권 50만원)\n- 국제인공지능윤리협회장상 : 1명 (문화상품권 25만원)\n- AI융합교육연구회장상 : 1명 (문화상품권 25만원)\n■ 결과 발표\n7.30 IAAE, KT재단 홈페이지 *시상식 개별 안내\n■ 주최\nKT그룹 희망나눔재단\n■ 문의\n02-3414-2068, 2055\n"}, {"contest_title": "제15회 전국 창의문제 해결능력 경진대회", "contest_host": "(사)IT여성기업인협회 영남지회", "contest_target_participants": "초등학생 , 중학생 , 고등학생 , 대학생 , 대학원생 , 일반인", "contest_reception_period": "2024.06.10 ~ 2024.07.15", "contest_decision_period": "2024.07.22 ~ 2024.07.29", "contest_compatition_area": "대 구", "contest_award": "기타 :과학기술정보통신부 장관상 등", "contest_homepage": "http://www.cpsfestival.org/", "contest_how_to_apply": "무료 접수", "contest_fee": " ", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405281626152836896.jpg", "contest_detail_text": "■ 대회명\n제15회 전국 창의문제 해결능력 경진대회\n■ 참가자격\n전국 초, 중, 고, 대학/일반 누구나\n■ 접수기간\n2024.6.10(월) - 2024.7.15(월) / 온라인 접수\n■ 예선대회\n2024.7.22(월) - 2024.7.29(월) / 온라인 대회\n■ 본선대회\n2024.8.31(토) 14시 / 호텔인터불고 대구 컨벤션홀\n■ 참가 방식 및 내용\n① 전국 초, 중, 고, 대학/일반인을 대상으로 알고리즘을 통한 문제 해결능력을 온라인 예선, 본선대회를 통해 검증하여 우수 창의 인재 양성\n② 진행방법\n- 초등부, 중등부, 고등부, 대학·일반부 등 총 4개 부문으로 구분하여 진행\n- (예선대회) 온라인 진행\n· 대회 기간 내 6문제 풀이 후, 정답 제출\n- (본선대회)오프라인 개최\n· 각 부문별 25팀 씩, 총 100팀 선발\n· 제한된 시간동안 6문제 풀이 후, 정답 제출\n■ 문의처\nTEL. 053-754-7891 / E-MAIL. kibwadg@kibwadg.org\n■ 주최·주관사 SNS 채널\n"}, {"contest_title": "K-디지털 챌린지 : NET 챌린지 캠프 시즌 11", "contest_host": "과학기술정보통신부/한국지능정보사회진흥원, KOREN연구협력포럼", "contest_target_participants": "대학생 , 대학원생", "contest_reception_period": "2024.06.05 ~ 2024.06.26", "contest_decision_period": "2024.06.26 ~ 2024.07.04", "contest_compatition_area": "서 울", "contest_award": "상금(총상금 : 1,200만원 / 1위 : 500만원)", "contest_homepage": "http://www.koren.kr/kor/Alram/contyView.asp?s=34&page=1", "contest_how_to_apply": "무료 접수", "contest_fee": " ", "contest_image": "https://www.contestkorea.com/admincenter/files/meet/202405241600553219312.jpg", "contest_detail_text": "■ 공모명\nK-디지털 챌린지 : NET 챌린지 캠프 시즌 11\n■ 공모분야\n- 네트워크 응용분야의 상용화가 가능한 ICT 신기술 및 서비스 관련 아이디어\n※ KOREN(차세대 네트워크 선도 연구시험망)이란?\n산업체·학계·연구기관 등이 연구·개발을 목적으로 무료로 이용할 수 있는 광대역, 고품질의 연구시험망으로, 미래 네트워크 관련 기술의 시험검증과 첨단 ICT 국제 공동 협력 기반을 조성하는 비영리 선도시험 네트워크 환경\n■ 참가자격\n대학생 또는 대학원생\n※ 전공 무관, 개인 또는 팀(6인 이하)으로 참가. 단, 석사 재학생까지 제한\n■ 접수기간\n2024.6.5.(수) 9:00 ~ 6.26.(수) 18:00까지\n■ 지원내용\n- 10팀을 선정하여 KOREN 기반 아이디어 개발 및 구현을 위한 연구개발비(팀별 200만원), 해커톤 캠프, KOREN 자원 등을 제공하고 최종 결과물 평가를 통해 우수팀 시상\n■ 접수방법\nKOREN 홈페이지(www.koren.kr)를 통한 온라인 접수\n제출서류: 참가신청서 및 기획안 1부\n※ 둘 중 하나라도 미제출 시 접수되지 않사오니, 유의하여 주시기 바랍니다\n접수링크 : https://www.koren.kr/kor/Alram/contyView.asp?s=34&page=1\n■ 시상내역\n*총계 9팀 1,200만원\n대상 과기부장관상 1팀 500만원\n금상 NIA원장상 1팀 300만원\n은상 KOREN 협력기관상 4팀 각 100만원\n특별상 통신사상(KT, SKB, LGU+) 3팀\n\n* 특별상(통신사상)은 대상/금상/은상과 중복수상 가능\n* 시상내역 및 일정은 내부 사정에 따라 변경될 수 있음\n* (유의사항) 아이디어에 대한 저작권 침해 등 법적인 책임은 응모자 본인에게 있으며, 타 공모전에 출품한 작품이거나 표절이 발생할 경우, 평가 및 심사에서 제외될 뿐만 아니라 시상 이후 표절이 확인된 경우에도 수상 취소와 함께 시상내역 또한 환수됨\n■ 문의사항\nNET 챌린지 캠프 사무국\nE. netcc@koren.kr\nT. 031-5182-9172, 9173\n"}] \ No newline at end of file diff --git a/src/main/java/com/example/capd/CapDApplication.java b/src/main/java/com/example/capd/CapDApplication.java index d298cc6..f191376 100644 --- a/src/main/java/com/example/capd/CapDApplication.java +++ b/src/main/java/com/example/capd/CapDApplication.java @@ -2,10 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class CapDApplication { - public static void main(String[] args) { SpringApplication.run(CapDApplication.class, args); } diff --git a/src/main/java/com/example/capd/Exception/ReviewSubmissionPeriodNotEndedException.java b/src/main/java/com/example/capd/Exception/ReviewSubmissionPeriodNotEndedException.java new file mode 100644 index 0000000..80ddfd5 --- /dev/null +++ b/src/main/java/com/example/capd/Exception/ReviewSubmissionPeriodNotEndedException.java @@ -0,0 +1,10 @@ +package com.example.capd.Exception; + +public class ReviewSubmissionPeriodNotEndedException extends RuntimeException { + public ReviewSubmissionPeriodNotEndedException(){ + super("심사 기간이 끝나지 않아 리뷰를 작성할 수 없습니다."); + } + public ReviewSubmissionPeriodNotEndedException(int n){ + super("접수 기간이 끝나지 않아 리뷰를 작성할 수 없습니다."); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/Exception/TeamNotConfirmedException.java b/src/main/java/com/example/capd/Exception/TeamNotConfirmedException.java deleted file mode 100644 index 5534242..0000000 --- a/src/main/java/com/example/capd/Exception/TeamNotConfirmedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.example.capd.Exception; - -public class TeamNotConfirmedException extends RuntimeException { - public TeamNotConfirmedException (){ - super("팀이 확정되지 않아 리뷰 작성이 불가능 합니다."); - } -} \ No newline at end of file diff --git a/src/main/java/com/example/capd/Exception/UserWithDesiredStackNotFoundException.java b/src/main/java/com/example/capd/Exception/UserWithDesiredStackNotFoundException.java index 73c85f3..05d7d8e 100644 --- a/src/main/java/com/example/capd/Exception/UserWithDesiredStackNotFoundException.java +++ b/src/main/java/com/example/capd/Exception/UserWithDesiredStackNotFoundException.java @@ -4,4 +4,5 @@ public class UserWithDesiredStackNotFoundException extends RuntimeException{ public UserWithDesiredStackNotFoundException() { super("원하는 스택을 가지고 있는 유저가 존재하지 않습니다."); } + } diff --git a/src/main/java/com/example/capd/User/JWT/AuthorizationExtractor.java b/src/main/java/com/example/capd/User/JWT/AuthorizationExtractor.java new file mode 100644 index 0000000..24d65b7 --- /dev/null +++ b/src/main/java/com/example/capd/User/JWT/AuthorizationExtractor.java @@ -0,0 +1,26 @@ +package com.example.capd.User.JWT; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.logging.log4j.util.Strings; +import org.springframework.stereotype.Component; + +import java.util.Enumeration; + +//헤더에서 토큰 추출하기 +@Component +public class AuthorizationExtractor { + public static final String AUTHORIZATION = "AAuthorization"; + public static final String ACCESS_TOKEN_TYPE = AuthorizationExtractor.class.getSimpleName() + ".ACCESS_TOKEN_TYPE"; + + public String extract(HttpServletRequest request, String type) { + Enumeration headers = request.getHeaders(AUTHORIZATION); + while (headers.hasMoreElements()) { + String value = headers.nextElement(); + if (value.toLowerCase().startsWith(type.toLowerCase())) { + return value.substring(type.length()).trim(); + } + } + + return Strings.EMPTY; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/JWT/BearerAuthInterceptor.java b/src/main/java/com/example/capd/User/JWT/BearerAuthInterceptor.java new file mode 100644 index 0000000..ddac26c --- /dev/null +++ b/src/main/java/com/example/capd/User/JWT/BearerAuthInterceptor.java @@ -0,0 +1,36 @@ +//package com.example.capd.User.JWT; +// +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import org.springframework.stereotype.Component; +//import org.springframework.util.StringUtils; +//import org.springframework.web.servlet.HandlerInterceptor; +// +//@Component +//public class BearerAuthInterceptor implements HandlerInterceptor { +// private AuthorizationExtractor authExtractor; +// private TokenProvider jwtTokenProvider; +// +// public BearerAuthInterceptor(AuthorizationExtractor authExtractor, TokenProvider jwtTokenProvider) { +// this.authExtractor = authExtractor; +// this.jwtTokenProvider = jwtTokenProvider; +// } +// +// @Override +// public boolean preHandle(HttpServletRequest request, +// HttpServletResponse response, Object handler) { +// System.out.println(">>> interceptor.preHandle 호출"); +// String token = authExtractor.extract(request, "Bearer"); +// if (StringUtils.isEmpty(token)) { +// return true; +// } +// +// if (!jwtTokenProvider.validateToken(token)) { +// throw new IllegalArgumentException("유효하지 않은 토큰"); +// } +// +// String name = jwtTokenProvider.getUsername(token); +// request.setAttribute("name", name); +// return true; +// } +//} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/JWT/JwtAuthFilter.java b/src/main/java/com/example/capd/User/JWT/JwtAuthFilter.java new file mode 100644 index 0000000..f9c3d64 --- /dev/null +++ b/src/main/java/com/example/capd/User/JWT/JwtAuthFilter.java @@ -0,0 +1,52 @@ +package com.example.capd.User.JWT; + +import com.example.capd.User.service.CustomUserDetailsService; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@RequiredArgsConstructor +public class JwtAuthFilter extends OncePerRequestFilter { // OncePerRequestFilter -> 한 번 실행 보장 + + private final CustomUserDetailsService customUserDetailsService; + private final JwtUtil jwtUtil; + + @Override + /** + * JWT 토큰 검증 필터 수행 + */ + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String authorizationHeader = request.getHeader("Authorization"); + + //JWT가 헤더에 있는 경우 + if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { + String token = authorizationHeader.substring(7); + //JWT 유효성 검증 + if (jwtUtil.validateToken(token)) { + Long userId = jwtUtil.getId(token); + + //유저와 토큰 일치 시 userDetails 생성 + UserDetails userDetails = customUserDetailsService.loadUserByUsername(userId.toString()); + + if (userDetails != null) { + //UserDetsils, Password, Role -> 접근권한 인증 Token 생성 + UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = + new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + + //현재 Request의 Security Context에 접근권한 설정 + SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); + } + } + } + + filterChain.doFilter(request, response); // 다음 필터로 넘기기 + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/JWT/JwtUtil.java b/src/main/java/com/example/capd/User/JWT/JwtUtil.java new file mode 100644 index 0000000..48ec1b1 --- /dev/null +++ b/src/main/java/com/example/capd/User/JWT/JwtUtil.java @@ -0,0 +1,110 @@ +package com.example.capd.User.JWT; + +import com.example.capd.User.dto.CustomUserInfoDto; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.time.ZonedDateTime; +import java.util.Date; + +@Slf4j +@Component +public class JwtUtil { + private final Key key; + private final long accessTokenExpTime; + + public JwtUtil( + @Value("${jwt.secret}") String secretKey, + @Value("${jwt.expiration_time}") long accessTokenExpTime + ) { + byte[] keyBytes = Decoders.BASE64.decode(secretKey); + this.key = Keys.hmacShaKeyFor(keyBytes); + this.accessTokenExpTime = accessTokenExpTime; + } + + + /** + * Access Token 생성 + * @param user + * @return Access Token String + */ + public String createAccessToken(CustomUserInfoDto user) { + return createToken(user, accessTokenExpTime); + } + + + /** + * JWT 생성 + * @param user + * @param expireTime + * @return JWT String + */ + private String createToken(CustomUserInfoDto user, long expireTime) { + Claims claims = Jwts.claims(); + claims.put("Id", user.getId()); + claims.put("email", user.getUserId()); + claims.put("role", user.getRoles()); + + ZonedDateTime now = ZonedDateTime.now(); + ZonedDateTime tokenValidity = now.plusSeconds(expireTime); + + + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(Date.from(now.toInstant())) + .setExpiration(Date.from(tokenValidity.toInstant())) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + + /** + * Token에서 User ID 추출 + * @param token + * @return User ID + */ + public Long getId(String token) { + return parseClaims(token).get("Id", Long.class); + } + + + /** + * JWT 검증 + * @param token + * @return IsValidate + */ + public boolean validateToken(String token) { + try { + Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); + return true; + } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { + log.info("Invalid JWT Token", e); + } catch (ExpiredJwtException e) { + log.info("Expired JWT Token", e); + } catch (UnsupportedJwtException e) { + log.info("Unsupported JWT Token", e); + } catch (IllegalArgumentException e) { + log.info("JWT claims string is empty.", e); + } + return false; + } + + + /** + * JWT Claims 추출 + * @param accessToken + * @return JWT Claims + */ + public Claims parseClaims(String accessToken) { + try { + return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken).getBody(); + } catch (ExpiredJwtException e) { + return e.getClaims(); + } + } +} diff --git a/src/main/java/com/example/capd/User/JWT/TokenAuthenticationFilter.java b/src/main/java/com/example/capd/User/JWT/TokenAuthenticationFilter.java new file mode 100644 index 0000000..5146ed1 --- /dev/null +++ b/src/main/java/com/example/capd/User/JWT/TokenAuthenticationFilter.java @@ -0,0 +1,59 @@ +//package com.example.capd.User.JWT; +// +//import jakarta.servlet.*; +//import jakarta.servlet.http.HttpServletRequest; +//import jakarta.servlet.http.HttpServletResponse; +//import lombok.RequiredArgsConstructor; +//import org.springframework.context.annotation.DependsOn; +//import org.springframework.security.core.Authentication; +//import org.springframework.security.core.context.SecurityContextHolder; +//import org.springframework.stereotype.Component; +//import org.springframework.util.StringUtils; +//import org.springframework.web.filter.GenericFilterBean; +//import org.springframework.web.filter.OncePerRequestFilter; +// +//import java.io.IOException; +// +//public class TokenAuthenticationFilter extends OncePerRequestFilter { +// +// private final TokenProvider tokenProvider; +// +// public TokenAuthenticationFilter(TokenProvider tokenProvider) { +// this.tokenProvider = tokenProvider; +// } +// +//// +// +// @Override +// protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { +// String token = tokenProvider.resolveToken(request); +// +// if (token != null && tokenProvider.validateToken(token)) { +// // check access token +// token = token.split(" ")[1].trim(); +// Authentication auth = tokenProvider.getAuthentication(token); +// SecurityContextHolder.getContext().setAuthentication(auth); +// } +// +// filterChain.doFilter(request, response); +// } +// +//} +// +//// private String getAccessToken(String authorizationHeader) { +//// if (authorizationHeader != null && authorizationHeader.startsWith(TOKEN_PREFIX)) { +//// return authorizationHeader.substring(TOKEN_PREFIX.length()); +//// } +//// return null; +//// } +// +//// Request Header 에서 토큰 정보를 꺼내오기 위한 메소드 +//// private String resolveToken(HttpServletRequest request) { +//// String bearerToken = request.getHeader(HEADER_AUTHORIZATION); +//// +//// if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { +//// return bearerToken.substring(7); +//// } +//// +//// return null; +//// } \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/JWT/TokenProvider.java b/src/main/java/com/example/capd/User/JWT/TokenProvider.java new file mode 100644 index 0000000..6a72560 --- /dev/null +++ b/src/main/java/com/example/capd/User/JWT/TokenProvider.java @@ -0,0 +1,112 @@ +//package com.example.capd.User.JWT; +// +//import com.example.capd.User.domain.Authority; +//import com.example.capd.User.domain.User; +//import com.example.capd.User.service.CustomUserDetailsService; +//import io.jsonwebtoken.*; +//import io.jsonwebtoken.io.Decoders; +//import io.jsonwebtoken.security.Keys; +//import jakarta.annotation.PostConstruct; +//import jakarta.servlet.http.HttpServletRequest; +//import lombok.RequiredArgsConstructor; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +//import org.springframework.beans.factory.InitializingBean; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.core.Ordered; +//import org.springframework.core.annotation.Order; +//import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +//import org.springframework.security.core.Authentication; +//import org.springframework.security.core.GrantedAuthority; +//import org.springframework.security.core.authority.SimpleGrantedAuthority; +//import org.springframework.security.core.userdetails.UserDetails; +//import org.springframework.stereotype.Component; +//import org.springframework.web.bind.annotation.RequestBody; +// +//import java.nio.charset.StandardCharsets; +//import java.security.Key; +//import java.time.Duration; +//import java.util.*; +//import java.util.stream.Collectors; +// +//@Component +//@RequiredArgsConstructor +//@Order(Ordered.HIGHEST_PRECEDENCE) +//public class TokenProvider { +// +// private final Logger logger = LoggerFactory.getLogger(TokenProvider.class); +// private Key secretKey; +// +// @Value("${security.jwt.token.secret-key}") +// private String salt; +// private final long exp = 1000L * 60 * 60; +// +// private final CustomUserDetailsService userDetailsService; +// +// @PostConstruct +// protected void init() { +// secretKey = Keys.hmacShaKeyFor(salt.getBytes(StandardCharsets.UTF_8)); +// } +// +// +// +// // 토큰 생성 +// public String createToken(String userId, List roles) { +// Claims claims = Jwts.claims().setSubject(userId); +// claims.put("roles", roles); +// Date now = new Date(); +// return Jwts.builder() +// .setClaims(claims) +// .setIssuedAt(now) +// .setExpiration(new Date(now.getTime() + exp)) +// .signWith(secretKey, SignatureAlgorithm.HS256) +// .compact(); +// } +// +// +// // 권한정보 획득 +// // Spring Security 인증과정에서 권한확인을 위한 기능 +// public Authentication getAuthentication(String token) { +// UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserId(token)); +// return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); +// } +// +// +// public String getUserId(String token) { +// return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody().getSubject(); +// } +// +// public String resolveToken(HttpServletRequest request) { +// return request.getHeader("Authorization"); +// } +// +// +// //토큰에서 값 추출 +// public String getUsername(@RequestBody String token) { +// logger.info("[getUsername] 토큰 기반 회원 구별 정보 추출"); +// //secretKey 설정, sub 값 추출 +// String info = Jwts.parser().setSigningKey(secretKey) +// .parseClaimsJws(token).getBody().getSubject(); +// +// logger.info("[getUsername] 토큰 기반 회원 구별 정보 추출 완료, info : {}", info); +// return info; +// +// } +// +// //검증 +// public boolean validateToken(String token) { +// try { +// // Bearer 검증 +// if (!token.substring(0, "BEARER ".length()).equalsIgnoreCase("BEARER ")) { +// return false; +// } else { +// token = token.split(" ")[1].trim(); +// } +// Jws claims = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token); +// // 만료되었을 시 false +// return !claims.getBody().getExpiration().before(new Date()); +// } catch (Exception e) { +// return false; +// } +// } +//} diff --git a/src/main/java/com/example/capd/User/ModelMapperConfig.java b/src/main/java/com/example/capd/User/ModelMapperConfig.java new file mode 100644 index 0000000..4f6f5e8 --- /dev/null +++ b/src/main/java/com/example/capd/User/ModelMapperConfig.java @@ -0,0 +1,13 @@ +package com.example.capd.User; + +import org.modelmapper.ModelMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ModelMapperConfig { + @Bean + public ModelMapper modelMapper(){ + return new ModelMapper(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/Point/CustomAccessDeniedHandler.java b/src/main/java/com/example/capd/User/Point/CustomAccessDeniedHandler.java new file mode 100644 index 0000000..d6b0aa7 --- /dev/null +++ b/src/main/java/com/example/capd/User/Point/CustomAccessDeniedHandler.java @@ -0,0 +1,42 @@ +package com.example.capd.User.Point; + +import com.example.capd.User.dto.ErrorResponseDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; + +@Slf4j(topic = "FORBIDDEN_EXCEPTION_HANDLER") +@AllArgsConstructor +@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + private final ObjectMapper objectMapper; + + @Override + public void handle(HttpServletRequest request, + HttpServletResponse response, + AccessDeniedException accessDeniedException) throws IOException, ServletException { + log.error("No Authorities", accessDeniedException); + + ErrorResponseDto errorResponseDto = new ErrorResponseDto(HttpStatus.FORBIDDEN.value(), accessDeniedException.getMessage(), LocalDateTime.now()); + + String responseBody = objectMapper.writeValueAsString(errorResponseDto); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpStatus.FORBIDDEN.value()); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(responseBody); + } +} diff --git a/src/main/java/com/example/capd/User/Point/CustomAuthenticationEntryPoint.java b/src/main/java/com/example/capd/User/Point/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..092ea4a --- /dev/null +++ b/src/main/java/com/example/capd/User/Point/CustomAuthenticationEntryPoint.java @@ -0,0 +1,39 @@ +package com.example.capd.User.Point; + +import com.example.capd.User.dto.ErrorResponseDto; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; + +@Slf4j(topic = "UNAUTHORIZATION_EXCEPTION_HANDLER") +@AllArgsConstructor +@Component +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + private final ObjectMapper objectMapper; + + @Override + public void commence(HttpServletRequest request, + HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + log.error("Not Authenticated Request", authException); + + ErrorResponseDto errorResponseDto = new ErrorResponseDto(HttpStatus.UNAUTHORIZED.value(), authException.getMessage(), LocalDateTime.now()); + + String responseBody = objectMapper.writeValueAsString(errorResponseDto); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(responseBody); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/config/CommonResponse.java b/src/main/java/com/example/capd/User/config/CommonResponse.java index 6a48256..2334b2d 100644 --- a/src/main/java/com/example/capd/User/config/CommonResponse.java +++ b/src/main/java/com/example/capd/User/config/CommonResponse.java @@ -1,6 +1,9 @@ package com.example.capd.User.config; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.springframework.http.HttpStatus; @Builder @@ -12,5 +15,4 @@ public class CommonResponse { private HttpStatus httpStatus; private String message; private Object data; - } - +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/config/CustomExceptionManager.java b/src/main/java/com/example/capd/User/config/CustomExceptionManager.java index dda8e6d..686251c 100644 --- a/src/main/java/com/example/capd/User/config/CustomExceptionManager.java +++ b/src/main/java/com/example/capd/User/config/CustomExceptionManager.java @@ -34,8 +34,8 @@ public ResponseEntity TeamMemberModificationException(Unauthoriz .message(e.getMessage()).build(); return new ResponseEntity<>(res, res.getHttpStatus()); } - @ExceptionHandler(TeamNotConfirmedException.class) - public ResponseEntity TeamNotException(TeamNotConfirmedException e) { + @ExceptionHandler(ReviewSubmissionPeriodNotEndedException.class) + public ResponseEntity ReviewNotException(ReviewSubmissionPeriodNotEndedException e) { CommonResponse res = CommonResponse.builder() .code(HttpStatus.NOT_FOUND.value()) .httpStatus(HttpStatus.NOT_FOUND) diff --git a/src/main/java/com/example/capd/User/config/LoginUtil.java b/src/main/java/com/example/capd/User/config/LoginUtil.java new file mode 100644 index 0000000..f52e6c2 --- /dev/null +++ b/src/main/java/com/example/capd/User/config/LoginUtil.java @@ -0,0 +1,17 @@ +package com.example.capd.User.config; + +import org.springframework.security.core.context.SecurityContextHolder; + +public class LoginUtil { + + public static boolean isLogin(){ + boolean result = true; + + Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + if(principal instanceof String){ + result = false; + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/config/MvcConfig.java b/src/main/java/com/example/capd/User/config/MvcConfig.java new file mode 100644 index 0000000..6ee2586 --- /dev/null +++ b/src/main/java/com/example/capd/User/config/MvcConfig.java @@ -0,0 +1,16 @@ +package com.example.capd.User.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class MvcConfig implements WebMvcConfigurer { + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/**") + //.addResourceLocations("classpath:/templates/") + .setCacheControl(CacheControl.noCache()); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/config/PasswordEncoderConfig.java b/src/main/java/com/example/capd/User/config/PasswordEncoderConfig.java new file mode 100644 index 0000000..40f3dc9 --- /dev/null +++ b/src/main/java/com/example/capd/User/config/PasswordEncoderConfig.java @@ -0,0 +1,15 @@ +package com.example.capd.User.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class PasswordEncoderConfig { + + //PasswordEncoder Bean + @Bean + public BCryptPasswordEncoder passwordEncoder(){ + return new BCryptPasswordEncoder(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/config/SecurityConfig.java b/src/main/java/com/example/capd/User/config/SecurityConfig.java new file mode 100644 index 0000000..532e126 --- /dev/null +++ b/src/main/java/com/example/capd/User/config/SecurityConfig.java @@ -0,0 +1,125 @@ +package com.example.capd.User.config; + +import com.example.capd.User.JWT.JwtAuthFilter; +import com.example.capd.User.JWT.JwtUtil; +import com.example.capd.User.Point.CustomAccessDeniedHandler; +import com.example.capd.User.Point.CustomAuthenticationEntryPoint; +import com.example.capd.User.service.CustomUserDetailsService; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; + +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + + + +@Configuration +@EnableWebSecurity +@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) +@AllArgsConstructor +public class SecurityConfig { + private final CustomUserDetailsService customUserDetailsService; + private final JwtUtil jwtUtil; + private final CustomAccessDeniedHandler accessDeniedHandler; + private final CustomAuthenticationEntryPoint authenticationEntryPoint; + + private static final String[] AUTH_WHITELIST = { + "http://localhost:3000/user/signup", "/user/**", "/", "/user/login", + "/v3/api-docs/**", "http://localhost:3000/user/login", "/user/signup", + "ws://localhost:8080/ws","/sub/chat/**","/data","/success" + }; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + //CSRF, CORS + http.csrf((csrf) -> csrf.disable()); + http.cors(Customizer.withDefaults()); + + //세션 관리 상태 없음으로 구성, Spring Security가 세션 생성 or 사용 X + http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)); + + //FormLogin, BasicHttp 비활성화 + http.formLogin((form) -> form.disable()); + http.httpBasic(AbstractHttpConfigurer::disable); + + + //JwtAuthFilter를 UsernamePasswordAuthenticationFilter 앞에 추가 + http.addFilterBefore(new JwtAuthFilter(customUserDetailsService, jwtUtil), UsernamePasswordAuthenticationFilter.class); + + http.exceptionHandling((exceptionHandling) -> exceptionHandling + .authenticationEntryPoint(authenticationEntryPoint) + .accessDeniedHandler(accessDeniedHandler) + ); + + // 권한 규칙 작성 + http.authorizeHttpRequests(authorize -> authorize + .requestMatchers(AUTH_WHITELIST).permitAll() + .anyRequest().permitAll() + ); + + return http.build(); + } + + +} + +//@EnableWebSecurity +//@Configuration +//@EnableMethodSecurity +//@RequiredArgsConstructor +//public class SecurityConfig extends SecurityConfigurerAdapter { +// +// private final CustomUserDetailsService userService; +// private final TokenProvider tokenProvider; +// +// @Override +// public void configure(HttpSecurity http) { +// http.addFilterBefore( +// new TokenAuthenticationFilter(tokenProvider), +// UsernamePasswordAuthenticationFilter.class +// ); +// } +// +// +// +// @Bean +// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { +// http +// // ID, Password 문자열을 Base64로 인코딩하여 전달하는 구조 +// .csrf(AbstractHttpConfigurer::disable) +// .httpBasic(AbstractHttpConfigurer::disable) +// .formLogin(AbstractHttpConfigurer::disable) +// .authorizeHttpRequests((authorize) -> authorize +// .requestMatchers("/user/**","/signup", "/", "/user/login").permitAll() +// .anyRequest().permitAll()) +// // 폼 로그인은 현재 사용하지 않음 +//// .formLogin(formLogin -> formLogin +//// .loginPage("/login") +//// .defaultSuccessUrl("/home")) +// .logout((logout) -> logout +// .logoutSuccessUrl("/login") +// .invalidateHttpSession(true)) +// .sessionManagement(session -> session +// .sessionCreationPolicy(SessionCreationPolicy.STATELESS) +// ) +// .addFilterBefore(new TokenAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class); +// +// return http.build(); +// } +// +// +// @Bean +// public PasswordEncoder passwordEncoder() { +// return new BCryptPasswordEncoder(); +// } +//} +// + diff --git a/src/main/java/com/example/capd/User/controller/ParticipationController.java b/src/main/java/com/example/capd/User/controller/ParticipationController.java index 93dc308..da41e6f 100644 --- a/src/main/java/com/example/capd/User/controller/ParticipationController.java +++ b/src/main/java/com/example/capd/User/controller/ParticipationController.java @@ -1,13 +1,17 @@ package com.example.capd.User.controller; import com.example.capd.User.config.CommonResponse; +import com.example.capd.User.dto.ContestDto; import com.example.capd.User.dto.ParticipationParam; import com.example.capd.User.service.ParticipationService; +import lombok.Getter; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequiredArgsConstructor public class ParticipationController { @@ -39,4 +43,10 @@ public ResponseEntity deletePart(@PathVariable Long contestId, @ ); return new ResponseEntity<>(res, res.getHttpStatus()); } + + //참여 신청한 공모전 리스트 + @GetMapping("/participation/{userId}") + public List myContestList(@PathVariable String userId){ + return participationService.myContestList(userId); + } } diff --git a/src/main/java/com/example/capd/User/controller/ProfileController.java b/src/main/java/com/example/capd/User/controller/ProfileController.java index 42235bc..f6ebfb4 100644 --- a/src/main/java/com/example/capd/User/controller/ProfileController.java +++ b/src/main/java/com/example/capd/User/controller/ProfileController.java @@ -6,7 +6,10 @@ import com.example.capd.User.dto.ProfileResponseDto; import com.example.capd.User.service.ProfileService; import lombok.RequiredArgsConstructor; +import org.springframework.core.io.FileSystemResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -14,6 +17,7 @@ @RestController @RequiredArgsConstructor +@CrossOrigin(origins = "http://localhost:3000/", allowedHeaders = "*") public class ProfileController { private final ProfileService profileService; @@ -63,9 +67,16 @@ public ResponseEntity deleteProfile(@PathVariable String userId) return new ResponseEntity<>(res, res.getHttpStatus()); } - @GetMapping("profile-stack/{contestId}/{userId}") - public List stackProfileList(@PathVariable Long contestId, @PathVariable String userId){ - return profileService.stackRecommendUsers(contestId, userId); + //전체 추천 + @GetMapping("profile-list/{contestId}/{userId}") + public List profileList(@PathVariable Long contestId, @PathVariable String userId){ + return profileService.recommendUsers(contestId, userId); } -} + // ai 추천 + @GetMapping("profile-ai/{contestId}/{userId}") + public List aiProfileList(@PathVariable Long contestId, @PathVariable String userId){ + profileService.aiStart(contestId, userId); + return profileService.aiRecommendUsers(contestId, userId); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/controller/UserController.java b/src/main/java/com/example/capd/User/controller/UserController.java new file mode 100644 index 0000000..7fbf706 --- /dev/null +++ b/src/main/java/com/example/capd/User/controller/UserController.java @@ -0,0 +1,107 @@ +package com.example.capd.User.controller; + +import com.example.capd.User.dto.*; +import com.example.capd.User.domain.User; +import com.example.capd.User.repository.UserRepository; +import com.example.capd.User.service.AuthService; +import com.example.capd.User.service.UserService; +import com.example.capd.User.config.CommonResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; +import org.springframework.ui.Model; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/user") +@CrossOrigin(origins = "http://localhost:3000/", allowedHeaders = "*") +public class UserController { + + private final UserService userService; + private final UserRepository userRepository; + private final AuthService authService; + + @PostMapping("/login") + public ResponseEntity getMemberProfile( + @Validated @RequestBody LoginRequestDto request + ) { + String token = this.authService.login(request); + String userId = request.getUserId(); // 클라이언트에게 전달할 사용자 아이디 + String id = String.valueOf(userRepository.findUserByUserId(userId).getId()); + String name = String.valueOf(userRepository.findUserByUserId(userId).getUsername()); + Map response = new HashMap<>(); + response.put("token", token); + response.put("userId", userId); + response.put("id", id); + response.put("name", name); + return ResponseEntity.status(HttpStatus.OK).body(response); + } + + @PostMapping("/signup") + public ResponseEntity join(@RequestBody UserDTO userDTO){ + userService.save(userDTO); + CommonResponse res = new CommonResponse( + 200, + HttpStatus.OK, + " 성공", + null + ); + return new ResponseEntity<>(res, res.getHttpStatus()); + //return userService.save(dto); + } + + @PostMapping("/idCheck") + public ResponseEntity checkUserId(@RequestBody UserIdRequest request) { + String userId = request.getUserId(); + Optional userOptional = userRepository.findFirstByUserId(userId); + CommonResponse res; + if (userOptional.isPresent()) { + res = new CommonResponse(400, HttpStatus.BAD_REQUEST, "아이디가 이미 존재합니다.", null); + return new ResponseEntity<>(res, res.getHttpStatus()); + } else { + res = new CommonResponse(200, HttpStatus.OK, "사용 가능한 아이디입니다.", null); + return new ResponseEntity<>(res, res.getHttpStatus()); + } + } + + @GetMapping("/get") + public ResponseEntity getUser(@RequestParam String userId) throws Exception { + return new ResponseEntity<>(userService.getUser(userId), HttpStatus.OK); + } + + @GetMapping("/logout") + public String logout(HttpServletRequest request, HttpServletResponse response){ + new SecurityContextLogoutHandler().logout(request,response, + SecurityContextHolder.getContext().getAuthentication()); + return "redirect:/login"; + } + + @PutMapping("/update") + public ResponseEntity updateUserInformation(@RequestBody UserUpdateRequest request) { + userService.updateUserInformation(request); + return ResponseEntity.status(HttpStatus.OK).body("User information updated successfully."); + } + + @DeleteMapping("/delete/{userId}") + public ResponseEntity deleteUser(@PathVariable String userId) { + try { + userService.deleteUser(userId); + return ResponseEntity.ok("User deleted successfully"); + } catch (UsernameNotFoundException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found: " + userId); + } + } +} diff --git a/src/main/java/com/example/capd/User/domain/Authority.java b/src/main/java/com/example/capd/User/domain/Authority.java new file mode 100644 index 0000000..34dc637 --- /dev/null +++ b/src/main/java/com/example/capd/User/domain/Authority.java @@ -0,0 +1,29 @@ +package com.example.capd.User.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class Authority { + @Id + @JsonIgnore + private Long AuthorityId; + + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + public void setUser(User user) { + this.user = user; + } +} diff --git a/src/main/java/com/example/capd/User/domain/Participation.java b/src/main/java/com/example/capd/User/domain/Participation.java index b8961e4..bfe9cf7 100644 --- a/src/main/java/com/example/capd/User/domain/Participation.java +++ b/src/main/java/com/example/capd/User/domain/Participation.java @@ -18,7 +18,6 @@ public class Participation { private Long id; private String additional; - private String time; //원하는 스택 @ElementCollection diff --git a/src/main/java/com/example/capd/User/domain/Profile.java b/src/main/java/com/example/capd/User/domain/Profile.java index 02b0ded..d1637cb 100644 --- a/src/main/java/com/example/capd/User/domain/Profile.java +++ b/src/main/java/com/example/capd/User/domain/Profile.java @@ -1,5 +1,6 @@ package com.example.capd.User.domain; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.*; @@ -19,6 +20,10 @@ public class Profile { private String intro; //한줄 소개 private double rate; //평점, 기본 평점 3.5 + private int myTime; // 내가 가능한 투자 가능 시간 + private int desiredTime; // 상대에게 바라는 투자 가능 시간 + private int collaborationCount; // 상대 협업 경험 횟수 + //기술 스택 @ElementCollection @JsonManagedReference @@ -39,12 +44,16 @@ public void setRate(double rate){ } @Builder - public Profile(Long id, String intro, List stackList, User user, List careers, double rate){ + public Profile(Long id, String intro, List stackList, User user, List careers, double rate, + int myTime, int desiredTime, int collaborationCount ){ this.id=id; this.intro=intro; this.stackList=stackList; this.user=user; this.careers=careers; this.rate=rate; + this.myTime = myTime; + this.desiredTime = desiredTime; + this.collaborationCount = collaborationCount; } } diff --git a/src/main/java/com/example/capd/User/domain/Review.java b/src/main/java/com/example/capd/User/domain/Review.java index 4ea9f89..9ca5321 100644 --- a/src/main/java/com/example/capd/User/domain/Review.java +++ b/src/main/java/com/example/capd/User/domain/Review.java @@ -1,6 +1,6 @@ package com.example.capd.User.domain; -import com.example.capd.team.domain.Team; +import com.example.capd.team.domain.Room; import com.fasterxml.jackson.annotation.JsonBackReference; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.Entity; @@ -38,9 +38,9 @@ public class Review { @JoinColumn(name = "reviewer_id") private User reviewer; -// //팀이랑 매핑 +// room이랑 매핑 @ManyToOne - @JoinColumn(name = "team_id") - private Team team; + @JoinColumn(name = "room_id") + private Room room; } diff --git a/src/main/java/com/example/capd/User/domain/User.java b/src/main/java/com/example/capd/User/domain/User.java index 7bebab9..b3a4239 100644 --- a/src/main/java/com/example/capd/User/domain/User.java +++ b/src/main/java/com/example/capd/User/domain/User.java @@ -4,36 +4,49 @@ import com.fasterxml.jackson.annotation.JsonManagedReference; import jakarta.persistence.*; import lombok.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import java.util.ArrayList; +import java.util.Collection; import java.util.List; @Entity +@Data +@NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder -@Getter @AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PROTECTED) public class User { - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - private String userId; + private String username; private String password; - private String userName; - private String email; - private String phone; + private String Email; + private char gender; private String address; - private String gender; - private String tendency; + private String Tendency; + private String Phone; + + + @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private List roles = new ArrayList<>(); + + public void setRoles(List roles) { + this.roles = roles; + roles.forEach(role -> role.setUser(this)); + } + //프로필과 일대일 매핑 @JsonManagedReference - @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) + @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER) private Profile profile; -// //팀멤버 매핑 + + // //팀멤버 매핑 @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List teamMembers = new ArrayList<>(); @@ -46,5 +59,4 @@ public class User { @JsonManagedReference private List receivedReviews = new ArrayList<>(); - } diff --git a/src/main/java/com/example/capd/User/dto/CareerParam.java b/src/main/java/com/example/capd/User/dto/CareerParam.java index fbb643c..4d41491 100644 --- a/src/main/java/com/example/capd/User/dto/CareerParam.java +++ b/src/main/java/com/example/capd/User/dto/CareerParam.java @@ -13,7 +13,7 @@ public class CareerParam { private String title; //공모명 private String stack; //프로젝트에서 사용한 스택 private int period; //프로젝트 기간 - private String gitHub; //깃허브 주소 (null) + private String gitHub; @Builder public CareerParam(String title, String stack, int period, String gitHub){ diff --git a/src/main/java/com/example/capd/User/dto/ContestDto.java b/src/main/java/com/example/capd/User/dto/ContestDto.java new file mode 100644 index 0000000..9bff709 --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/ContestDto.java @@ -0,0 +1,18 @@ +package com.example.capd.User.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class ContestDto { + private Long contestId; + private String title; + + @Builder + ContestDto(Long contestId, String title){ + this.contestId = contestId; + this.title = title; + } +} diff --git a/src/main/java/com/example/capd/User/dto/CreateAccessTokenRequest.java b/src/main/java/com/example/capd/User/dto/CreateAccessTokenRequest.java new file mode 100644 index 0000000..eaa0346 --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/CreateAccessTokenRequest.java @@ -0,0 +1,8 @@ +package com.example.capd.User.dto; + +import lombok.Data; + +@Data +public class CreateAccessTokenRequest { + private String refreshToken; +} diff --git a/src/main/java/com/example/capd/User/dto/CreateAccessTokenResponse.java b/src/main/java/com/example/capd/User/dto/CreateAccessTokenResponse.java new file mode 100644 index 0000000..1b95b2e --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/CreateAccessTokenResponse.java @@ -0,0 +1,10 @@ +package com.example.capd.User.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class CreateAccessTokenResponse { + private String accessToken; +} diff --git a/src/main/java/com/example/capd/User/dto/CustomUserInfoDto.java b/src/main/java/com/example/capd/User/dto/CustomUserInfoDto.java new file mode 100644 index 0000000..7fdfebc --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/CustomUserInfoDto.java @@ -0,0 +1,28 @@ +package com.example.capd.User.dto; + +import com.example.capd.User.domain.Authority; +import lombok.*; + +import java.util.List; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CustomUserInfoDto{ + + private Long id; + private String userId; + private String username; + private String password; + private String Email; + private char gender; + private String address; + private String Tendency; + private String Phone; + private List roles; + + +} + diff --git a/src/main/java/com/example/capd/User/dto/ErrorResponseDto.java b/src/main/java/com/example/capd/User/dto/ErrorResponseDto.java new file mode 100644 index 0000000..8ef6a26 --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/ErrorResponseDto.java @@ -0,0 +1,13 @@ +package com.example.capd.User.dto; +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +public class ErrorResponseDto { + private int status; + private String message; + private LocalDateTime timestamp; +} diff --git a/src/main/java/com/example/capd/User/dto/LoginRequestDto.java b/src/main/java/com/example/capd/User/dto/LoginRequestDto.java new file mode 100644 index 0000000..94ff27c --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/LoginRequestDto.java @@ -0,0 +1,19 @@ +package com.example.capd.User.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.antlr.v4.runtime.misc.NotNull; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class LoginRequestDto { + + @NotNull + private String userId; + + + @NotNull + private String password; +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/dto/ParticipationParam.java b/src/main/java/com/example/capd/User/dto/ParticipationParam.java index bc87638..efd5cd8 100644 --- a/src/main/java/com/example/capd/User/dto/ParticipationParam.java +++ b/src/main/java/com/example/capd/User/dto/ParticipationParam.java @@ -16,13 +16,11 @@ public class ParticipationParam { private String userId; private Long contestId; private String additional; - private String time; private List stackList; public Participation toEntity(User user, Contest contest){ return Participation.builder() .additional(additional) - .time(time) .stackList(stackList) .contest(contest) .user(user).build(); diff --git a/src/main/java/com/example/capd/User/dto/ProfileParticipationRes.java b/src/main/java/com/example/capd/User/dto/ProfileParticipationRes.java index aac5016..4e52601 100644 --- a/src/main/java/com/example/capd/User/dto/ProfileParticipationRes.java +++ b/src/main/java/com/example/capd/User/dto/ProfileParticipationRes.java @@ -12,12 +12,13 @@ public class ProfileParticipationRes { private Long id;//프로필 기본키 private String userId; + private String userName; private String intro; //한줄 소개 private double rate; - private List stackList; //스택 + private List stackList; //스택(본인) private List careers; //조회할때 경력도 한번에 넘겨주기 위함 - private String additional; - private String time; + private String additional; //원하는 주제나 프로젝트 + private int time; diff --git a/src/main/java/com/example/capd/User/dto/ProfileRequestDto.java b/src/main/java/com/example/capd/User/dto/ProfileRequestDto.java index 0d45f2e..74d954f 100644 --- a/src/main/java/com/example/capd/User/dto/ProfileRequestDto.java +++ b/src/main/java/com/example/capd/User/dto/ProfileRequestDto.java @@ -17,16 +17,24 @@ public class ProfileRequestDto { private String intro; //한줄 소개 private List stackList; //스택 private List careers; + private int myTime; // 내가 가능한 투자 가능 시간 + private int desiredTime; // 상대에게 바라는 투자 가능 시간 + private int collaborationCount; // 상대 협업 경험 횟수 @Builder - public ProfileRequestDto(String userId,String intro, List stackList){ + public ProfileRequestDto(String userId,String intro, List stackList, int myTime, int desiredTime, int collaborationCount){ this.userId = userId; this.intro = intro; this.stackList = stackList; + this.myTime = myTime; + this.desiredTime = desiredTime; + this.collaborationCount = collaborationCount; } public Profile toEntity(User user) { - return Profile.builder().intro(intro).stackList(stackList).user(user).build(); + return Profile.builder().intro(intro).stackList(stackList). + myTime(myTime).desiredTime(desiredTime).collaborationCount(collaborationCount) + .user(user).build(); } public List getCareers() { diff --git a/src/main/java/com/example/capd/User/dto/ProfileResponseDto.java b/src/main/java/com/example/capd/User/dto/ProfileResponseDto.java index 9fdf4f3..9cab676 100644 --- a/src/main/java/com/example/capd/User/dto/ProfileResponseDto.java +++ b/src/main/java/com/example/capd/User/dto/ProfileResponseDto.java @@ -12,9 +12,14 @@ public class ProfileResponseDto { private Long id;//프로필 기본키 private String userId; + private String userName; private String intro; //한줄 소개 private double rate; private List stackList; //스택 private List careers; //조회할때 경력도 한번에 넘겨주기 위함 + private int myTime; + private int desiredTime; + private int collaborationCount; + } \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/dto/ReviewRequestDto.java b/src/main/java/com/example/capd/User/dto/ReviewRequestDto.java index f38ab14..f32242e 100644 --- a/src/main/java/com/example/capd/User/dto/ReviewRequestDto.java +++ b/src/main/java/com/example/capd/User/dto/ReviewRequestDto.java @@ -1,31 +1,31 @@ package com.example.capd.User.dto; import com.example.capd.User.domain.Review; -import com.example.capd.team.domain.Team; import com.example.capd.User.domain.User; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import com.example.capd.team.domain.Room; +import lombok.*; @Getter @Setter @NoArgsConstructor(access = AccessLevel.PUBLIC) +@Builder +@AllArgsConstructor public class ReviewRequestDto { private String content; private double rate; private String reviewerId; //리뷰 쓰는 private String reviewedUserId; //리뷰 받는 - private Long teamId; + private Long roomId; + private Long contestId; - public Review toEntity(User reviewerId,User reviewedUserId, Team team) { + public Review toEntity(User reviewerId,User reviewedUserId, Room room) { return Review.builder() .content(content) .rate(rate) .reviewer(reviewerId) .reviewedUser(reviewedUserId) - .team(team).build(); + .room(room).build(); } } diff --git a/src/main/java/com/example/capd/User/dto/SignRequest.java b/src/main/java/com/example/capd/User/dto/SignRequest.java new file mode 100644 index 0000000..94f90a8 --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/SignRequest.java @@ -0,0 +1,18 @@ +package com.example.capd.User.dto; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SignRequest{ + private Long id; + private String userId; + private String username; + private String password; + private String Email; + private char gender; + private String address; + private String Tendency; + private String Phone; +} diff --git a/src/main/java/com/example/capd/User/dto/SignResponse.java b/src/main/java/com/example/capd/User/dto/SignResponse.java new file mode 100644 index 0000000..7c1ddcb --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/SignResponse.java @@ -0,0 +1,40 @@ +package com.example.capd.User.dto; + +import com.example.capd.User.domain.Authority; +import com.example.capd.User.domain.User; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class SignResponse { + + private Long id; + private String userId; + private String username; + private String Email; + private char gender; + private String address; + private String Tendency; + private String Phone; + + private List roles = new ArrayList<>(); + private String token; + + public SignResponse(User user){ + this.id = user.getId(); + this.userId = user.getUserId(); + this.username = user.getUsername(); + this.Email = user.getEmail(); + this.gender = user.getGender(); + this.address = user.getAddress(); + this.Tendency = user.getTendency(); + this.Phone = user.getPhone(); + this.roles = user.getRoles(); + } + +} diff --git a/src/main/java/com/example/capd/User/dto/UserDTO.java b/src/main/java/com/example/capd/User/dto/UserDTO.java new file mode 100644 index 0000000..e52aa25 --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/UserDTO.java @@ -0,0 +1,45 @@ +package com.example.capd.User.dto; + +import com.example.capd.User.domain.Authority; +import com.example.capd.User.domain.User; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class UserDTO { + + private String userId; + private String username; + private String password; + private String Email; + private char gender; + private String address; + private String Tendency; + private String Phone; + + + + // UserDTO 클래스에서의 toEntity() 메서드 + @Builder + public User toEntity() { + + return User.builder() + .userId(userId) + .username(username) + .password(password) + .Email(Email) + .gender(gender) + .address(address) + .Tendency(Tendency) + .Phone(Phone) + .build(); + } + + +} diff --git a/src/main/java/com/example/capd/User/dto/UserIdRequest.java b/src/main/java/com/example/capd/User/dto/UserIdRequest.java new file mode 100644 index 0000000..4cb2b6f --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/UserIdRequest.java @@ -0,0 +1,10 @@ +package com.example.capd.User.dto; + +import lombok.Data; + +@Data +public class UserIdRequest { + private String userId; + +} + diff --git a/src/main/java/com/example/capd/User/dto/UserUpdateRequest.java b/src/main/java/com/example/capd/User/dto/UserUpdateRequest.java new file mode 100644 index 0000000..b261eee --- /dev/null +++ b/src/main/java/com/example/capd/User/dto/UserUpdateRequest.java @@ -0,0 +1,15 @@ +package com.example.capd.User.dto; + +import lombok.Data; + +@Data +public class UserUpdateRequest { + private String userId; + private String newEmail; + private String newPassword; + private String newUsername; + private char newGender; + private String newAddress; + private String newTendency; + private String newPhone; +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/repository/ParticipationRepository.java b/src/main/java/com/example/capd/User/repository/ParticipationRepository.java index dd904bf..5bd166a 100644 --- a/src/main/java/com/example/capd/User/repository/ParticipationRepository.java +++ b/src/main/java/com/example/capd/User/repository/ParticipationRepository.java @@ -6,18 +6,25 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface ParticipationRepository extends JpaRepository { @Query("SELECT p FROM Participation p " + "JOIN p.user u " + "JOIN p.contest c " + - "WHERE c.id = :contestId AND u.userId = :userId") + "WHERE c.id = :contestId AND u.id = :userId") Participation findParticipationByContestIdAndUserId( @Param("contestId") Long contestId, - @Param("userId") String userId + @Param("userId") Long userId ); + @Query("SELECT p FROM Participation p " + + "JOIN p.user u " + + "WHERE u.id = :userId") + List findParticipationsByUserId(@Param("userId") Long userId); + } diff --git a/src/main/java/com/example/capd/User/repository/UserRepository.java b/src/main/java/com/example/capd/User/repository/UserRepository.java index dfbca13..4da34d0 100644 --- a/src/main/java/com/example/capd/User/repository/UserRepository.java +++ b/src/main/java/com/example/capd/User/repository/UserRepository.java @@ -1,23 +1,34 @@ package com.example.capd.User.repository; -import com.example.capd.User.domain.Review; import com.example.capd.User.domain.User; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.data.repository.query.Param; +import org.springframework.data.jpa.repository.Query; import java.util.List; import java.util.Optional; +import com.example.capd.User.domain.Review; + @Repository public interface UserRepository extends JpaRepository { - Optional findByUserId(String userId); - List findReceivedReviewsByUserId(String userId); + User findByUsername(String userId); + + Optional findByUserId(String userId); + + User findUserByUserId(String userId); + + + List findReceivedReviewsByUserId(String userId); + + Optional findFirstByUserId(String userId); - @Query("SELECT u FROM User u JOIN Participation part ON u.id = part.user.id WHERE part.contest.id = :contestId") - List findUsersByContestParticipation(@Param("contestId") Long contestId); + @Query("SELECT u FROM User u WHERE u.username = :username") + Optional findOneWithAuthoritiesByUsername(@Param("username") String username); - List findAllByUserIdIn(List membersId); + @Query("SELECT u FROM User u JOIN Participation part ON u.id = part.user.id WHERE part.contest.id = :contestId") + List findUsersByContestParticipation(@Param("contestId") Long contestId); + List findAllByUserIdIn(List membersId); } diff --git a/src/main/java/com/example/capd/User/service/AuthService.java b/src/main/java/com/example/capd/User/service/AuthService.java new file mode 100644 index 0000000..8bfa347 --- /dev/null +++ b/src/main/java/com/example/capd/User/service/AuthService.java @@ -0,0 +1,44 @@ +package com.example.capd.User.service; + +import com.example.capd.User.JWT.JwtUtil; +import com.example.capd.User.domain.User; +import com.example.capd.User.dto.CustomUserInfoDto; +import com.example.capd.User.dto.LoginRequestDto; +import com.example.capd.User.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AuthService{ + + private final JwtUtil jwtUtil; + private final UserRepository userRepository; + private final PasswordEncoder encoder; + private final ModelMapper modelMapper; + @Transactional + public String login(LoginRequestDto dto) { + String userId = dto.getUserId(); + String password = dto.getPassword(); + User user = userRepository.findUserByUserId(userId); + if(user == null) { + throw new UsernameNotFoundException("Id가 존재하지 않습니다."); + } + + // 암호화된 password를 디코딩한 값과 입력한 패스워드 값이 다르면 null 반환 + if(!encoder.matches(password, user.getPassword())) { + throw new BadCredentialsException("비밀번호가 일치하지 않습니다."); + } + + CustomUserInfoDto info = modelMapper.map(user, CustomUserInfoDto.class); + + String accessToken = jwtUtil.createAccessToken(info); + return accessToken; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/service/CustomUserDetails.java b/src/main/java/com/example/capd/User/service/CustomUserDetails.java new file mode 100644 index 0000000..8d4208d --- /dev/null +++ b/src/main/java/com/example/capd/User/service/CustomUserDetails.java @@ -0,0 +1,65 @@ +package com.example.capd.User.service; + +import com.example.capd.User.domain.User; +import com.example.capd.User.dto.CustomUserInfoDto; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +@Getter +@RequiredArgsConstructor +public class CustomUserDetails implements UserDetails { + + private final CustomUserInfoDto user; + + @Override + public Collection getAuthorities() { + List roles = new ArrayList<>(); + roles.add("ROLE_" + user.getRoles().toString()); + + + return roles.stream() + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getId().toString(); + } + + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + + +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/User/service/CustomUserDetailsService.java b/src/main/java/com/example/capd/User/service/CustomUserDetailsService.java new file mode 100644 index 0000000..893a7aa --- /dev/null +++ b/src/main/java/com/example/capd/User/service/CustomUserDetailsService.java @@ -0,0 +1,53 @@ +package com.example.capd.User.service; + + +import com.example.capd.User.domain.User; +import com.example.capd.User.dto.CustomUserInfoDto; +import com.example.capd.User.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.modelmapper.ModelMapper; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + private final UserRepository userRepository; + private final ModelMapper mapper; + + @Override + public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException { + User user = userRepository.findById(Long.parseLong(id)) + .orElseThrow(() -> new UsernameNotFoundException("해당하는 유저가 없습니다.")); + + CustomUserInfoDto dto = mapper.map(user, CustomUserInfoDto.class); + + return new CustomUserDetails(dto); + } +} + + +// private User createUser(String username, User user) { +// if (!user.isActivated()) { +// throw new RuntimeException(username + " -> 활성화되어 있지 않습니다."); +// } +// +// List grantedAuthorities = user.getAuthorities().stream() +// .map(authority -> new SimpleGrantedAuthority(authority.getAuthority())) +// .collect(Collectors.toList()); +// +// return new User(user.getUsername(), +// user.getPassword(), +// grantedAuthorities); +// } diff --git a/src/main/java/com/example/capd/User/service/ParticipationService.java b/src/main/java/com/example/capd/User/service/ParticipationService.java index 2e91527..5545ced 100644 --- a/src/main/java/com/example/capd/User/service/ParticipationService.java +++ b/src/main/java/com/example/capd/User/service/ParticipationService.java @@ -3,6 +3,7 @@ import com.example.capd.Exception.AlreadyAppliedException; import com.example.capd.User.domain.Participation; import com.example.capd.User.domain.User; +import com.example.capd.User.dto.ContestDto; import com.example.capd.User.dto.ParticipationParam; import com.example.capd.contest.repository.ContestRepository; import com.example.capd.User.repository.ParticipationRepository; @@ -11,6 +12,10 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import com.example.capd.contest.domain.Contest; + +import java.util.List; +import java.util.stream.Collectors; + @Service @RequiredArgsConstructor public class ParticipationService { @@ -24,17 +29,15 @@ public void saveParticipation(ParticipationParam participationParam){ String userId = participationParam.getUserId(); Long contestId = participationParam.getContestId(); - Participation participationExists = participationRepository.findParticipationByContestIdAndUserId(contestId, userId); - - if (participationExists !=null) { - throw new AlreadyAppliedException(); - } - User user = userRepository.findByUserId(userId) .orElseThrow(() -> new EntityNotFoundException("유저를 찾을 수 없습니다. id=" + userId)); Contest contest = contestRepository.findById(contestId) .orElseThrow(() -> new EntityNotFoundException("공모전을 찾을 수 없습니다. id=" + contestId)); + Participation participationExists = participationRepository.findParticipationByContestIdAndUserId(contestId, user.getId()); + if (participationExists !=null) { + throw new AlreadyAppliedException(); + } Participation participation = participationParam.toEntity(user, contest); participationRepository.save(participation); @@ -46,8 +49,22 @@ public void deleteParticipation(Long contestId,String userId){ Contest contest = contestRepository.findById(contestId) .orElseThrow(()-> new EntityNotFoundException("해당 공모전을 찾을 수 없습니다. 현황을 확인하세요="+contestId)); - Participation participation = participationRepository.findParticipationByContestIdAndUserId(contestId, userId); + Participation participation = participationRepository.findParticipationByContestIdAndUserId(contestId, user.getId()); participationRepository.deleteById(participation.getId()); } + + public List myContestList(String userId){ + User user = userRepository.findUserByUserId(userId); + List participations = participationRepository.findParticipationsByUserId(user.getId()); + + return participations.stream() + .map(participation -> { + Contest contest = participation.getContest(); + ContestDto contestDto = ContestDto.builder(). + contestId(contest.getId()).title(contest.getTitle()).build(); + return contestDto; + }) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/com/example/capd/User/service/ProfileService.java b/src/main/java/com/example/capd/User/service/ProfileService.java index 26f83d4..f475930 100644 --- a/src/main/java/com/example/capd/User/service/ProfileService.java +++ b/src/main/java/com/example/capd/User/service/ProfileService.java @@ -19,15 +19,18 @@ public interface ProfileService { public ProfileResponseDto getMyProfile(String userId); //필요스택 필터링 일치 하는 프로필 리스트 (ai 적용x) - public List stackRecommendUsers(Long contestId, String userId); + // public List stackRecommendUsers(Long contestId, String userId); + + public List recommendUsers(Long contestId, String userId); //프로필 전체 조회 (ai 추천 프로필), 공모전 id값 받기 - public List aiRecommendUsers(String userId, Long contestId); + public List aiRecommendUsers(Long contestId, String userId); //프로필 수정 public void editProfile(ProfileRequestDto profileRequestDto); //프로필 삭제(user 매핑 때문에, user 삭제되어야 프로필 삭제됨, user 탈퇴시 사용) public void deleteProfile(String userId); + public void aiStart(Long contestId, String userId); } diff --git a/src/main/java/com/example/capd/User/service/ProfileServiceImpl.java b/src/main/java/com/example/capd/User/service/ProfileServiceImpl.java index ec51b6c..a6b9e5a 100644 --- a/src/main/java/com/example/capd/User/service/ProfileServiceImpl.java +++ b/src/main/java/com/example/capd/User/service/ProfileServiceImpl.java @@ -4,10 +4,17 @@ import com.example.capd.User.domain.*; import com.example.capd.User.dto.*; import com.example.capd.User.repository.*; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; + +import org.hibernate.dialect.function.PostgreSQLTruncRoundFunction; +import org.json.simple.*; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import org.springframework.stereotype.Service; +import java.io.*; import java.util.*; import java.util.stream.Collectors; @@ -26,7 +33,9 @@ public void saveProfile(ProfileRequestDto profileRequestDto) { //유저 존재 유무 확인 Optional userOptional = userRepository.findByUserId(profileRequestDto.getUserId()); - User user = userRepository.findByUserId(profileRequestDto.getUserId()).get(); + User user = userRepository.findByUserId(profileRequestDto.getUserId()) + .orElseThrow(() -> new EntityNotFoundException("해당하는 유저를 찾을 수 없습니다. userId=" + profileRequestDto.getUserId())); + if (user.getProfile() != null) { throw new IllegalArgumentException("프로필이 이미 존재합니다. id=" + profileRequestDto.getUserId()); } @@ -36,9 +45,12 @@ public void saveProfile(ProfileRequestDto profileRequestDto) { profile.setRate(rate); profileRepository.save(profile); - List careers = profileRequestDto.getCareers().stream() - .map(careerParam -> careerParam.toEntity(profile)) - .collect(Collectors.toList()); + List careers = profileRequestDto.getCareers() != null ? + profileRequestDto.getCareers().stream() + .map(careerParam -> careerParam.toEntity(profile)) + .collect(Collectors.toList()) : + Collections.emptyList(); + careers.forEach(careerRepository::save); @@ -54,48 +66,88 @@ public ProfileResponseDto getMyProfile(String userId) { } @Override - public List stackRecommendUsers(Long contestId, String userId) { - - Participation participation = participationRepository.findParticipationByContestIdAndUserId(contestId, userId); - List stackList = (participation != null) ? participation.getStackList() : Collections.emptyList(); + public List recommendUsers(Long contestId, String userId){ + User user1 = userRepository.findUserByUserId(userId); - List matchingUsers; + List matchingUsers = userRepository.findUsersByContestParticipation(contestId); - matchingUsers = userRepository.findUsersByContestParticipation(contestId); - - //본일 프로필 제외,stackList 일치율 0인 사람은 제외, 일치울 높은순으로 정렬,팀 있는 유저 제외 List resultProfiles = matchingUsers.stream() - .filter(user -> - !user.getUserId().equals(userId) && - user.getProfile() != null && - user.getProfile().getStackList().stream().anyMatch(stackList::contains) && - !hasTeamForContest(user, contestId) - ) - .sorted((user1, user2) -> - (int) user2.getProfile().getStackList().stream().filter(stackList::contains).count() - - (int) user1.getProfile().getStackList().stream().filter(stackList::contains).count()) + .filter(user -> !user.getUserId().equals(userId)&& !hasTeamForContest(user, contestId)) .map(user -> { - if (user.getProfile() != null) { - return mapToProfileParticipation(user.getProfile(),participation); - } - return null; + Participation userParticipation = participationRepository.findParticipationByContestIdAndUserId(contestId, user.getId()); + return mapToProfileParticipation(user.getProfile(),userParticipation); }) - .filter(Objects::nonNull) .collect(Collectors.toList()); - if (resultProfiles.isEmpty()) { - throw new UserWithDesiredStackNotFoundException(); - } return resultProfiles; } + // 해당 공모전에 팀이 없는지 확인하는 메서드 private boolean hasTeamForContest(User user, Long contestId) { - return user.getTeamMembers().stream().anyMatch(teamMember -> teamMember.getTeam().getContest().getId().equals(contestId)); + return user.getTeamMembers().stream() + .filter(teamMember -> teamMember.getRoom() != null && teamMember.getRoom().getContest() != null && teamMember.getRoom().getStatus()) + .anyMatch(teamMember -> teamMember.getRoom().getContest().getId().equals(contestId)); + } + + @Override + public List aiRecommendUsers(Long contestId, String userId) { + // AI가 추천한 유저 리스트 불러오기 + User user = userRepository.findUserByUserId(userId); + String filename = contestId + "_" + user.getId() + ".json"; + List recommendedUserIds = loadRecommendedUserIds(filename); + + // 추천된 유저들의 프로필 조회 및 반환 + List recommendedProfiles = new ArrayList<>(); + for (Long recommendedUserId : recommendedUserIds) { + // 해당 공모전에 팀이 없는지 확인 + User recommendedUser = userRepository.findById(recommendedUserId) + .orElseThrow(() -> new EntityNotFoundException("유저를 찾을 수 없습니다. userId=" + recommendedUserId)); + if (!hasTeamForContest(recommendedUser, contestId)) { + // 팀이 없는 경우 프로필 조회 및 결과에 추가 + Participation participation = participationRepository.findParticipationByContestIdAndUserId(contestId, recommendedUserId); + if (participation != null) { + ProfileParticipationRes profileResponse = mapToProfileParticipation(recommendedUser.getProfile(), participation); + if (profileResponse != null) { + recommendedProfiles.add(profileResponse); + } + } + } + } + return recommendedProfiles; } + @Override - public List aiRecommendUsers(String userId, Long contestId) { - //******ai 추천 유저 리스트 필요*************8 - return null; + public void aiStart(Long contestId, String userId) { + User user = userRepository.findUserByUserId(userId); + // 파이썬 스크립트 실행 + String pythonScriptPath = "C:/IntelliJ/Back-end/src/main/java/com/example/capd/python/ai.py"; + System.out.println("파이썬 스크립트 실행."); + + try { + // 외부 프로세스로 파이썬 스크립트 실행 + ProcessBuilder processBuilder = new ProcessBuilder("python", pythonScriptPath, + String.valueOf(contestId), + String.valueOf(user.getId())); + processBuilder.redirectErrorStream(true); // 에러 스트림을 표준 출력으로 리다이렉션 + Process process = processBuilder.start(); + + // 파이썬 스크립트 실행이 완료될 때까지 대기 + int exitCode = process.waitFor(); + System.out.println("파이썬 스크립트 실행이 완료되었습니다. 종료 코드: " + exitCode); + + // 파이썬 스크립트의 실행 결과 읽기 + InputStream inputStream = process.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + + String line; + while ((line = reader.readLine()) != null) { + // 실행 결과 처리 + System.out.println(line); + } + } catch (IOException | InterruptedException e) { + System.out.println("파이썬 스크립트 실행 중 오류가 발생했습니다."); + e.printStackTrace(); + } } @Override @@ -108,13 +160,16 @@ public void editProfile(ProfileRequestDto profileRequestDto) { .orElseThrow(() -> new EntityNotFoundException("프로필이 존재하지 않습니다: " + userId)); User user = userRepository.findByUserId(profileRequestDto.getUserId()).get(); - + profileRepository.deleteById(profile.getId()); Profile updatedProfile = Profile.builder() .id(profile.getId()) .intro(profileRequestDto.getIntro()) .stackList(profileRequestDto.getStackList()) .rate(profile.getRate()) //기존의 프로필 별점을 그대로 적용 (안하면 0.0됨) + .myTime(profileRequestDto.getMyTime()) + .desiredTime(profileRequestDto.getDesiredTime()) + .collaborationCount(profileRequestDto.getCollaborationCount()) .user(user) .careers(profileRequestDto.getCareers().stream() .map(careerParam -> { @@ -145,8 +200,7 @@ public void editProfile(ProfileRequestDto profileRequestDto) { @Override public void deleteProfile(String userId) { - User user = userRepository.findByUserId(userId) - .orElseThrow(() -> new EntityNotFoundException("유저를 찾을 수 없습니다. userId=" + userId)); + User user = userRepository.findUserByUserId(userId); Profile profile = user.getProfile(); if (profile == null) { @@ -161,10 +215,14 @@ private ProfileResponseDto mapToDTO(Profile profile) { ProfileResponseDto dto = new ProfileResponseDto(); dto.setId(profile.getId()); dto.setUserId(profile.getUser().getUserId()); + dto.setUserName(profile.getUser().getUsername()); dto.setIntro(profile.getIntro()); - dto.setRate(profile.getRate()); + dto.setRate(rateAverage(profile.getUser().getUserId())); dto.setStackList(profile.getStackList()); dto.setCareers(profile.getCareers().stream().map(this::mapCareerToDto).collect(Collectors.toList())); + dto.setMyTime(profile.getMyTime()); + dto.setDesiredTime(profile.getDesiredTime()); + dto.setCollaborationCount(profile.getCollaborationCount()); return dto; } @@ -182,12 +240,54 @@ private ProfileParticipationRes mapToProfileParticipation(Profile profile, Parti ProfileParticipationRes dto = new ProfileParticipationRes(); dto.setId(profile.getUser().getId()); dto.setUserId(profile.getUser().getUserId()); + dto.setUserName(profile.getUser().getUsername()); dto.setIntro(profile.getIntro()); - dto.setRate(profile.getRate()); + dto.setRate(rateAverage(profile.getUser().getUserId())); dto.setStackList(profile.getStackList()); dto.setAdditional(participation.getAdditional()); - dto.setTime(participation.getTime()); + dto.setTime(profile.getMyTime()); dto.setCareers(profile.getCareers().stream().map(this::mapCareerToDto).collect(Collectors.toList())); return dto; } + + // JSON 파일에서 추천된 유저 ID 리스트 불러오기 + private List loadRecommendedUserIds(String filename) { + try (BufferedReader reader = new BufferedReader(new FileReader(filename))) { + StringBuilder jsonData = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + jsonData.append(line); + } + // JSON 데이터 파싱 + JSONParser parser = new JSONParser(); + JSONArray jsonArray = (JSONArray) parser.parse(jsonData.toString()); + // 추천된 유저 ID 리스트 반환 + List recommendedUserIds = new ArrayList<>(); + for (Object obj : jsonArray) { + JSONObject jsonObject = (JSONObject) obj; + Long userId = (Long) jsonObject.get("user_id"); + recommendedUserIds.add(userId); + } + return recommendedUserIds; + } catch (IOException | ParseException e) { + e.printStackTrace(); + return Collections.emptyList(); + } + } + + public double rateAverage(String userId) { + User user = userRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException("id가 존재하지 않습니다:: " + userId)); + + List receivedReviews = user.getReceivedReviews(); + if (!receivedReviews.isEmpty()) { + double totalRating = receivedReviews.stream().mapToDouble(Review::getRate).sum(); + double averageRating = totalRating / receivedReviews.size(); + + return Math.round(averageRating * 10.0) / 10.0; + } else { + return 3.5; + } + } + } diff --git a/src/main/java/com/example/capd/User/service/ReviewServiceImpl.java b/src/main/java/com/example/capd/User/service/ReviewServiceImpl.java index 9de7fa6..0289eeb 100644 --- a/src/main/java/com/example/capd/User/service/ReviewServiceImpl.java +++ b/src/main/java/com/example/capd/User/service/ReviewServiceImpl.java @@ -1,16 +1,20 @@ package com.example.capd.User.service; -import com.example.capd.Exception.TeamNotConfirmedException; -import com.example.capd.team.domain.Team; +import com.example.capd.Exception.ReviewSubmissionPeriodNotEndedException; +import com.example.capd.contest.domain.Contest; +import com.example.capd.contest.repository.ContestRepository; import com.example.capd.User.domain.*; import com.example.capd.User.dto.ReviewRequestDto; import com.example.capd.User.repository.ReviewRepository; -import com.example.capd.team.repository.TeamRepository; import com.example.capd.User.repository.UserRepository; +import com.example.capd.team.domain.Room; +import com.example.capd.team.repository.RoomRepository; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.stream.Collectors; @@ -20,12 +24,13 @@ public class ReviewServiceImpl implements ReviewService{ private final UserRepository userRepository; private final ReviewRepository reviewRepository; - private final TeamRepository teamRepository; + private final RoomRepository roomRepository; + private final ContestRepository contestRepository; @Override public void saveReview(ReviewRequestDto reviewRequestDto) { - Team team = teamRepository.findById(reviewRequestDto.getTeamId()) - .orElseThrow(() -> new EntityNotFoundException("팀이 존재하지 않습니다: " + reviewRequestDto.getTeamId())); + Room room = roomRepository.findById(reviewRequestDto.getRoomId()) + .orElseThrow(() -> new EntityNotFoundException("팀이 존재하지 않습니다: " + reviewRequestDto.getRoomId())); User reviewer = userRepository.findByUserId(reviewRequestDto.getReviewerId()) .orElseThrow(() -> new EntityNotFoundException("작성자 id가 존재하지 않습니다: " + reviewRequestDto.getReviewerId())); @@ -33,13 +38,46 @@ public void saveReview(ReviewRequestDto reviewRequestDto) { User reviewedUser = userRepository.findByUserId(reviewRequestDto.getReviewedUserId()) .orElseThrow(() -> new EntityNotFoundException("리뷰 받는 사람 id가 존재하지 않습니다: " + reviewRequestDto.getReviewedUserId())); - //팀 현황이 확정일 경우에만 리뷰 작성 가능 - if (Boolean.TRUE.equals(team.getStatus())) { - Review review = reviewRequestDto.toEntity(reviewer, reviewedUser, team); + Contest contest = contestRepository.findById(reviewRequestDto.getContestId()) + .orElseThrow(() -> new EntityNotFoundException(" 존재하지 않는 공모전 id: " + reviewRequestDto.getReviewedUserId())); + + //팀 확정 상태인 경우만 리뷰 작성 + if(room.getStatus()==true){ + Review review = reviewRequestDto.toEntity(reviewer, reviewedUser, room); reviewRepository.save(review); - } else { - throw new TeamNotConfirmedException(); + }else{ + throw new NullPointerException("팀이 확정되지 않아 리뷰를 작성할 수 없습니다."); } + +// //심사 기간에 ~ 없는 경우도 접수 기간으로 +// if(contest.getDecisionPeriod() != null || !contest.getDecisionPeriod().contains("~")) {//심사기간 +// String[] decisionPeriod = contest.getDecisionPeriod().split("~"); +// String endDateString = decisionPeriod[1].trim(); +// +// LocalDate endDate = LocalDate.parse(endDateString, DateTimeFormatter.ofPattern("yyyy.MM.dd")); +// LocalDate currentDate = LocalDate.now(); +// +// //심사 기간 마감일 경우에만 리뷰 작성 +// if (currentDate.isAfter(endDate)) { +// Review review = reviewRequestDto.toEntity(reviewer, reviewedUser, room); +// reviewRepository.save(review); +// } else { +// throw new ReviewSubmissionPeriodNotEndedException(); +// } +// } else{//접수 기간 +// String[] receptionPeriod = contest.getReceptionPeriod().split("~"); +// String endDateString = receptionPeriod[1].trim(); +// LocalDate endDate = LocalDate.parse(endDateString, DateTimeFormatter.ofPattern("yyyy.MM.dd")); +// LocalDate currentDate = LocalDate.now(); +// +// if(currentDate.isAfter(endDate)) { +// Review review = reviewRequestDto.toEntity(reviewer, reviewedUser, room); +// reviewRepository.save(review); +// }else{ +// throw new ReviewSubmissionPeriodNotEndedException(0); +// } +// } + } @Override @@ -75,7 +113,7 @@ private ReviewRequestDto convertToReviewDTO(Review review) { reviewRequestDto.setRate(review.getRate()); reviewRequestDto.setReviewerId(review.getReviewer().getUserId()); reviewRequestDto.setReviewedUserId(review.getReviewedUser().getUserId()); - reviewRequestDto.setTeamId(review.getTeam().getId()); + reviewRequestDto.setRoomId(review.getRoom().getId()); return reviewRequestDto; } diff --git a/src/main/java/com/example/capd/User/service/UserService.java b/src/main/java/com/example/capd/User/service/UserService.java new file mode 100644 index 0000000..1e26c4b --- /dev/null +++ b/src/main/java/com/example/capd/User/service/UserService.java @@ -0,0 +1,92 @@ +package com.example.capd.User.service; + +import com.example.capd.User.dto.SignRequest; +import com.example.capd.User.dto.SignResponse; + +import com.example.capd.User.domain.User; +import com.example.capd.User.dto.UserDTO; +import com.example.capd.User.dto.UserUpdateRequest; +import com.example.capd.User.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + //private final TokenProvider tokenProvider; + + //회원가입 + public Long save(UserDTO dto) { + if (userRepository.findOneWithAuthoritiesByUsername(dto.getUserId()).orElse(null) != null) { + throw new RuntimeException("이미 가입되어 있는 유저입니다."); + } + + // 유저 정보를 만들어서 save + User user = User.builder() + .userId(dto.getUserId()) + .username(dto.getUsername()) + .password(passwordEncoder.encode(dto.getPassword())) + .Email(dto.getEmail()) + .gender(dto.getGender()) + .address(dto.getAddress()) + .Tendency(dto.getTendency()) + .Phone(dto.getPhone()) + .build(); + + return userRepository.save(user).getId(); + } + + //정보수정 + public void updateUserInformation(UserUpdateRequest request) { + User user = userRepository.findUserByUserId(request.getUserId()); + if (user != null) { + user.setEmail(request.getNewEmail()); + user.setPassword(passwordEncoder.encode(request.getNewPassword())); + user.setUsername(request.getNewUsername()); + user.setGender(request.getNewGender()); + user.setAddress(request.getNewAddress()); + user.setTendency(request.getNewTendency()); + user.setPhone(request.getNewPhone()); + userRepository.save(user); + } else { + throw new UsernameNotFoundException("User not found with userId: " + request.getUserId()); + } + } + + public SignResponse getUser(String userId) throws Exception { + User user = userRepository.findByUserId(userId) + .orElseThrow(() -> new Exception("계정을 찾을 수 없습니다.")); + return new SignResponse(user); + } + + //회원 탈퇴 + public void deleteUser(String userId) { + User user = userRepository.findUserByUserId(userId); + if (user != null) { + userRepository.delete(user); + } else { + throw new UsernameNotFoundException("유저를 찾을 수 없습니다: " + userId); + } + } + +// public HashMap useridOverlap(Long id) { +// HashMap map = new HashMap<>(); +// map.put("result", userRepository.existsById(id)); +// return map; +// } + + // 유저,권한 정보를 가져오는 메소드 + @Transactional(readOnly = true) + public Optional getUserWithAuthorities(String username) { + return userRepository.findOneWithAuthoritiesByUsername(username); + } +} diff --git a/src/main/java/com/example/capd/contest/controller/ContestInfoController.java b/src/main/java/com/example/capd/contest/controller/ContestInfoController.java index 2ed7d90..1b10395 100644 --- a/src/main/java/com/example/capd/contest/controller/ContestInfoController.java +++ b/src/main/java/com/example/capd/contest/controller/ContestInfoController.java @@ -3,20 +3,22 @@ import com.example.capd.contest.domain.Contest; import com.example.capd.contest.service.ContestInfoService; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.ui.Model; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.Map; @RestController @RequiredArgsConstructor +@CrossOrigin(origins = "http://localhost:3000/", allowedHeaders = "*") public class ContestInfoController { private final ContestInfoService contestInfoService; - @GetMapping("/showData") + @GetMapping("/showData") // 백엔드 실행시 프론트 만들수있음 public String showData(Model model) { List> data = contestInfoService.fetchDataFromDatabase(); model.addAttribute("data", data); @@ -29,8 +31,38 @@ public List contestData() { } //공모전 id 리스트 - @GetMapping("/contest-list") - public List contestIdList(){ - return contestInfoService.getContestId(); + @GetMapping("/contest-list") // 콘테스트의 ID를 보내줌? + public ResponseEntity> contestIdList(){ + return ResponseEntity.ok(contestInfoService.getContestId()); + } + + @GetMapping("/contestdetail/{id}") + public ResponseEntity getContestDetail(@PathVariable Long id) { + Contest contest = contestInfoService.findContestDetailById(id); + if(contest == null) { + return new ResponseEntity<>(HttpStatus.NOT_FOUND); + } + else { + return new ResponseEntity<>(contest, HttpStatus.OK); + } + } + + @PostMapping("/viewPlus") + public ResponseEntity increaseViewCount(@RequestBody Map viewsIncreaseRequest) { + Long contestId = viewsIncreaseRequest.get("id"); + + Contest contest = contestInfoService.findContestDetailById(contestId); + if (contest == null) { + return new ResponseEntity<>("Contest not found", HttpStatus.NOT_FOUND); + } else { + Long currentViews = contest.getViews(); + if (currentViews == null) { + currentViews = 0L; // currentViews가 null이면 0으로 초기화 + } + Long updatedViews = currentViews + 1; + contest.setViews(updatedViews); + contestInfoService.saveContest(contest); + return new ResponseEntity<>("View count increased successfully", HttpStatus.OK); + } } } \ No newline at end of file diff --git a/src/main/java/com/example/capd/contest/domain/Contest.java b/src/main/java/com/example/capd/contest/domain/Contest.java index 0699688..bf9ea2c 100644 --- a/src/main/java/com/example/capd/contest/domain/Contest.java +++ b/src/main/java/com/example/capd/contest/domain/Contest.java @@ -1,7 +1,8 @@ package com.example.capd.contest.domain; import com.example.capd.User.domain.Participation; -import com.example.capd.team.domain.Team; +import com.example.capd.team.domain.Room; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.*; @@ -9,10 +10,10 @@ import java.util.List; -@Entity(name = "contest") +@Entity @Getter @Setter -@NoArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor(access = AccessLevel.PUBLIC) @Builder @AllArgsConstructor public class Contest { @@ -52,14 +53,19 @@ public class Contest { @Column(length = 400) private String image; - @Column(length = 2000) + @Column(length = 10000) private String detailText; + @Column + private Long views; //참여할게요 매핑 @OneToMany(mappedBy = "contest") + @JsonIgnore private List participations = new ArrayList<>(); // 팀 매핑 @OneToMany(mappedBy = "contest") - private List teams = new ArrayList<>(); + @JsonIgnore + private List rooms = new ArrayList<>(); + } \ No newline at end of file diff --git a/src/main/java/com/example/capd/contest/repository/ContestRepository.java b/src/main/java/com/example/capd/contest/repository/ContestRepository.java index 791efc6..5e89359 100644 --- a/src/main/java/com/example/capd/contest/repository/ContestRepository.java +++ b/src/main/java/com/example/capd/contest/repository/ContestRepository.java @@ -2,6 +2,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import com.example.capd.contest.domain.Contest; import java.util.List; @@ -9,7 +10,7 @@ @Repository public interface ContestRepository extends JpaRepository { - Contest findByTeams_Id(Long teamId); + Contest findByRooms_Id(Long roomId); @Query(value = "select * from Contest", nativeQuery = true) List> fetchDataFromDatabase(); @@ -18,4 +19,7 @@ public interface ContestRepository extends JpaRepository { @Query(value = "SELECT c.id FROM Contest c", nativeQuery = true) List findAllIds(); -} + + @Query(value = "SELECT c FROM Contest c WHERE c.id = :id") + Contest findByIdWithoutParticipations(@Param("id") Long id); +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/contest/service/ContestInfoService.java b/src/main/java/com/example/capd/contest/service/ContestInfoService.java index 8fddf6a..1780319 100644 --- a/src/main/java/com/example/capd/contest/service/ContestInfoService.java +++ b/src/main/java/com/example/capd/contest/service/ContestInfoService.java @@ -26,4 +26,14 @@ public List getAllContests() { public List getContestId(){ return contestRepository.findAllIds(); } + + // 공모전 id로 해당 공모전 정보 반환 + public Contest findContestDetailById(Long id) { + return contestRepository.findByIdWithoutParticipations(id); + } + + // 업데이트된 공모전 정보 저장 (예: 조회수증가) + public Contest saveContest(Contest contest) { + return contestRepository.save(contest); + } } \ No newline at end of file diff --git a/src/main/java/com/example/capd/contest/service/CrawlingScriptService.java b/src/main/java/com/example/capd/contest/service/CrawlingScriptService.java index 3fa1506..afe6d05 100644 --- a/src/main/java/com/example/capd/contest/service/CrawlingScriptService.java +++ b/src/main/java/com/example/capd/contest/service/CrawlingScriptService.java @@ -20,7 +20,7 @@ public class CrawlingScriptService { public void executeCrawlingScript() { try { // 파이썬 스크립트 경로 - String pythonScriptPath = "C:\\IntelliJ\\capD\\src\\main\\java\\com\\example\\capd\\python\\Crawling_START.py"; + String pythonScriptPath = "C:/IntelliJ/Back-end/src/main/java/com/example/capd/python/Crawling_START.py"; System.out.println("파이썬 실행"); // 외부 프로세스로 파이썬 스크립트 실행 diff --git a/src/main/java/com/example/capd/data/DataController.java b/src/main/java/com/example/capd/data/DataController.java new file mode 100644 index 0000000..5c9c241 --- /dev/null +++ b/src/main/java/com/example/capd/data/DataController.java @@ -0,0 +1,179 @@ +package com.example.capd.data; + +import com.example.capd.User.dto.*; +import com.example.capd.User.service.ParticipationService; +import com.example.capd.User.service.ProfileService; +import com.example.capd.User.service.ReviewService; +import com.example.capd.User.service.UserService; +import com.example.capd.team.domain.Room; +import com.example.capd.team.domain.TeamMember; +import com.example.capd.team.dto.ChatRoomDto; +import com.example.capd.team.service.RoomService; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.type.TypeReference; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.xssf.usermodel.XSSFRow; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +@Controller +@RequiredArgsConstructor +public class DataController { + + private final ObjectMapper objectMapper; + private final ProfileService profileService; + private final ParticipationService participationService; + private final UserService userService; + private final RoomService roomService; + private final ReviewService reviewService; + + @GetMapping("/data") + public String main() { + + return "main"; //메인화면 + } + + @GetMapping("/success") + public String sucess() { + return "success"; //성공화면 + } + + @PostMapping("/addUserExcel") + public String readUserExcel(@RequestParam("file") MultipartFile file) throws IOException { + XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream()); + XSSFSheet worksheet = workbook.getSheetAt(0); + + for (int i = 1; i < worksheet.getPhysicalNumberOfRows(); i++) { + DataFormatter formatter = new DataFormatter(); + XSSFRow row = worksheet.getRow(i); + + UserDTO user = new UserDTO(); + user.setUserId(formatter.formatCellValue(row.getCell(0))); + user.setUsername(formatter.formatCellValue(row.getCell(1))); + user.setPassword(formatter.formatCellValue(row.getCell(2))); + user.setAddress(formatter.formatCellValue(row.getCell(3))); + user.setEmail(formatter.formatCellValue(row.getCell(4))); + user.setPhone(formatter.formatCellValue(row.getCell(5))); + user.setGender(formatter.formatCellValue(row.getCell(6)).charAt(0)); + user.setTendency(formatter.formatCellValue(row.getCell(7))); + userService.save(user); + } + workbook.close(); + return "redirect:/success"; + } + + @PostMapping("/addProfileExcel") + public String readProfileExcel(@RequestParam("file") MultipartFile file) throws IOException { + XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream()); + XSSFSheet worksheet = workbook.getSheetAt(0); + + for (int i = 1; i < worksheet.getPhysicalNumberOfRows(); i++) { + DataFormatter formatter = new DataFormatter(); + XSSFRow row = worksheet.getRow(i); + + List careers = objectMapper.readValue(formatter.formatCellValue(row.getCell(6)), new TypeReference>() {}); + List stackList = objectMapper.readValue(formatter.formatCellValue(row.getCell(5)), new TypeReference>() {}); + int myTime = Integer.parseInt(formatter.formatCellValue(row.getCell(4))); + int desiredTime = Integer.parseInt(formatter.formatCellValue(row.getCell(3))); + int collaborationCount = Integer.parseInt(formatter.formatCellValue(row.getCell(2))); + + ProfileRequestDto profile = ProfileRequestDto.builder() + .userId(formatter.formatCellValue(row.getCell(0))) + .intro(formatter.formatCellValue(row.getCell(1))) + .collaborationCount(collaborationCount) + .desiredTime(desiredTime) + .myTime(myTime) + .stackList(stackList) + .build(); + profile.setCareers(careers); + profileService.saveProfile(profile); + } + workbook.close(); + return "redirect:/success"; + } + + @PostMapping("/addParticipationExcel") + public String readParticipationExcel(@RequestParam("file") MultipartFile file) throws IOException { + XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream()); + XSSFSheet worksheet = workbook.getSheetAt(0); + + for (int i = 1; i < worksheet.getPhysicalNumberOfRows(); i++) { + DataFormatter formatter = new DataFormatter(); + XSSFRow row = worksheet.getRow(i); + + List stackList = objectMapper.readValue(formatter.formatCellValue(row.getCell(3)), new TypeReference>() {}); + + ParticipationParam participation = ParticipationParam.builder() + .contestId(Long.parseLong(formatter.formatCellValue(row.getCell(0)))) + .userId(formatter.formatCellValue(row.getCell(1))) + .additional(formatter.formatCellValue(row.getCell(2))) + .stackList(stackList) + .build(); + + participationService.saveParticipation(participation); + } + workbook.close(); + return "redirect:/success"; + } + + @PostMapping("/addRoomExcel") + public String readRoomeExcel(@RequestParam("file") MultipartFile file) throws IOException { + XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream()); + XSSFSheet worksheet = workbook.getSheetAt(0); + + for (int i = 1; i < worksheet.getPhysicalNumberOfRows(); i++) { + DataFormatter formatter = new DataFormatter(); + XSSFRow row = worksheet.getRow(i); + + List member_list = objectMapper.readValue(formatter.formatCellValue(row.getCell(3)), new TypeReference>() {}); + + ChatRoomDto room = ChatRoomDto.builder() + .name(formatter.formatCellValue(row.getCell(0))) + .leaderId(formatter.formatCellValue(row.getCell(1))) + .contestId(Long.parseLong(formatter.formatCellValue(row.getCell(2)))) + .memberIds(member_list) + .build(); + roomService.createRoom(room); + room.setStatus(true); + } + workbook.close(); + return "redirect:/success"; + } + + @PostMapping("/addReviewExcel") + public String readReviewExcel(@RequestParam("file") MultipartFile file) throws IOException { + XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream()); + XSSFSheet worksheet = workbook.getSheetAt(0); + + for (int i = 1; i < worksheet.getPhysicalNumberOfRows(); i++) { + DataFormatter formatter = new DataFormatter(); + XSSFRow row = worksheet.getRow(i); + + ReviewRequestDto review = ReviewRequestDto.builder() + .rate(Double.parseDouble(formatter.formatCellValue(row.getCell(0)))) + .reviewedUserId(formatter.formatCellValue(row.getCell(1))) + .reviewerId(formatter.formatCellValue(row.getCell(2))) + .content(formatter.formatCellValue(row.getCell(3))) + .roomId(Long.parseLong(formatter.formatCellValue(row.getCell(4)))) + .contestId(Long.parseLong(formatter.formatCellValue(row.getCell(5)))) + .build(); + reviewService.saveReview(review); + } + workbook.close(); + return "redirect:/success"; + } + +} diff --git a/src/main/java/com/example/capd/python/Crawling_START.py b/src/main/java/com/example/capd/python/Crawling_START.py index 24ebb54..b3e8698 100644 --- a/src/main/java/com/example/capd/python/Crawling_START.py +++ b/src/main/java/com/example/capd/python/Crawling_START.py @@ -2,8 +2,8 @@ # 실행할 파이썬 파일 경로들 file_paths = [ - 'C:\IntelliJ\capD\src\main\java\com\example\capd\python\contestkorea_crawling.py', - 'C:\IntelliJ\capD\src\main\java\com\example\capd\python\wevity_crawling.py' + 'C:/IntelliJ/Back-end/src/main/java/com/example/capd/python/contestkorea_crawling.py', + 'C:/IntelliJ/Back-end/src/main/java/com/example/capd/python/wevity_crawling.py' ] # 각 파일을 순회하며 실행 diff --git a/src/main/java/com/example/capd/python/ai.py b/src/main/java/com/example/capd/python/ai.py new file mode 100644 index 0000000..53c15b9 --- /dev/null +++ b/src/main/java/com/example/capd/python/ai.py @@ -0,0 +1,283 @@ +import pymysql +import pandas as pd +import numpy as np +from sklearn.feature_extraction.text import CountVectorizer +from sklearn.metrics.pairwise import cosine_similarity +import sys +import json +import matplotlib.pyplot as plt + +connection = pymysql.connect(host='localhost', + user='root', + password='db12', + database='capd', + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) +contest_id = sys.argv[1] +user_id = sys.argv[2] + +try: + with connection.cursor() as cursor: + # profile 테이블에서 데이터 가져오기 + profile_sql = f"SELECT user_id, collaboration_count, my_time, desired_time, rate FROM profile WHERE user_id = '{user_id}'" + cursor.execute(profile_sql) + profile_row = cursor.fetchone() # 해당 유저의 정보를 가져옴 + + + # 다른 유저들의 프로필 정보 저장할 리스트 초기화 + other_users_profiles = [] + + if profile_row: + # 참여 데이터 가져오기 + participation_sql = f""" + SELECT psl.stack_list + FROM participation_stack_list psl + JOIN participation p ON psl.participation_id = p.id + JOIN contest c ON p.contest_id = c.id + WHERE p.user_id = '{user_id}' + AND c.id = '{contest_id}'; + """ + cursor.execute(participation_sql) + participation_rows = cursor.fetchall() + + # 스택 리스트 조합 + stack_lists = [participation['stack_list'] for participation in participation_rows] + combined_stack_list = ', '.join(stack_lists) #원하는 스택 정보 + + # 프로필 정보 저장 (id, 원하는 스택, 원하는 경력 횟수, 원하는 투자시간, 내 평점) + profile_info = { + 'id': profile_row['user_id'], + 'stack_list': combined_stack_list, + 'collaboration_count': profile_row['collaboration_count'], + 'desired_time': profile_row['desired_time'], + 'rate': profile_row['rate'] + } + print("Current User Profile:") + print(profile_info) + + # 다른 유저들의 프로필 정보 가져오기 (id, 본인 스택, 경력횟수, 투자가능시간 ,별점) + other_users_profile_sql = f""" + SELECT p.user_id, GROUP_CONCAT(DISTINCT psl.stack_list) AS stack_list, COUNT(DISTINCT c.id) AS collaboration_count, p.my_time, p.rate + FROM profile p + JOIN profile_stack_list psl ON p.id = psl.profile_id + LEFT JOIN career c ON p.id = c.profile_id + JOIN participation par ON par.user_id = p.user_id + JOIN contest cont ON par.contest_id = cont.id + WHERE cont.id = '{contest_id}' AND p.user_id != '{user_id}' + GROUP BY p.id, p.my_time, p.rate; + """ + cursor.execute(other_users_profile_sql) + other_users_profile_rows = cursor.fetchall() + + for row in other_users_profile_rows: + other_user_profile_info = { + 'id': row['user_id'], + 'stack_list': row['stack_list'], + 'collaboration_count': row['collaboration_count'], + 'my_time': row['my_time'], + 'rate': row['rate'] + } + other_users_profiles.append(other_user_profile_info) + + # 결과 출력 + print("\nOther Users Profiles:") + df = pd.DataFrame(other_users_profiles) + print(df) + + else: + print(f"No profile found for user with id {user_id}") +finally: + # 연결 닫기 + connection.close() + +# 1. 내가 원하는 상대의 기술스택, 내가 원하는 상대의 협업프로젝트 경험 횟수, 내가 원하는 투자가능시간, 내 평점 +# 리스트로 저장 +# 내가 원하는 상대의 기술스택 +my_want_skill = combined_stack_list +want_exp_count = profile_row['collaboration_count'] +my_investable_time = profile_row['desired_time'] +my_raiting = profile_row['rate'] + +# 2. userid, 상대의 기술스택, 상대 협업프로젝트 경험 횟수, 상대가 실제 투자가능한 시간, 상대의 평점 +# (상대방들은 여러명 -> 리스트 내에 튜플로 저장) + +other_people = [] + +for row in other_users_profile_rows: + other_person = { + 'id': row['user_id'], + 'stack_list': row['stack_list'], + 'collaboration_count': row['collaboration_count'], + 'my_time': row['my_time'], + 'rate': row['rate'] + } + other_people.append(other_person) + +# other_people을 원하는 형식으로 변경 +other_people_formatted = [ + ( + person['id'], + person['stack_list'], + person['collaboration_count'], + person['my_time'], + person['rate'] + ) + for person in other_people +] +cnt_vect = CountVectorizer(token_pattern=r'[^,\s]+') # CountVectorizer객체. 나와 상대들의 기술스택을 각각 벡터화하는 데 이용. + +# 나와 다른 사람의 기술스택을 벡터화한 행렬 저장. +skills_matrix = cnt_vect.fit_transform([my_want_skill] + [other_person[1] for other_person in other_people_formatted]) +# print(skills_matrix) # CSR 매트릭스(0 제거 희소행렬)로 반환되어 단어 최초단어등장 위치를 기준으로 벡터화된 데이터임. +# print(type(skills_matrix)) +print('단어장(벡터화된 토큰의 단어 정보. 실제로는 벡터값이 매핑되어 있습니다.)',cnt_vect.vocabulary_) + +# 코사인 유사도 계산하기 +cosine_similarities = cosine_similarity(skills_matrix) +print('여기까지의 유사도 (기본 코사인 유사도) : \n', cosine_similarities[0],end='\n-------------------------------\n') +# 우리가 생각하는 코사인 유사도는 반환결과의 첫 번째 행(cosine_similarities[0])입니다. 첫 번째 요소(cosine_similarities[0][0])는 내 스택 vs 내 스택이고 그 다음 요소부터(cosine_similarities[0][1]) 내 스택 vs 상대1, (cosine_similarities[0][2])내 스택 vs 상대 2 ... 이런식이에요 + +# 상대방 별 가중치 계산하기 +weighted_similarities = [] +for i in range(1, len(other_people_formatted)+1) : # 0번째는 내가 원하는 스택과 내가 원하는 스택의 유사도임.. + similarity_score = cosine_similarities[0][i] + print('나 vs {0}번째 상대방 기본 유사도'.format(i), similarity_score) + + exp_diff = abs(want_exp_count - other_people_formatted[i-1][2]) # 수정된 부분 + # 투자가능한 시간의 차 + inv_time_diff = abs(my_investable_time - other_people_formatted[i-1][3]) # 수정된 부분 + # 평점 차 + raiting_diff = abs(my_raiting - other_people_formatted[i-1][4]) + + + # 협업프로젝트경험 가중치 계산 + if exp_diff == 0 : + exp_weight = 1.2 + elif exp_diff >= 10 : + exp_weight = 0.8 + else : + exp_weight = 1.2 - (exp_diff / 10) * 0.4 + + # 투자가능한 시간 가중치 계산 + if inv_time_diff >= 15 : + inv_time_weight = 0.9 + elif inv_time_diff == 0: + inv_time_weight = 1.1 + else : + inv_time_weight = 1.1 - (inv_time_diff / 15) * 0.2 + + # 평점 가중치 계산 + if raiting_diff == 0 : + raiting_weight = 1.05 + else : + raiting_weight = 1.05 - (raiting_diff / 5) * 0.1 + + # 총 가중치 계산 + total_weight = exp_weight * inv_time_weight * raiting_weight + + # 가중치 적용한 최종 유사도 + current_weighted_similarity = similarity_score * total_weight + print('나 vs {0}번째 상대방의 가중치 적용된 최종 유사도'.format(i), current_weighted_similarity) + # 유사도 저장 후 다음 반복(다음 사람과 비교) + weighted_similarities.append(current_weighted_similarity) + print('--------------------------------------') + +# 코사인 유사도 시각화 - x축이 유사도, y축이 비교대상 스택 문자열인 가로 바그래프 +# ----------------- 1. 각각 막대그래프 따로 그리기 (순위변화 보기 용이함) ------------------------ +# matplotlib의 barh() 가로막대그래프 +# X축 : 코사인 유사도 +# plt.figure(figsize=(12, 6)) +# plt.subplot(1, 2, 1) +# xData = cosine_similarities[0][1:] +# # # Y축 : 각 userId +# yData = [str(other_person[0]) for other_person in other_people_formatted] +# +# plt.barh(yData, xData, label='Cosine Similarity', color='r') +# +# plt.ylabel('userId') # y축 이름 +# plt.xlabel('Cosine Similarity') # x축 이름 +# plt.title('Cosine Similarity by userId Horizental bar graph\n Before applying weight') # 그래프 제목 +# plt.grid() +# # # 각 유사도 값 그래프 바 옆에 표시하기 +# for index, value in enumerate(xData) : +# if value != 0.0 : # ha속성은 바 끝으로부터 어느쪽으로 텍스트를 보여줄건지 결정 +# plt.text(value, index, str(value), ha='right') +# else : # 값이 0.0일때 왼쪽으로 표시하면 왼쪽에 userId가 겹쳐서 잘 안보임.. +# plt.text(value, index, str(value), ha='left') +# +# # # 가중치 적용 이후 코사인 유사도 가로 바 그래프 +# xData = weighted_similarities +# plt.subplot(1, 2, 2) # 1행 2열의 두 번째 그래프 +# plt.barh(yData, xData, color='skyblue', label='Weighted Cosine Similarity') +# plt.ylabel('user Id') +# plt.xlabel('Weighted Cosine Similarity') +# plt.title('Cosine Similarity by userId Horizental bar graph\nAfter applying weight') +# plt.grid() +# for index, value in enumerate(xData): +# if value != 0.0: +# plt.text(value, index, str(value), ha='right') +# else: +# plt.text(value, index, str(value), ha='left') +# plt.show() + +# # # ----------------- 2. 한번에 그리기 (가중치 적용 전후 얼마나 변화했는지 보기 용이함) ------------------------ +# xData = cosine_similarities[0][1:] # 적용 전 코사인 유사도 +# xData_weighted = weighted_similarities # 적용 후 코사인 유사도 +# yData = [str(other_person[0]) for other_person in other_people_formatted] # userId 혹은 다른 식별자 +# +# # # 그래프 그리기 +# plt.figure(figsize=(10, 6)) # 그래프 사이즈 설정 +# +# # # 가로 막대 그래프 그리기 (적용 전) +# plt.barh(np.arange(len(yData)), xData, color='skyblue', label='Before Weighted', height=0.4) +# +# # # 가로 막대 그래프 그리기 (적용 후) +# plt.barh(np.arange(len(yData)) + 0.4, xData_weighted, color='orange', label='After Weighted', height=0.4) +# +# # # 그래프에 텍스트 표시 +# for i, value in enumerate(xData): +# if value != 0.0: +# plt.text(value, i, str(round(value, 2)), ha='right', va='center', fontsize=10) # 적용 전 막대 오른쪽에 텍스트 표시 +# else: +# plt.text(value, i, str(round(value, 2)), ha='left', va='center', fontsize=10) # 값이 0.0일 때 왼쪽에 텍스트 표시 +# for i, value in enumerate(xData_weighted): +# if value != 0.0: +# plt.text(value, i + 0.4, str(round(value, 2)), ha='right', va='center', fontsize=10) # 적용 후 막대 오른쪽에 텍스트 표시 +# else: +# plt.text(value, i + 0.4, str(round(value, 2)), ha='left', va='center', fontsize=10) # 값이 0.0일 때 왼쪽에 텍스트 표시 +# +# # # 그래프 제목, 축 이름 설정 +# plt.title('Cosine Similarity Before and After Weighted') +# plt.xlabel('Cosine Similarity') +# plt.ylabel('User Id') +# plt.yticks(np.arange(len(yData)) + 0.2, yData) # y 축에 userId 표시 +# plt.grid(axis='x') # x 축에만 그리드 표시 +# plt.legend() # 범례 표시 +# plt.tight_layout() # 그래프 간격 조정 +# plt.show() + + + +# 상위 10명의 id 추출. 10명이 안될 경우 정렬만 수행 +top10_indices = [] +if len(weighted_similarities) > 10 : + top10_indices = np.argsort(weighted_similarities)[::-1][:10] +else : + top10_indices = np.argsort(weighted_similarities)[::-1][:len(weighted_similarities)] +# 상위n명의 정보 출력 + +json_list = [] + +for idx in top10_indices: + other_user_id = other_people_formatted[idx][0] + print("상대방 id:", other_user_id , ', 최종 유사도:', weighted_similarities[idx]) + if weighted_similarities[idx] != 0: + json_list.append({"user_id": other_user_id }) + +# JSON 파일명 생성 +json_filename = f"{contest_id}_{user_id}.json" +json_data = [{"rank": rank, "user_id": user_info["user_id"]} for rank, user_info in enumerate(json_list[:10], start=1)] + +# 상위 10명의 id를 JSON 파일로 저장 +with open(json_filename, 'w') as json_file: + json.dump(json_data, json_file) \ No newline at end of file diff --git a/src/main/java/com/example/capd/python/contestkorea_crawling.py b/src/main/java/com/example/capd/python/contestkorea_crawling.py index 44451b4..3530525 100644 --- a/src/main/java/com/example/capd/python/contestkorea_crawling.py +++ b/src/main/java/com/example/capd/python/contestkorea_crawling.py @@ -26,7 +26,17 @@ def extract_contest_info(browser): participation_fee = browser.find_element(By.CSS_SELECTOR, "#wrap > div.container.list_wrap > div.left_cont > div.view_cont_area > div.view_top_area.clfx > div.clfx > div.txt_area > table > tbody > tr:nth-child(10) > td").text else : participation_fee = browser.find_element(By.CSS_SELECTOR, "#wrap > div.container.list_wrap > div.left_cont > div.view_cont_area > div.view_top_area.clfx > div.clfx > div.txt_area > table > tbody > tr:nth-child(9) > td").text - detail_text = browser.find_element(By.CSS_SELECTOR, "#wrap > div.container.list_wrap > div.left_cont > div.view_cont_area > div.tab_cont > div > div").text + + detail_text_elements = browser.find_elements(By.CSS_SELECTOR, ".view_detail_area h2, .view_detail_area p") + detail_text = "" + for element in detail_text_elements: + if element.tag_name == "h2": + # h2 태그인 경우 '■' 추가 + detail_text += f"■ {element.text}\n" + else: + # p 태그인 경우 텍스트만 추가 + detail_text += f"{element.text}\n" + image = browser.find_element(By.CSS_SELECTOR, "#wrap > div.container.list_wrap > div.left_cont > div.view_cont_area > div.view_top_area.clfx > div.clfx > div.img_area > div > img") image_link = image.get_attribute('src') @@ -50,6 +60,7 @@ def extract_contest_info(browser): chrome_options = Options() chrome_options.add_experimental_option("detach", True) chrome_options.add_experimental_option("excludeSwitches", ["enable-logging"]) +chrome_options.add_argument("headless") browser = webdriver.Chrome(options=chrome_options) # 웹 페이지 열기 diff --git a/src/main/java/com/example/capd/python/wevity_crawling.py b/src/main/java/com/example/capd/python/wevity_crawling.py index a973dce..83e5576 100644 --- a/src/main/java/com/example/capd/python/wevity_crawling.py +++ b/src/main/java/com/example/capd/python/wevity_crawling.py @@ -48,7 +48,7 @@ def extract_contest_info(browser) : # 불필요한 에러 메시지 없애기 chrome_options.add_experimental_option("excludeSwitches", ["enable-logging"]) - +chrome_options.add_argument("headless") # 브라우저 생성, 웹 사이트 열기 browser = webdriver.Chrome(options=chrome_options) diff --git a/src/main/java/com/example/capd/socket/config/MyHandler.java b/src/main/java/com/example/capd/socket/config/MyHandler.java index 17ab4a1..39d8d8f 100644 --- a/src/main/java/com/example/capd/socket/config/MyHandler.java +++ b/src/main/java/com/example/capd/socket/config/MyHandler.java @@ -3,26 +3,30 @@ import com.example.capd.socket.dto.CheeringMessageDto; import com.example.capd.socket.dto.MessageDto; import com.example.capd.socket.service.ChatService; +import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; + import java.io.IOException; + import com.fasterxml.jackson.databind.ObjectMapper; + import java.util.*; import java.util.stream.Collectors; + import org.json.simple.JSONArray; import org.json.simple.JSONObject; public class MyHandler extends TextWebSocketHandler { + private static final Logger logger = LoggerFactory.getLogger(MyHandler.class); private final Map sessions = new HashMap<>(); private final Map> roomSubscribers = new HashMap<>(); private final Deque cheeringMessagesQueue = new ArrayDeque<>(); //응원글 저장할 - private static final Logger logger = LoggerFactory.getLogger(MyHandler.class); - private final ChatService chatService; public MyHandler(ChatService chatService) { @@ -77,6 +81,7 @@ private void subscribeToRoom(WebSocketSession session, Long roomId) { @Override public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload = message.getPayload(); + logger.info("Received cheer message payload: {}", payload); // payload 출력 //구독 메시지 확인 if (payload.startsWith("/sub/chat/")) { @@ -91,16 +96,12 @@ else if (payload.startsWith("/pub/chat/")) { // 발행 메시지 처리 handlePublicationMessage(session, message, sessions); } - //실시간 응원글 - else if (payload.equals("/cheer")) { - sendCheeringMessagesToAll(); - } - // 기타 메시지는 기존 방식으로 처리 + //실시간 응원글 및 기타 메시지는 기존 방식으로 처리 else { chatService.processMessage(session, message, sessions); } - } + //웹소켓 종료 @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { @@ -138,6 +139,7 @@ private void broadcastMessageToRoom(String message, Long roomId) { }); } + //발행 메시지를 처리하는 메서드 public void handlePublicationMessage(WebSocketSession session, TextMessage message, Map sessions) { String payload = message.getPayload(); @@ -158,6 +160,8 @@ private void sendChatHistoryToUser(WebSocketSession session, Long roomId) { JSONObject messageObject = new JSONObject(); messageObject.put("senderId", message.getSenderId()); messageObject.put("message", message.getMessage()); + messageObject.put("senderName", message.getSenderName()); + messageObject.put("roomId", message.getRoomId()); chatHistoryArray.add(messageObject); } @@ -174,31 +178,4 @@ private void sendChatHistoryToUser(WebSocketSession session, Long roomId) { // Handle exception as needed } } - - - // 모든 클라이언트에게 응원 메시지 전송 메서드 - private void sendCheeringMessagesToAll() { - String jsonMessages = convertCheeringMessagesToJson(); - sessions.values().forEach((session) -> { - if (session.isOpen()) { - try { - session.sendMessage(new TextMessage(jsonMessages)); - } catch (IOException e) { - logger.error("Error sending cheering messages to all: {}", e.getMessage()); - } - } - }); - } - - private List cheeringMessages = new ArrayList<>(); - - // 응원 메시지를 JSON 형식으로 변환하는 메서드 - private String convertCheeringMessagesToJson() { - cheeringMessages = chatService.getCheeringMessage(); - // List를 JSON 형식의 문자열로 변환하여 반환 - String jsonMessages = cheeringMessages.stream() - .map(message -> "{ \"senderId\": \"" + message.getSenderId() + "\", \"message\": \"" + message.getMessage() + "\" }") - .collect(Collectors.joining(",", "[", "]")); - return jsonMessages; - } } \ No newline at end of file diff --git a/src/main/java/com/example/capd/socket/domain/Message.java b/src/main/java/com/example/capd/socket/domain/Message.java index c00ebfc..f42ff7a 100644 --- a/src/main/java/com/example/capd/socket/domain/Message.java +++ b/src/main/java/com/example/capd/socket/domain/Message.java @@ -1,6 +1,7 @@ package com.example.capd.socket.domain; import com.example.capd.team.domain.Room; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/example/capd/socket/dto/CheeringMessageDto.java b/src/main/java/com/example/capd/socket/dto/CheeringMessageDto.java index 873942d..ba0d0cf 100644 --- a/src/main/java/com/example/capd/socket/dto/CheeringMessageDto.java +++ b/src/main/java/com/example/capd/socket/dto/CheeringMessageDto.java @@ -8,11 +8,6 @@ @NoArgsConstructor @AllArgsConstructor public class CheeringMessageDto { - private String senderId; + private String senderName; private String message; - - // senderId: message 형식의 문자열 반환 - public String toJsonString() { - return senderId + ":" + message; - } } diff --git a/src/main/java/com/example/capd/socket/dto/MessageDto.java b/src/main/java/com/example/capd/socket/dto/MessageDto.java index ee37fc0..e6ebeed 100644 --- a/src/main/java/com/example/capd/socket/dto/MessageDto.java +++ b/src/main/java/com/example/capd/socket/dto/MessageDto.java @@ -8,12 +8,11 @@ @Getter @Setter -@Builder @NoArgsConstructor -@AllArgsConstructor public class MessageDto { private String message; private String senderId; + private String senderName; private Long roomId; public Message toEntity(Room room) { @@ -25,12 +24,12 @@ public Message toEntity(Room room) { .build(); } - public static MessageDto fromEntity(Message message) { - MessageDto messageDto = new MessageDto(); - messageDto.setMessage(message.getMessage()); - messageDto.setSenderId(message.getSenderId()); - messageDto.setRoomId(message.getRoom().getId()); - return messageDto; + @Builder + MessageDto(String message, String senderId, String senderName, Long roomId){ + this.message = message; + this.senderId = senderId; + this.senderName = senderName; + this.roomId = roomId; } } diff --git a/src/main/java/com/example/capd/socket/service/ChatService.java b/src/main/java/com/example/capd/socket/service/ChatService.java index 5513a6d..ad0d494 100644 --- a/src/main/java/com/example/capd/socket/service/ChatService.java +++ b/src/main/java/com/example/capd/socket/service/ChatService.java @@ -1,136 +1,44 @@ package com.example.capd.socket.service; +import com.example.capd.User.domain.User; import com.example.capd.contest.repository.ContestRepository; +import com.example.capd.socket.config.MyHandler; import com.example.capd.socket.domain.Message; import com.example.capd.socket.repository.MessageRepository; import com.example.capd.team.domain.*; import com.example.capd.team.repository.*; -import com.example.capd.User.domain.*; -import com.example.capd.team.dto.*;; import com.example.capd.socket.dto.*; -import com.example.capd.User.dto.*;; import com.example.capd.User.repository.*; import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; -import com.example.capd.contest.domain.Contest; import java.io.IOException; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; @Service @RequiredArgsConstructor public class ChatService { - - private final UserRepository userRepository; + private static final Logger logger = LoggerFactory.getLogger(MyHandler.class); private final RoomRepository roomRepository; private final MessageRepository messageRepository; - private final TeamRepository teamRepository; - private final ContestRepository contestRepository; - - - public Long createRoom(ChatRoomDto chatRoomDto) { - Team team = teamRepository.findById(chatRoomDto.getTeamId()) - .orElseThrow(() -> new EntityNotFoundException("팀이 존재하지 않습니다.")); - - Room room = chatRoomDto.toEntity(team); - roomRepository.save(room); - - return room.getId(); - } - - public void deleteRoom(Long roomId) { - Room room = roomRepository.findById(roomId) - .orElseThrow(() -> new EntityNotFoundException("채팅방이 존재하지 않습니다.")); - - //메시지 -> 채팅방 -> 팀 순 삭제 - messageRepository.deleteAll(room.getMessages()); - roomRepository.deleteById(roomId); - teamRepository.deleteById(room.getTeam().getId()); - } - - //팀 id로 채팅방 id 조회 - public Long getRoomId(Long teamId) { - Room room = roomRepository.findByTeamId(teamId); - Contest contest = contestRepository.findByTeams_Id(teamId); - - if (isReceptionPeriodEnded(contest.getReceptionPeriod())) { - messageRepository.deleteAll(room.getMessages()); - //채팅방 삭제 - roomRepository.delete(room); - teamRepository.deleteById(room.getTeam().getId()); - return null; - } - return room.getId(); - } - - private boolean isReceptionPeriodEnded(String receptionPeriod) { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd'T'HH:mm:ss"); - String[] period = receptionPeriod.split("~"); - - LocalDateTime startDate = LocalDateTime.parse(period[0] + "T00:00:00", formatter); - LocalDateTime endDate = LocalDateTime.parse(period[1] + "T23:59:59", formatter); - LocalDateTime currentTime = LocalDateTime.now(); - - return currentTime.isEqual(endDate) || currentTime.isAfter(endDate); - } - - //유저가 속한 채팅방 전체 조회 - public List getRoomList(String userId) { - User user = userRepository.findByUserId(userId) - .orElseThrow(() -> new EntityNotFoundException("유저가 존재하지 않습니다.")); - - List teams = teamRepository.findByMembersUserId(user.getId()); - - List teamIds = teams.stream() - .map(Team::getId) - .collect(Collectors.toList()); - - List rooms = roomRepository.findByTeamIdIn(teamIds); - - //ChatRoomDto 형태로 엔티티를 변환 - List chatRoomDtos = rooms.stream() - .map(room -> { - return ChatRoomDto.builder() - .roomId(room.getId()) - .password(room.getPassword()) - .userId(userId) - .name(room.getName()) - .teamId(room.getTeam().getId()) - .build(); - }) - .collect(Collectors.toList()); - - return chatRoomDtos; - } + private final UserRepository userRepository; - //채팅방 비번 확인 - public Boolean checkRoomPw(RoomPwDto roomPwDto) { - Room room = roomRepository.findById(roomPwDto.getRoomId()) - .orElseThrow(() -> new EntityNotFoundException("채팅방이 존재하지 않습니다.")); - if (room.getPassword().equals(roomPwDto.getPassword())) { - return true; - } else { - return false; - } - } //이전 채팅 불러오기 public List getMessages(Long roomId) { List messages = messageRepository.findByRoomIdOrderByTimeStamp(roomId); List messageDtoList = messages.stream() .map(message -> { + Optional sender = userRepository.findByUserId(message.getSenderId()); return MessageDto.builder() - .roomId(message.getId()) - .senderId(message.getSenderId()) + .roomId(message.getRoom().getId()) + .senderId(sender.get().getUserId()) + .senderName(sender.get().getUsername()) .message(message.getMessage()) .build(); }) @@ -146,8 +54,8 @@ public void processMessage(WebSocketSession session, TextMessage message, Map sender = userRepository.findByUserId(chatMessageDto.getSenderId()); // 다른 세션에 새로운 채팅 메시지 전송 + chatMessageDto.setSenderName(sender.get().getUsername()); broadcastMessageToAll(sessionId, objectMapper.writeValueAsString(chatMessageDto), sessions); } } @@ -183,6 +93,30 @@ private boolean isCheeringMessage(TextMessage message) { } } + private void broadcastCheeringMessages(Map sessions) { + ObjectMapper objectMapper = new ObjectMapper(); + List cheeringMessages = getCheeringMessage(); + String messagePayload; + try { + Map messageWrapper = new HashMap<>(); + messageWrapper.put("type", "cheer"); + messageWrapper.put("cheeringMessages", cheeringMessages); + messagePayload = objectMapper.writeValueAsString(messageWrapper); + + } catch (IOException e) { + return; + } + + for (WebSocketSession s : sessions.values()) { + if (s.isOpen()) { + try { + s.sendMessage(new TextMessage(messagePayload)); + } catch (IOException e) { + } + } + } + } + // 모든 세션에 메시지 브로드캐스트 private void broadcastMessageToAll(String senderSessionId, String message, Map sessions) { sessions.values().forEach((s) -> { @@ -197,19 +131,19 @@ private void broadcastMessageToAll(String senderSessionId, String message, Map cheeringMessages = new ArrayList<>(); - private synchronized void saveCheeringMessage(String senderId, String message) { + private synchronized void saveCheeringMessage(String senderName, String message) { // 최대 저장할 응원 메시지 수를 초과하는 경우, 가장 오래된 메시지 삭제 if (cheeringMessages.size() >= MAX_CHEERING_MESSAGES) { // 가장 오래된 메시지 삭제 cheeringMessages.remove(0); } // 새로운 응원 메시지 추가 - cheeringMessages.add(new CheeringMessageDto(senderId, message)); + cheeringMessages.add(new CheeringMessageDto(senderName, message)); } public List getCheeringMessage(){ return cheeringMessages; diff --git a/src/main/java/com/example/capd/team/controller/RoomController.java b/src/main/java/com/example/capd/team/controller/RoomController.java index ffc4111..42bf3a5 100644 --- a/src/main/java/com/example/capd/team/controller/RoomController.java +++ b/src/main/java/com/example/capd/team/controller/RoomController.java @@ -2,8 +2,9 @@ import com.example.capd.User.config.CommonResponse; import com.example.capd.team.dto.ChatRoomDto; -import com.example.capd.team.dto.RoomPwDto; import com.example.capd.socket.service.ChatService; +import com.example.capd.team.dto.RoomParam; +import com.example.capd.team.service.RoomService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; @@ -17,12 +18,12 @@ @RequiredArgsConstructor public class RoomController { - private final ChatService chatService; + private final RoomService roomService; //방 저장 - @PostMapping("/chat-room") + @PostMapping("/room") public ResponseEntity saveChatRoom(@RequestBody ChatRoomDto chatRoomDto) { - chatService.createRoom(chatRoomDto); + roomService.createRoom(chatRoomDto); CommonResponse res = new CommonResponse( 200, HttpStatus.OK, @@ -32,61 +33,52 @@ public ResponseEntity saveChatRoom(@RequestBody ChatRoomDto chat return new ResponseEntity<>(res, res.getHttpStatus()); } + //단일 조회 + @GetMapping("/room/{roomId}") + public ChatRoomDto RoomId(@PathVariable Long roomId) { + return roomService.getRoom(roomId); - @GetMapping("/chat-room/{teamId}") - public ResponseEntity RoomId(@PathVariable Long teamId) { - CommonResponse res; - Long roomId = chatService.getRoomId(teamId); - if(roomId!=null){ - res = new CommonResponse( - 200, - HttpStatus.OK, - "조회 성공", - roomId - ); - }else { - res = new CommonResponse( - 400, - HttpStatus.NOT_FOUND, - "공모 기간이 끝나 삭제된 채팅방입니다.", - null - ); - } - return new ResponseEntity<>(res, res.getHttpStatus()); - } - + } - @GetMapping("/room-list/{userId}") + //userId가 속한 채팅방 전체 조회 + @GetMapping("/rooms/{userId}") public List RoomList(@PathVariable String userId) { - return chatService.getRoomList(userId); + return roomService.getRoomList(userId); + } + + @GetMapping("/rooms-contest/{contestId}") + public List getContestRoomList(@PathVariable Long contestId){ + return roomService.contestRoomList(contestId); } - @PostMapping("/chat-room/password") - public ResponseEntity checkRoomPw(@RequestBody RoomPwDto roomPwDto){ - Boolean checkPw = chatService.checkRoomPw(roomPwDto); + @PostMapping("/room-update/status") + public ResponseEntity updateStatus(@RequestBody RoomParam roomParam){ + roomService.updateRoomStatus(roomParam.getRoomId(),roomParam.getStatus(), roomParam.getUserId()); + CommonResponse res = new CommonResponse( + 200, + HttpStatus.OK, + "수정 성공", + null + ); + return new ResponseEntity<>(res, res.getHttpStatus()); + } - CommonResponse res; - if(checkPw){ - res = new CommonResponse( - 200, - HttpStatus.OK, - "비밀번호 인증 성공", - null - ); - }else{ - res = new CommonResponse( - 400, - HttpStatus.NOT_FOUND, - "비밀번호 인증 실패", - null - ); - } + @PostMapping("/room-update/members") + public ResponseEntity updateTeamMember(@RequestBody RoomParam roomParam){ + roomService.addMembersToTeam(roomParam.getRoomId(),roomParam.getMemberIds(), roomParam.getUserId()); + CommonResponse res = new CommonResponse( + 200, + HttpStatus.OK, + "수정 성공", + null + ); return new ResponseEntity<>(res, res.getHttpStatus()); } + //방삭제 @DeleteMapping("delete-room/{roomId}") public ResponseEntity deleteRoom(@PathVariable Long roomId){ - chatService.deleteRoom(roomId); + roomService.deleteRoom(roomId); CommonResponse res = new CommonResponse( 200, HttpStatus.OK, diff --git a/src/main/java/com/example/capd/team/controller/TeamController.java b/src/main/java/com/example/capd/team/controller/TeamController.java deleted file mode 100644 index b3d248f..0000000 --- a/src/main/java/com/example/capd/team/controller/TeamController.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.example.capd.team.controller; - -import com.example.capd.User.config.CommonResponse; -import com.example.capd.team.dto.TeamParam; -import com.example.capd.team.dto.TeamRequestDto; -import com.example.capd.team.service.TeamService; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.util.List; - -@RestController -@RequiredArgsConstructor -public class TeamController { - - private final TeamService teamService; - - @PostMapping("/team-save") - public ResponseEntity saveTeam(@RequestBody TeamRequestDto teamRequestDto){ - teamService.createTeam(teamRequestDto); - CommonResponse res = new CommonResponse( - 200, - HttpStatus.OK, - "저장 성공", - null - ); - return new ResponseEntity<>(res, res.getHttpStatus()); - } - - @PostMapping("/team-update/status/{userId}") - public ResponseEntity updateTeamStatus(@RequestBody TeamParam teamParam, @PathVariable String userId){ - teamService.updateTeamStatus(teamParam.getTeamId(),teamParam.getStatus(), userId); - CommonResponse res = new CommonResponse( - 200, - HttpStatus.OK, - "수정 성공", - null - ); - return new ResponseEntity<>(res, res.getHttpStatus()); - } - - - @PostMapping("/team-update/members/{userId}") - public ResponseEntity updateTeamMember(@RequestBody TeamParam teamParam, @PathVariable String userId){ - teamService.addMembersToTeam(teamParam.getTeamId(),teamParam.getMemberIds(), userId); - CommonResponse res = new CommonResponse( - 200, - HttpStatus.OK, - "수정 성공", - null - ); - return new ResponseEntity<>(res, res.getHttpStatus()); - } - - - @GetMapping("/team-get/{teamId}") - public TeamParam getTeam(@PathVariable Long teamId){ - return teamService.getTeam(teamId); - } - - @GetMapping("/teams-user/{userId}") - public List getMyTeamList(@PathVariable String userId){ - return teamService.MyteamList(userId); - } - - @GetMapping("/teams-contest/{contestId}") - public List getContestTeamList(@PathVariable Long contestId){ - return teamService.contestTeamList(contestId); - } - - @DeleteMapping("/team-delete/{teamId}") - public ResponseEntity deleteTeam(@PathVariable Long teamId){ - teamService.deleteTeam(teamId); - CommonResponse res = new CommonResponse( - 200, - HttpStatus.OK, - "삭제 성공", - null - ); - return new ResponseEntity<>(res, res.getHttpStatus()); - } -} diff --git a/src/main/java/com/example/capd/team/domain/Room.java b/src/main/java/com/example/capd/team/domain/Room.java index bec8f95..53e887b 100644 --- a/src/main/java/com/example/capd/team/domain/Room.java +++ b/src/main/java/com/example/capd/team/domain/Room.java @@ -1,8 +1,9 @@ package com.example.capd.team.domain; +import com.example.capd.User.domain.Review; +import com.example.capd.contest.domain.Contest; import com.example.capd.socket.domain.Message; -import com.example.capd.team.domain.Team; -import com.fasterxml.jackson.annotation.JsonManagedReference; +import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -10,6 +11,7 @@ import lombok.NoArgsConstructor; import java.util.ArrayList; +import java.util.Comparator; import java.util.Date; import java.util.List; @@ -24,16 +26,41 @@ public class Room { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + private Boolean status; + private String leaderId; private String name; private Date timeStamp; - private String password; - @OneToOne - @JoinColumn(name = "team_id") - @JsonManagedReference - private Team team; + //팀멤버 매핑 + @OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnore + private List members = new ArrayList<>(); + //리뷰랑 매핑 @OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnore + private List roomReviews = new ArrayList<>(); + + //공모전 매핑 + @ManyToOne + @JoinColumn(name = "contest_id") + private Contest contest; + + @OneToMany(mappedBy = "room", cascade = CascadeType.ALL, orphanRemoval = true) + @JsonIgnore private List messages = new ArrayList<>(); + public void setStatus(Boolean status){ + this.status = status; + } + + //가장 최신 메시지 반환 + public Message getLastMessage() { + if (messages.isEmpty()) { + return null; + } + return messages.stream() + .max(Comparator.comparing(Message::getId)) + .orElse(null); + } } \ No newline at end of file diff --git a/src/main/java/com/example/capd/team/domain/Team.java b/src/main/java/com/example/capd/team/domain/Team.java deleted file mode 100644 index fd52ff9..0000000 --- a/src/main/java/com/example/capd/team/domain/Team.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.example.capd.team.domain; - -import com.example.capd.User.domain.Review; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonManagedReference; -import jakarta.persistence.*; -import lombok.*; -import com.example.capd.contest.domain.Contest; -import java.util.ArrayList; -import java.util.List; - -@Entity -@Builder -@Getter -@AllArgsConstructor -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Team { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - //팀확정 상태. 팀이 확정되고, 공모전 접수 기간이 끝난다면 리뷰 작성 - private Boolean status; - private String leaderId; - - //팀멤버 매핑 - @OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true) - @JsonIgnore - private List members = new ArrayList<>(); - -// //리뷰랑 매핑 - @OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true) - private List teamReviews = new ArrayList<>(); - - //공모전 매핑 - @ManyToOne - @JoinColumn(name = "contest_id") - private Contest contest; - - @JsonManagedReference - @OneToOne(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true) - private Room room; - - public void setStatus(Boolean status){ - this.status = status; - } -} diff --git a/src/main/java/com/example/capd/team/domain/TeamMember.java b/src/main/java/com/example/capd/team/domain/TeamMember.java index 7a3c604..bf6b4a6 100644 --- a/src/main/java/com/example/capd/team/domain/TeamMember.java +++ b/src/main/java/com/example/capd/team/domain/TeamMember.java @@ -17,21 +17,21 @@ public class TeamMember { private Long id; @ManyToOne - @JoinColumn(name = "team_id") - private Team team; + @JoinColumn(name = "room_id") + private Room room; @ManyToOne - @JoinColumn(name = "user_id") + @JoinColumn(name = "userId") private User user; @Builder - public TeamMember(User user, Team team) { + public TeamMember(User user, Room room) { this.user=user; - this.team=team; + this.room=room; } - public static TeamMember fromUserAndTeam(User user, Team team) { - return new TeamMember(user, team); + public static TeamMember fromUserAndTeam(User user, Room room) { + return new TeamMember(user, room); } } diff --git a/src/main/java/com/example/capd/team/dto/ChatRoomDto.java b/src/main/java/com/example/capd/team/dto/ChatRoomDto.java index f382fce..52391ba 100644 --- a/src/main/java/com/example/capd/team/dto/ChatRoomDto.java +++ b/src/main/java/com/example/capd/team/dto/ChatRoomDto.java @@ -1,10 +1,14 @@ package com.example.capd.team.dto; +import com.example.capd.contest.domain.Contest; import com.example.capd.team.domain.Room; -import com.example.capd.team.domain.Team; import lombok.*; +import java.awt.*; +import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.Date; +import java.util.List; @Getter @Setter @@ -12,18 +16,23 @@ @NoArgsConstructor(access = AccessLevel.PUBLIC) @AllArgsConstructor public class ChatRoomDto { - private Long teamId; - private String userId; + + private Boolean status; private String name; - private String password; + private String leaderId; + + private List memberIds; + private Long contestId; private Long roomId; + private String lastMessage; + private String LastMessageTimeStamp; - public Room toEntity(Team team) { + public Room toEntity(Contest contest) { return Room.builder() .timeStamp(new Date()) - .password(password) .name(name) - .team(team) + .leaderId(leaderId) + .contest(contest) .build(); } } diff --git a/src/main/java/com/example/capd/team/dto/TeamParam.java b/src/main/java/com/example/capd/team/dto/RoomParam.java similarity index 57% rename from src/main/java/com/example/capd/team/dto/TeamParam.java rename to src/main/java/com/example/capd/team/dto/RoomParam.java index 5bd160e..e14f377 100644 --- a/src/main/java/com/example/capd/team/dto/TeamParam.java +++ b/src/main/java/com/example/capd/team/dto/RoomParam.java @@ -1,16 +1,16 @@ package com.example.capd.team.dto; -import lombok.*; +import lombok.Getter; import java.util.List; @Getter -@Setter -@NoArgsConstructor(access = AccessLevel.PUBLIC) -public class TeamParam { +public class RoomParam +{ private Boolean status; + private String userId; private List memberIds; private Long contestId; - private Long teamId; + private Long roomId; } diff --git a/src/main/java/com/example/capd/team/dto/RoomPwDto.java b/src/main/java/com/example/capd/team/dto/RoomPwDto.java deleted file mode 100644 index 93fcb80..0000000 --- a/src/main/java/com/example/capd/team/dto/RoomPwDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.capd.team.dto; - -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; - -@Getter -@Setter -@Builder -public class RoomPwDto { - private Long roomId; - private String password; -} diff --git a/src/main/java/com/example/capd/team/dto/TeamRequestDto.java b/src/main/java/com/example/capd/team/dto/TeamRequestDto.java deleted file mode 100644 index f6d9f24..0000000 --- a/src/main/java/com/example/capd/team/dto/TeamRequestDto.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.example.capd.team.dto; - -import com.example.capd.team.domain.Team; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import com.example.capd.contest.domain.Contest; -import java.util.List; - -@Getter -@Setter -@Builder -public class TeamRequestDto { - - private Boolean status; - private List memberIds; - private Long contestId; - private String leaderId; - - - public Team toEntity(Contest contest){ - return Team.builder() - .status(status) - .leaderId(leaderId) - .contest(contest).build(); - } - -} diff --git a/src/main/java/com/example/capd/team/repository/RoomRepository.java b/src/main/java/com/example/capd/team/repository/RoomRepository.java index 9d48050..28ab132 100644 --- a/src/main/java/com/example/capd/team/repository/RoomRepository.java +++ b/src/main/java/com/example/capd/team/repository/RoomRepository.java @@ -11,8 +11,11 @@ @Repository public interface RoomRepository extends JpaRepository { - @Query("SELECT DISTINCT r FROM Room r JOIN r.team t WHERE t.id IN :teamIds") - List findByTeamIdIn(@Param("teamIds") List teamIds); - Room findByTeamId(Long teamId); + List findAllByMembersUserUserId(String userId); + + List findByContestId(Long contestId); + + @Query("SELECT r FROM Room r JOIN r.members m WHERE m.user.id = :userId") + List findByMembersUserId(Long userId) ; } diff --git a/src/main/java/com/example/capd/team/repository/TeamRepository.java b/src/main/java/com/example/capd/team/repository/TeamRepository.java deleted file mode 100644 index 7019d2b..0000000 --- a/src/main/java/com/example/capd/team/repository/TeamRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.capd.team.repository; - -import com.example.capd.team.domain.Team; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Repository; - -import java.util.List; - -@Repository -public interface TeamRepository extends JpaRepository { - List findAllByMembersUserUserId(String userId); - List findByContestId(Long contestId); - - @Query("SELECT t FROM Team t JOIN t.members m WHERE m.user.id = :userId") - List findByMembersUserId(Long userId); - -} - diff --git a/src/main/java/com/example/capd/team/service/RoomService.java b/src/main/java/com/example/capd/team/service/RoomService.java new file mode 100644 index 0000000..9b9bc1d --- /dev/null +++ b/src/main/java/com/example/capd/team/service/RoomService.java @@ -0,0 +1,201 @@ +package com.example.capd.team.service; + +import com.example.capd.Exception.MemberLimitExceededException; +import com.example.capd.Exception.TeamAlreadyConfirmedException; +import com.example.capd.Exception.UnauthorizedTeamMemberModificationException; +import com.example.capd.contest.repository.ContestRepository; +import com.example.capd.socket.domain.Message; +import com.example.capd.socket.repository.MessageRepository; +import com.example.capd.team.domain.*; +import com.example.capd.team.repository.*; +import com.example.capd.User.domain.*; +import com.example.capd.team.dto.*;; +import com.example.capd.socket.dto.*; +import com.example.capd.User.dto.*;; +import com.example.capd.User.repository.*; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import com.example.capd.contest.domain.Contest; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class RoomService { + + private final UserRepository userRepository; + private final RoomRepository roomRepository; + private final MessageRepository messageRepository; + private final ContestRepository contestRepository; + private final TeamMemberRepository teamMemberRepository; + + + public void createRoom(ChatRoomDto chatRoomDto) { + + List memberIds = chatRoomDto.getMemberIds(); + Long contestId = chatRoomDto.getContestId(); + + Contest contest = contestRepository.findById(contestId) + .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 공모전 입니다: " + contestId)); + + if(memberIds.size()>6){ + throw new MemberLimitExceededException(); + } + + //방 생성 + Room room = chatRoomDto.toEntity(contest); + room.setStatus(false); + room = roomRepository.save(room); + Room finalTeam = room; + List teamMembers = userRepository.findAllByUserIdIn(memberIds) + .stream() + .map(user -> TeamMember.fromUserAndTeam(user, finalTeam)) + .collect(Collectors.toList()); + + teamMemberRepository.saveAll(teamMembers); + } + + + // 채팅방 단일 조회 + public ChatRoomDto getRoom(Long roomId) { + Room room = roomRepository.findById(roomId) + .orElseThrow(() -> new EntityNotFoundException("채팅방을 찾을 수 없습니다. userId=" + roomId)); + Contest contest = contestRepository.findByRooms_Id(roomId); + //공모전 +// if (isReceptionPeriodEnded(contest.getReceptionPeriod())) { +// messageRepository.deleteAll(room.getMessages()); +// //채팅방 삭제 +// roomRepository.delete(room); +// return null; +// } + return mapToDto(room); + } + + //방 확정 상태 변경 + public void updateRoomStatus(Long roomId, Boolean newStatus, String userId) { + Room room = roomRepository.findById(roomId) + .orElseThrow(() -> new EntityNotFoundException("채팅방이 존재하지 않습니다.")); + + User modifyingUser = userRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 유저 아이디: " + userId)); + + if (!modifyingUser.getUserId().equals(room.getLeaderId())) { + throw new UnauthorizedTeamMemberModificationException(); + } + if (room.getStatus() != null && room.getStatus()) { + throw new TeamAlreadyConfirmedException(); + } + + room.setStatus(newStatus); + roomRepository.save(room); + } + + private boolean isReceptionPeriodEnded(String receptionPeriod) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy.MM.dd'T'HH:mm:ss"); + String[] period = receptionPeriod.split("~"); + + LocalDateTime startDate = LocalDateTime.parse(period[0] + "T00:00:00", formatter); + LocalDateTime endDate = LocalDateTime.parse(period[1] + "T23:59:59", formatter); + LocalDateTime currentTime = LocalDateTime.now(); + + + return currentTime.isEqual(endDate) || currentTime.isAfter(endDate); + } + + //유저가 속한 채팅방 전체 조회 + public List getRoomList(String userId) { + + List rooms = roomRepository.findAllByMembersUserUserId(userId); + + return rooms.stream() + .map(this::mapToDto) + .collect(Collectors.toList()); + } + + //공모전에 생성된 채팅방 전체 조회 + public List contestRoomList(Long contestId) { + List rooms = roomRepository.findByContestId(contestId); + return rooms.stream() + .map(this::mapToDto) + .collect(Collectors.toList()); + } + + public void deleteRoom(Long roomId) { + Room room = roomRepository.findById(roomId) + .orElseThrow(() -> new EntityNotFoundException("채팅방이 존재하지 않습니다.")); + + //메시지 -> 채팅방 순 삭제 + messageRepository.deleteAll(room.getMessages()); + roomRepository.deleteById(roomId); + } + + //팀 멤버 수정 + public void addMembersToTeam(Long roomId, List memberIds, String userId) { + Room room = roomRepository.findById(roomId) + .orElseThrow(() -> new EntityNotFoundException("채팅방이 존재하지 않습니다 ")); + + User modifyingUser = userRepository.findByUserId(userId) + .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 유저 아이디: " + userId)); + + if (!modifyingUser.getUserId().equals(room.getLeaderId())) { + throw new UnauthorizedTeamMemberModificationException(); + } + if (room.getStatus() != null && room.getStatus()) { + throw new TeamAlreadyConfirmedException(); + } + + List teamMembers = new ArrayList<>(); + + for (String memberId : memberIds) { + User user = userRepository.findByUserId(memberId) + .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 유저 아이디: " + memberId)); + + // 이미 저장되어 있는 멤버인지 확인 + boolean isMember = room.getMembers().stream() + .anyMatch(member -> member.getUser().equals(user)); + + if (!isMember) { + TeamMember teamMember = TeamMember.builder() + .user(user) + .room(room) + .build(); + teamMembers.add(teamMember); + } + } + + room.getMembers().addAll(teamMembers); + roomRepository.save(room); + } + + private ChatRoomDto mapToDto(Room room) { + ChatRoomDto chatRoomDto = new ChatRoomDto(); + chatRoomDto.setRoomId(room.getId()); + chatRoomDto.setName(room.getName()); + chatRoomDto.setLeaderId(room.getLeaderId()); + chatRoomDto.setContestId(room.getContest().getId()); + + List stringMemberIds = room.getMembers().stream() + .map(teamMember -> teamMember.getUser().getUserId()) + .collect(Collectors.toList()); + chatRoomDto.setMemberIds(stringMemberIds); + + Message lastMessage = room.getLastMessage(); + if (lastMessage != null) { + chatRoomDto.setLastMessage(lastMessage.getMessage()); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm"); + String formattedTimestamp = lastMessage.getTimeStamp().format(formatter); + chatRoomDto.setLastMessageTimeStamp(formattedTimestamp); + } + return chatRoomDto; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/capd/team/service/TeamService.java b/src/main/java/com/example/capd/team/service/TeamService.java deleted file mode 100644 index 40d1d68..0000000 --- a/src/main/java/com/example/capd/team/service/TeamService.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.capd.team.service; - -import com.example.capd.team.dto.TeamRequestDto; -import com.example.capd.team.dto.TeamParam; -import org.springframework.stereotype.Service; - -import java.util.List; - -@Service -public interface TeamService { - - //팀 생성 - public void createTeam(TeamRequestDto teamRequestDto); - - //단일 조회 (특정 팀 조회) - public TeamParam getTeam(Long teamId); - - //팀 전체 조회 (본인이 속한 팀 리스트) - public List MyteamList(String userId); - - //팀 전체 조회 (공모전 참가 팀 리스트) - public List contestTeamList(Long contestId); - - //팀 해산 - public void deleteTeam(Long teamId); - - //팀 status 변경 - public void updateTeamStatus(Long teamId, Boolean newStatus, String userId); - //팀원 수정 - public void addMembersToTeam(Long teamId, List memberIds, String userId); -} diff --git a/src/main/java/com/example/capd/team/service/TeamServiceImpl.java b/src/main/java/com/example/capd/team/service/TeamServiceImpl.java deleted file mode 100644 index 9fa923e..0000000 --- a/src/main/java/com/example/capd/team/service/TeamServiceImpl.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.example.capd.team.service; - - -import com.example.capd.Exception.*; -import com.example.capd.team.domain.Team; -import com.example.capd.team.domain.TeamMember; -import com.example.capd.User.domain.*; -import com.example.capd.team.dto.TeamRequestDto; -import com.example.capd.team.dto.TeamParam; -import com.example.capd.contest.repository.ContestRepository; -import com.example.capd.team.repository.TeamMemberRepository; -import com.example.capd.team.repository.TeamRepository; -import com.example.capd.User.repository.UserRepository; -import jakarta.persistence.EntityNotFoundException; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; -import com.example.capd.contest.domain.Contest; -@Service -@RequiredArgsConstructor -public class TeamServiceImpl implements TeamService { - - private final TeamRepository teamRepository; - private final TeamMemberRepository teamMemberRepository; - private final UserRepository userRepository; - private final ContestRepository contestRepository; - - - @Override - public void createTeam(TeamRequestDto teamRequestDto) { - { - List memberIds = teamRequestDto.getMemberIds(); - Boolean status = teamRequestDto.getStatus(); - Long contestId = teamRequestDto.getContestId(); - - Contest contest = contestRepository.findById(contestId) - .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 공모전 입니다: " + contestId)); - - if(memberIds.size()>6){ - throw new MemberLimitExceededException(); - } - //팀 생성 - Team team = teamRequestDto.toEntity(contest); - team = teamRepository.save(team); - - Team finalTeam = team; - List teamMembers = userRepository.findAllByUserIdIn(memberIds) - .stream() - .map(user -> TeamMember.fromUserAndTeam(user, finalTeam)) - .collect(Collectors.toList()); - - teamMemberRepository.saveAll(teamMembers); - - } - } - - @Override - public TeamParam getTeam(Long teamId) { - Team team = teamRepository.findById(teamId) - .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 팀 아이디 입니다: " + teamId)); - - return mapToDto(team); - } - - @Override - public List MyteamList(String userId) { - List teams = teamRepository.findAllByMembersUserUserId(userId); - - return teams.stream() - .map(this::mapToDto) - .collect(Collectors.toList()); - } - - @Override - public List contestTeamList(Long contestId) { - List teams = teamRepository.findByContestId(contestId); - return teams.stream() - .map(this::mapToDto) - .collect(Collectors.toList()); - } - - @Override - public void updateTeamStatus(Long teamId, Boolean newStatus,String userId) { - Team team = teamRepository.findById(teamId) - .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 팀입니다.: " + teamId)); - - User modifyingUser = userRepository.findByUserId(userId) - .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 유저 아이디: " + userId)); - - if (!modifyingUser.getUserId().equals(team.getLeaderId())) { - throw new UnauthorizedTeamMemberModificationException(); - } - if (team.getStatus() != null && team.getStatus()) { - throw new TeamAlreadyConfirmedException(); - } - - team.setStatus(newStatus); - teamRepository.save(team); - } - - @Override - public void addMembersToTeam(Long teamId, List memberIds, String userId) { - Team team = teamRepository.findById(teamId) - .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 팀입니다.: " + teamId)); - - User modifyingUser = userRepository.findByUserId(userId) - .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 유저 아이디: " + userId)); - - if (!modifyingUser.getUserId().equals(team.getLeaderId())) { - throw new UnauthorizedTeamMemberModificationException(); - } - if (team.getStatus() != null && team.getStatus()) { - throw new TeamAlreadyConfirmedException(); - } - - List teamMembers = new ArrayList<>(); - - for (String memberId : memberIds) { - User user = userRepository.findByUserId(memberId) - .orElseThrow(() -> new EntityNotFoundException("존재하지 않는 유저 아이디: " + memberId)); - - // 이미 저장되어 있는 멤버인지 확인 - boolean isMember = team.getMembers().stream() - .anyMatch(member -> member.getUser().equals(user)); - - if (!isMember) { - TeamMember teamMember = TeamMember.builder() - .user(user) - .team(team) - .build(); - teamMembers.add(teamMember); - } - } - - team.getMembers().addAll(teamMembers); - teamRepository.save(team); - } - - @Override - public void deleteTeam(Long teamId) { - teamRepository.deleteById(teamId); - } - - private TeamParam mapToDto(Team team) { - TeamParam teamParam = new TeamParam(); - teamParam.setStatus(team.getStatus()); - teamParam.setTeamId(team.getId()); - teamParam.setContestId(team.getContest().getId()); - List stringMemberIds = team.getMembers().stream() - .map(teamMember -> teamMember.getUser().getUserId()) - .collect(Collectors.toList()); - teamParam.setMemberIds(stringMemberIds); - - return teamParam; - } - -} - - - - - diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 3d8cbbb..0438430 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,6 +1,6 @@ # Database Configuration -spring.datasource.url=jdbc:mysql://localhost:3306/capstone +spring.datasource.url=jdbc:mysql://localhost:3306/capd spring.datasource.username=root spring.datasource.password=db12 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver @@ -14,5 +14,15 @@ spring.jpa.show-sql=true #logging.level.org.springframework.web=INFO #logging.level.com.yourpackage=DEBUG # -## Server Configuration +## Server Configuration 32 33 34 35** 37 38 39 40 41 42 423 #server.port=8080 + +spring.jpa.properties.hibernate.format_sql=true +jwt.expiration_time=86400000 +jwt.secret=VlwEyVBsYt9V7zq57TejMnVUyzblYcfPQye08f7MGVA9XkHa +spring.jpa.generate-ddl=true +spring.jpa.format_sql=true + +spring.mvc.pathmatch.matching-strategy=ant_path_matcher + +security.jwt.header=Authorization diff --git a/src/main/resources/templates/main.html b/src/main/resources/templates/main.html new file mode 100644 index 0000000..49ff2f9 --- /dev/null +++ b/src/main/resources/templates/main.html @@ -0,0 +1,43 @@ + + + + + 메인화면 + + +

엑셀 업로드 페이지~

+

테이블 id값(기본키) 1부터 시작 안하면 에러남 주의 특히 공모전

+

ALTER TABLE 테이블명 AUTO_INCREMENT = 1;

+ +

User Excel Upload

+
+ + +
+ +

Profile Excel Upload

+
+ + +
+ +

Participation Excel Upload

+
+ + +
+ +

Room Excel Upload

+
+ + +
+ +

Review Excel Upload

+
+ + +
+ + + \ No newline at end of file diff --git a/src/main/resources/templates/success.html b/src/main/resources/templates/success.html new file mode 100644 index 0000000..50897ea --- /dev/null +++ b/src/main/resources/templates/success.html @@ -0,0 +1,10 @@ + + + + + Title + + +

굿! 다음 ㄱㄱ

+ + \ No newline at end of file diff --git a/wevity_contest_data.json b/wevity_contest_data.json index 608bbe5..332acf1 100644 --- a/wevity_contest_data.json +++ b/wevity_contest_data.json @@ -1 +1 @@ -[{"contest_title": "제4회 한국조지메이슨대학교 소셜임팩트 디지털아트 경연대회", "contest_host": "한국조지메이슨대학교", "contest_target_participants": "대학생, 청소년", "contest_reception_period": "2024-02-05 ~ 2024-04-14", "contest_decision_period": "비공개", "contest_competition_area": "비공개", "contest_award": "1천만원이하, 1등 상금 : 100만원", "contest_homepage": "https://progressatplay.org/kr-home/", "contest_how_to_apply": "비공개", "contest_fee": "비공개", "contest_image": "https://www.wevity.com/upload/contest/20240202164735_819c302d.jpg", "contest_detail_text": "제4회 한국조지메이슨대학교 소셜임팩트 디지털아트 경연대회\n\n■ 공모 내용\n우리 사회가 직면한 다양한 문제에 컴퓨터게임 및 디지털미디어를 활용해 대중적 인식을 높이기 위한 경연대회\nex) 빈부격차, 환경문제, 디지털격차, 저출산문제, 난민문제 등 \n\n■ 지원 자격\n- 국·내외 청소년 및 대학생 누구나 \n\n■ 접수기간: 2.5(월) - 4.14(일)\n\n■ 지원 방법\n- 구글폼으로 접수 ( https://docs.google.com/forms/d/1vwcFgPtNHbdIjuUYtF2QrVagROyht4oLxTjUeHnxw2M/edit )\n\n■ 시상내역\n[대학생]\n 최우수상:100만원\n 우수상:70만원\n 장려상:50만원\n[중·고등학생]\n 최우수상:70만원\n 우수상:50만원\n 장려상:50만원\n[인기상]\n 인기상:30만원\n[특전]\n 수상자 전원 NCSOFT 사옥 및 스튜디오 투어 \n\n■ 문의 사항\n- 032-626-5027\n\n"}, {"contest_title": "퀴즈노스 영상 콘테스트", "contest_host": "(주)유썸퀴즈노스코리아/(주)피아미디어그", "contest_target_participants": "제한없음", "contest_reception_period": "2024-01-15 ~ 2024-03-18", "contest_decision_period": "비공개", "contest_competition_area": "비공개", "contest_award": "총 상금 1,700만원 규모, 1등 상금 : 1,000만원", "contest_homepage": "http://www.quiznos.co.kr/news/promotion.php?ptype=view&idx=1726&page=1&code=promotion", "contest_how_to_apply": "비공개", "contest_fee": "비공개", "contest_image": "https://www.wevity.com/upload/contest/20240129033904_92b93c5e.jpg", "contest_detail_text": "퀴즈노스 영상 콘테스트\n\n■ 공모개요\n- 퀴즈노스 샌드위치만의 차별화된 장점을 표현하는 모든 주제를 영상으로 만들어 주세요!\n\n■ 공모주제\n- 갓 구운 샌드위치, 퀴즈노스\n- 지금까지 먹어보지 못한 최상의 샌드위치, 퀴즈노스\n- 퀴즈노스 브랜드를 표현할 수 있는 모든 주제\n\n■ 공모분야\n- 영상콘텐츠\n- CF 홍보 영상 (20초 내외)\n- 바이럴 영상(1~3분)\n- 숏츠 영상(10초~1분)\n\n■ 기간 및 일정\n- 접수기간 : 2024.01.15(월)~2024.03.18(월)\n- 발표 : 2024.03.22(금)\n\n■ 지원자격\n- 제한 없음\n\n■ 접수방법\n- 담당자 전자메일로 발송(fiayh@fiamg.co.kr)\n ※ 해시태그 #퀴즈노스 #샌드위치 #퀴즈노스공모전 태그\n 본인 인스타그램 혹은 유튜브 채널에 업로드 후 개인정보 수집, 저작물 사용 동의서, 원본 파일과 함께 제출\n\n■ 출품규격\n- 영상 (avi, mp4, mov 중 택1)\n\n■ 제출서류\n- 개인정보 수집 동의서\n- 저작물 사용 동의서\n- 영상 업로드한 링크\n- 원본 영상\n\n■ 심사기준\n- 작품성, 완성도, 창의성, 대중성, 브랜드 적합성, 마케팅 활용도, 조회수 등을 종합적으로 평가\n\n■ 시상내역\n- 최우수상 (1팀) : 1,000만원\n- 우수상 (2팀) : 300만원\n- 장려상 (20팀) : 퀴즈노스 기프트카드 5만원권\n\n■ 유의사항\n- 복수의 작품응모는 가능하나, 중복수상은 불가함.\n- 본 영상 외, 메이킹영상(NG영상, 팀 홍보영상 등) 또한 자유롭게 출품할 수 있음. \n- 접수된 사항은 수정 및 취소 불가하며, 접수된 서류는 일체 반환하지 않음. \n 다만, 입상하지 않은 응모작에 대해서는 공모 종료일로부터 3개월 이내에 모두 폐기함.\n- 수상작의 지적재산권에대한 이용료는 시상금으로 대체하며, 상금에 대한 제세공과금은 수상자가 부담함.\n- 수상작과 관련하여 저작권, 초상권 등 법적 분쟁 발생 시 응모자 본인에게 책임이 있음.\n- 수상작은 작품의 원본 파일을 반드시 제출 해야함.\n- 수상자는 공모전에 출품한 작품의 모든 저작권, 소유권이 “퀴즈노스”에 귀속됨을 확인함\n (출품작 및 수상작은 추후 주최사의 홍보 콘텐츠로 활용될 수 있음. )\n- 영상제작 시 저작권 및 초상권 문제가 없는 (혹은 저작권자의 사용승인을 받은) 소스를 사용하시길 권장함.\n 추후 관련 문제가 발생할 경우, 책임은 출품자 본인에게 있음. \n- 참가신청 기재 내용에 허위사실이 발견되거나 기 입상된 작품, 표적작품으로 판명될 경우 심사에서 제외되며, 시상 후에라도 수상자 선정 취소(시상금 전액 반납) 처리함\n- “수상자”는 AI를 통하여 작품을 제작할 경우 제출시에 AI로 제작한 항목(카피, 이미지, 제작, 영상등)을 공유하여야 하고, 이를 고지하지 않거나 차후 저작권에 문제가 생길 경우 “수상자”는 “회사”를 면책하고, 분쟁에 따른 모든 민형사상 책임질 것을 확인함.\n- 적합한 작품이 없을 경우 수상자를 선정하지 않거나, 수상금에 변동이 있을 수 있음.\n- 심사내용은 비공개를 원칙으로 함\n- 상기 내용은 주최사 및 사무국 사정으로 인해 변경될 수 있음. \n\n■ 문의\n- 이메일 : fiayh@fiamg.co.kr\n \n\n\n\n"}, {"contest_title": "2024년 프로보노 ICT멘토링 프로젝트 및 참여자 모집", "contest_host": "과학기술정보통신부/한국정보방송통신대연", "contest_target_participants": "대학생, 기타", "contest_reception_period": "2024-02-02 ~ 2024-03-21", "contest_decision_period": "비공개", "contest_competition_area": "비공개", "contest_award": "다양한 혜택, 1등 상금 : 400만원", "contest_homepage": "https://www.hanium.or.kr/", "contest_how_to_apply": "비공개", "contest_fee": "비공개", "contest_image": "https://www.wevity.com/upload/contest/20240223200113_98ce83ed.jpg", "contest_detail_text": "2024년 프로보노 ICT멘토링 프로젝트 및 참여자 모집\n\n■ 개요\nICT전문가와 대학생들이 한 팀을 이루어 장애인, 노약자 등 사회적 약자의 삶의 질 개선을 주제로 ICT 프로젝트 수행\n프로젝트 수행팀만 공모전 참여 가능\n\n■ 일정\n- 2월2일~3월21일: 프로젝트 및 참여자 모집\n- 3월 말: 승인 프로젝트 발표\n- 4월1일~10월31일: 프로젝트 수행(멘토링 진행)\n * ICT멘토링데이: 4월13일~14일\n * 프로젝트 중간점검: 6~7월\n * 공모전: 8~10월\n * ICT멘토링 엑스포: 12월\n\n■ 참가자격: ICT에 관심있는 국내 대학생\n- 전공 및 학년 무관/휴학생 가능\n\n■ 지원사항\n- 개발환경(실습장비, 클라우드서버, 협업툴 팀당 130만원 이내)\n- 회의실(토즈 등) 및 교통비(시외) 실비 지원\n- 온·오프라인 교육, 취업컨설팅\n- 앱 등록, 프로그램 등록, 논문게재 수수료 실비 지원\n- 특허 코칭(전담 변리사의 프로젝트 고도화 컨설팅 및 출원 지원)\n- 공모전 혜택(팀당)\n (대상 1팀) 과기부장관상+장학금 400만원+해외장학연수\n (금상 2팀) 과기부장관상+장학금 300만원\n (은상 3팀) IITP원장상+장학금 200만원\n (동상 10팀) ICT대연합회장상+장학금 70만원\n (입선 19팀) ICT대연합회장상+장학금 30만원\n\n■ 참여신청 방법\n(방법 1) 프로젝트 개요서를 작성하여 사이트에 등록 후 팀원 모집\n(방법 2) 다른 사람이 등록한 프로젝트에 파트너 신청\n\n■ 문의처\n- 이메일: probono@kfict.or.kr\n- 전화: 02) 2132-2108, 2112, 2114\n※ 지원사항 등 세부내용은 변경될 수 있습니다. \n\n\n"}, {"contest_title": "2024년 광진구 빅데이터 분석 공모전", "contest_host": "광진구", "contest_target_participants": "제한없음", "contest_reception_period": "2024-04-01 ~ 2024-05-03", "contest_decision_period": "비공개", "contest_competition_area": "비공개", "contest_award": "1천만원이하, 1등 상금 : 300만원", "contest_homepage": "http://gwangjin.go.kr/portal/bbs/B0000003/view.do?nttId=6206346&menuNo=200192", "contest_how_to_apply": "비공개", "contest_fee": "비공개", "contest_image": "https://www.wevity.com/upload/contest/20240224005000_512b10b8.jpg", "contest_detail_text": "2024년 광진구 빅데이터 분석 공모전\n\n■ 공모주제 : 광진구 현안 관련 자유주제\n\n■ 응모조건 : 광진구에 관심 있는 전 국민\n※ 개인 또는 팀(대표자 포함 4인 이내)을 구성하여 참가 가능\n\n■ 응모형식 : PDF 형식으로 20매 이내의 분석결과서 및 1매의 요약본 제출\n\n■ 응모방법 : 온라인 접수(광진구 홈페이지 ▶ 온라인정책방 ▶ 제안하기 ▶ 특별공모)\n\n■ 접수기간 : 2024. 4. 1. ~ 2024. 5. 3.\n\n■ 시상계획 : 총 6팀 선정, 구청장 상장과 상금 지급(총 590만원)\n- 대상(1팀) : 300만원\n- 최우수상(2팀) : 100만원\n- 우수상(3팀) : 30만원\n\n■ 문의처 : 서울특별시 광진구청 스마트정보담당관\n- 이 메 일 : jeongho96@gwangjin.go.kr\n- 전화번호 : 02-450-7234\n\n"}, {"contest_title": "24년상반기 SW개발공모전(SW개발아이디어 및 결과물)", "contest_host": "한국스마트정보교육원/한국스마트정보교육", "contest_target_participants": "일반인, 대학생", "contest_reception_period": "2024-01-31 ~ 2024-06-28 ", "contest_decision_period": "비공개", "contest_competition_area": "비공개", "contest_award": "1천만원이하, 1등 상금 : 50만원", "contest_homepage": "http://ksmart.or.kr/?c=5/9&type=notice&mod=view&type=notice&idx=bbs_content_24013000003", "contest_how_to_apply": "비공개", "contest_fee": "비공개", "contest_image": "https://www.wevity.com/upload/contest/20240131173219_1d7ab858.jpg", "contest_detail_text": "24년상반기 SW개발공모전(SW개발아이디어 및 결과물)\n\n■ 공모 주제\n[1] 주제\n 1) 생성형AI 응용 SW (웹,어플 결과물)\n 2) H/W연계 가정 또는 구현한 IOT 응용 SW (웹,어플 결과물)\n 3) 정부,기업 Open API 응용 SW (웹,어플 결과물)\n 4) 기타 응용 SW (웹,어플 결과물)\n\n■ 참가 분류1\n1)아이디어만 제출\n2)아이디어 및 결과물(실행 가능 방법 및 개발문서,DB,소스코드 압축)\n\n■ 참가 분류2 \n 1)개인\n 2)팀\n\n■ 지원 자격\n- 대학 재학생 / 졸업생 개인 또는 팀\n- 일반인 개인 또는 팀\n\n■ 모집 기간\n- 2024년 6월 28일까지 (신청서 접수 시 아이디어 및 결과물 최종 제출)\n\n■ 지원 방법\n- 제출방법(이메일) : sugang@ksmart.or.kr\n제출시 제목 : 24년1회차_참가자명(팀명)__공모전신청서(마감일_24년06월28일)_한국스마트정보교육원_제출일자\n첨부파일 : 1. 공통 :공모전서 2. 결과물 : 압축파일 하나로 제출\n1)아이디어만 제출 또는 2)아이디어 및 결과물 공통 내용\n* 공통 : 프로젝트명 / 사용자(개인,기업,정부) / 목적(적합성,독창성,사업화가능성,서비스확장성,기타) / 기대효과 / 주요기능 / 보완해야 할 점\n2)아이디어 및 결과물 추가 제출\n* 실행 가능 경로(실행방법 소개) / SW개발환경 / 개발문서 / 소스코드 / DB(ERD,백업파일)\n\n■ 평가 기준\n1)필수작성 적합성-목적-10점\n2)필수작성 독창성-목적-10점\n3)필수작성 사업성-기대-10점\n4)필수작성 서비스확장성- 기대-10점\n5)필수작성 강점-10점\n6)필수작성 주요기능-10점\n7)결과물 제출 필수작성 SW개발환경 -10점\n8)결과물 제출 필수작성 개발문서 등 -10점\n9)결과물 제출 필수작성 소스코드 -20점\n10)선택사항 보완해야 할 점\n\n■ 활동 혜택\n- 무료 교육 및 전북IT기업협회 회원사 취업지원\n\n■ 평가 방법 및 평가위원\n- 평가 방법 : 평가 배점 기준으로 평가 함\n- 평가 위원 : 전북IT기업협회 회원사 대표 및 임원진\n\n■ 시상\n- 상장 : 1등(최우수상) / 2등(우수상) / 3등(장려상)\n- 상금(상품) : \n 1등(최우수상 1명 또는 1팀) : 50만원 (상금 또는 상품)\n 2등(우수상 1명 또는 1팀) : 20만원 (상금 또는 상품)\n 3등(장려상 3명 또는 3팀) : 10만원 (상금 또는 상품)\n * 전북IT기업협회 회원사 후원금 추가 지급\n\n■ 문의 사항\n- 063-717-1008\n\n\n"}, {"contest_title": "제2회 청소년 IT 경시대회", "contest_host": "한국정보기술진흥원", "contest_target_participants": "청소년, 어린이, 기타", "contest_reception_period": "2024-02-26 ~ 2024-03-14", "contest_decision_period": "비공개", "contest_competition_area": "비공개", "contest_award": "다양한 혜택, 1등 상금 : ", "contest_homepage": "https://kitpa.org/contest-2nd/", "contest_how_to_apply": "비공개", "contest_fee": "비공개", "contest_image": "https://www.wevity.com/upload/contest/20240224001344_ba6d9b1c.jpg", "contest_detail_text": "제2회 청소년 IT 경시대회\n\n■ 참가대상 : 전국 초.중.고 재학생 또는 이에 준하는 자\n\n■ 접수기간 : 2024년 2월 26일 (월) ~ 3월 10일 (일)\n- 추가접수: 3월 11일 (월) ~ 3월 14일 (목) (추가접수 기간 전형료: 50,000원)\n\n■ 결과발표 : 2024년 3월 25일 (월) 오후 6시 이후\n\n■ 전형료 : 40,000원\n\n■ 문제구성\n- 프로그래밍 언어 부문 \n * 문항 수: 객관식 30문항, 단답형 5문항\n * 시험시간: 총 1시간 30분\n * 배점: 문항 별 상이 (총 500점)\n * 선택 가능한 언어: C, Python\n * 프로그래밍 언어의 문법의 이해도를 테스트하고 코드 디버깅 능력을 확인\n- 알고리즘 부문 \n * 문항 수: 3문항\n * 시험시간: 총 2시간 30분\n * 배점: 문제 당 100점 (총 300점)\n * 사용 가능한 언어: C, C++, Python, Java\n * 주어진 문제를 선호하는 프로그래밍 언어를 이용해 해결\n- 데이터분석 부문 (Python) \n * 문항 수: 객관식 30문항, 단답형 5문항\n * 시험시간: 총 1시간 30분\n * 배점: 문항 별 상이 (총 500점)\n * numpy, pandas, matplotlib 라이브러리를 Python 데이터 분석 코드와 데이터 분석 기술에 대한 이해도 평가\n\n■ 시상 (예정)\n각 부문별(초등부 프로그래밍 언어, 중등부 프로그래밍 언어, 고등부 프로그래밍 언어, 초등부 알고리즘, 중등부 알고리즘, 고등부 알고리즘, 통합부문 데이터분석)으로 시상합니다.\n- 대상 : 1명 (총 7명)\n- 금상 : 2명 (총 14명)\n- 은상 : 3명 (총 21명)\n- 동상 : 상위 5%\n- 장려상 : 상위 20%\n- 단체대상 : 3개 단체\n(개인)대상 수상자 무선 이어폰, USB, 에코백 등 / 은상 이상 수상자 USB, 에코백, 문화상품권 등 / 장려상 이상 수상자 USB, 에코백 등\n(단체)대상 수상 단체 공기청정기\n경품 수상자는 제세공과금 납부 및 개인정보 제공에 동의하셔야 수령 가능합니다. 제세공과금 납부 및 개인정보 제공을 거부할 수 있으며, 이 경우 부득이 수상자의 경품 취득 거부 의사표시로 간주됩니다.\n제세공과금이란, 국가나 지방공공단체에서 부과하는 공적부담금으로 소득세법상 기타소득으로 교정된 5만원 이상의 경품에는 경품 금액의 기타소득세(20%)와 주민세(2%)가 부과됩니다.\n\n■ 문의처 : 한국정보기술진흥원 교육기획본부 대회운영팀 메일 (contest@kitpa.org)\n\n\n"}] \ No newline at end of file +[{"contest_title": "미래융합 인재발굴 소프트웨어 챌린지", "contest_host": "과학기술정보통신부", "contest_target_participants": "일반인, 대학생, 청소년, 기타", "contest_reception_period": "2024-06-04 ~ 2024-06-23", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1천만원이하, 1등 상금 : 300만원", "contest_homepage": "http://www.gsia-sw.kr/01_sub/01_sub.html", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240604001831_cb7d5b65.jpg", "contest_detail_text": ""}, {"contest_title": "LG 유플러스 와이낫 부스터스 시즌3 모집", "contest_host": "LG 유플러스", "contest_target_participants": "제한없음", "contest_reception_period": "2024-06-03 ~ 2024-06-18", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "다양한 혜택, 1등 상금 : ", "contest_homepage": "https://bit.ly/3Kt17F9", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240603234341_56037113.jpg", "contest_detail_text": "LG 유플러스 와이낫 부스터스 시즌3 모집\n\n내 방식대로 본격적으로! 이제 우리가 숏폼의 룰\n\n모여. 앞서. 가는 100일간의 도전 프로젝트\n크리에이터로 태어난 우리가 바로 BOOST US!\n우리가 리드하고, 룰이 되는 진짜 판으로 와!\n\n진짜 판에 들어오고 싶은 크리에이터 누.구.나 환영\n\n■ 모집 기간 : 6월 3일(월) ~ 6월 18일(화)\n\n■ 선발 인원 : 120명\n\n■ 발표 일정 : 6월 21일(금) 홈페이지 발표\n\n■ 지원 방법 : 와이낫 부스터스 홈페이지에서 지원\n- https://bit.ly/3X89e1o\n\n■ 부스터스 시즌3 혜택\n- 참여만 해도 기본 제작지원금 100만원\n- 최대 상금 1,500만원(우수 활동자 대상)\n- 파워 크리에이터의 멘토링 / META 파트너십 강의 프로그램\n- 부스터스 네트워크 지원 (소모임 활동)\n\n■ 문의처\n- boost-us@freebr.co.kr\n\n"}, {"contest_title": "애경 서포터즈 AK Lover Beauty & Life Club 모집 (2024년 하반기)", "contest_host": "애경산업", "contest_target_participants": "일반인, 대학생, 기타", "contest_reception_period": "2024-06-03 ~ 2024-06-19", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "다양한 혜택, 1등 상금 : 30만원", "contest_homepage": "https://bit.ly/4bZ6Eil", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240603005903_f45edbb0.jpg", "contest_detail_text": "애경 서포터즈 AK Lover Beauty & Life Club 모집 (2024년 하반기)\n\n애경 서포터즈는 생활뷰티기업 애경의 다양한 화장품 & 생활용품 신제품과 스테디 제품을 체험해 보고 리뷰를 남기는 서포터즈입니다. 체험리뷰 뿐만 아니라 브랜드와의 커뮤니케이션을 통해 제품 개발 전후 의견을 전달 할 수 있는 다양한 참여 마케팅도 진행되고 있습니다.\n\nAK Lover는 2012년부터 약 12년간 운영되고 있으며, 평소 블로그 / 인스타그램 / 숏폼(릴스, 쇼츠, 틱톡 등) 콘텐츠 제작에 관심이 많고 채널 성장의 기회를 잡고 싶은 20대~40대 누구나 신청 및 지원이 가능합니다.\n\nBeauty & Life Club 지원자 200명을 대상으로 제주항공 20만 포인트, 풀리오 마사지기, 갤럭시 핏 등을 추첨을 통해 지급합니다.\n\n■ 모집 기간: 6/3(월) ~ 6/19(수)\n\n■ 당첨자 발표: 6/24(월)\n\n■ 지원방법 : 온라인 지원\n- https://bit.ly/4bZ6Eil\n\n■ 활동 기간: 2024년 7월 ~ 11월 (약 5개월)\n\n\n"}, {"contest_title": "2024년 제3기 보험연구원 온라인 서포터즈 모집", "contest_host": "보험연구원", "contest_target_participants": "일반인, 대학생", "contest_reception_period": "2024-05-27 ~ 2024-06-16", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "다양한 혜택, 1등 상금 : ", "contest_homepage": "https://kiri.or.kr/community/noticeView.do?bid=120794&tpcd=N01&searchCon=&searchWord=&page=1", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240530152838_50b7e1b1.jpg", "contest_detail_text": "2024년 제3기 보험연구원 온라인 서포터즈 모집\n\n■ 모집대상\n- ‘보험’에 관심이 있는 누구나(일반인, 대학생(휴학생) 등)\n\n■ 모집부문 및 지원자격\n- 보험 영상제작 부문(3명(팀)내외)\n * 보험에 관심이 많고 영상 제작이 가능한 자\n * 유튜브 채널 운영자 우대(구독자수, 조회수 참고)\n- 보험연구원 온라인 홍보 부문(4명(팀)내외)\n * 보험에 관심이 많고 SNS 등을 통하여 온라인 홍보가 가능한 자\n * 블로그, 인스타그램 운영자 우대(이웃수, 팔로워수, 조회수 등 선발에 참고)\n- 팀단위 참가는 최대 2명까지 가능\n\n■ 모집일정\n- 모집기간 : 2024년 5월 27일(월)~ 6월 16일(일)까지\n- 서류심사 : 2024년 6월 17일(월)~ 6월 21일(금)\n- 유선면접(필요시): 2024년 6월 21일(금)\n- 최종합격자(팀) 발표 : 2024년 6월 25(화) / 과정별 합격자(팀)에 한해 개별통보\n- 발대식 : 2024년 7월 1일(월) ※ 합격자 발대식 참석 필수\n- 기타\n * 상황에 따라 일정변경 가능\n * 오프라인 발대식 예정\n * 활동기간 : 2024년 7월 1일(월) ~ 9월 1일(일)(2개월간)\n\n■ 지원방법\n- 지 원 서 : 보험연구원 홈페이지 공지사항 內 지원서를 다운받아 작성 후 제출\n- 제출방법 : 이메일 제출(kirise@kiri.or.kr)\n\n■ 서포터즈 활동내용\n- 보험 영상제작 부문\n * 보험관련 영상, 보험연구원 기관 또는 연구결과소개 영상 제작\n * 활동기간 중 영상 1개(3분 이상) 또는 영상 2개(3분 이하) 이상 제작\n * 영상 주제·내용·퀄리티를 확인하여 온라인 서포터즈 활동 인정여부 결정\n * 유튜브 ‘보험연구원’ 채널에서 서포터즈 영상 참고\n- 보험연구원 온라인 홍보 부문\n * 보험연구원 기관·연구결과, 보험연구원 유튜브 영상 등을 소개하는 컨텐츠를 제작하여 블로그 및 인스타그램에 게시\n * 네이버 또는 인스타그램에 보험연구원 검색 시 나오는 홍보 컨텐츠 참고\n * 활동기간 중 1주 2건 이상 게시\n * 게시물의 주제·내용·퀄리티를 확인하여 온라인 서포터즈 활동 인정여부 결정\n * 네이버 및 인스타그램에서 “보험연구원 서포터즈” 검색 후 활동 블로그 참고\n\n■ 선발기준\n- 보험에 대한 이해도가 높은 열정적이고 적극적인 분\n- 영상, 카드뉴스 등의 콘텐츠 제작이 가능하고, SNS(블로그, 인스타그램) 활동에 흥미와 재능이 있는 분\n- 보험연구원 온라인 서포터즈 활동과 오프라인 행사에 참여하고 충실히 수행할 수 있는 분\n* 이외 보험연구원 자체 심사기준에 따라 선발\n\n■ 서포터즈 활동 혜택\n- 서포터즈 월별 활동비 제공(1명(팀)당 총 20만원)\n- 부문별 활동 우수자(팀) 포상\n구분\n영상제작\n온라인 홍보\n최우수상\n1명(팀) 50만원 + 일본보험문화탐방\n1명(팀) 50만원 + 일본보험문화탐방\n우수상\n1명(팀) 50만원\n1명(팀) 50만원\n장려상\n1명(팀) 30만원\n2명(팀) 30만원\n\n※ 최우수상 수상 시 일본보험문화탐방 실시\n * 항공+숙박+교통 지원\n * 주관사 지정 일정으로 시상식 이후 안내(2박 3일 예정)\n * 각 부문별 해외문화탐방 종료 후 1주 이내 탐방 관련 홍보물 제출 必 \n (영상제작부문: 2분 이상 영상 1개, 온라인 홍보 부문 게시물 2건)\n * 주관사의 사정으로 부득이하게 일본 탐방이 제한되는 경우, 탐방지 변경 또는 상금으로 대체될 수 있음.\n- 서포터즈 활동 수료 인증서 수여(팀 단위 참가의 경우 인증서는 개인별 수여)\n\n■ 최종합격자 발표 및 발대식\n- 최종합격자(팀) 발표 : 2024년 6월 25(화) / 과정별 합격자(팀)에 한해 개별통보\n- 발대식 : 2024년 7월 1일(월) ※ 합격자 발대식 참석 필수\n\n■ 유의사항\n※ 온라인 서포터즈 선발을 위해 제출한 지원서 등의 전부 또는 일부가 위조되었거나 허위인 경우 선발 및 시상이 취소될 수 있음.\n※ 온라인 서포터즈 선발을 위해 제출한 지원서 등의 기재착오 및 활동 중 의사소통 곤란으로 인한 불이익은 지원자 본인의 책임임.\n※ 지원자 현황에 따라 선발인원이 조정될 수 있음.\n※ 제출한 영상 및 홍보물은 순수 창작물이어야 하며, 저작재산권·초상권 등 각종 법령에 위반 되는 사실이 밝혀질 경우 선발과 수상을 취소함.\n※ 제출한 영상 및 홍보물이 저작재산권·초상권 등 법적 문제가 발생하여 생기는 불이익 또는 분쟁은 제출자 본인의 책임임.\n※ 제출한 영상은 보험연구원 유튜브 채널에 1년간 게시될 예정이며, 영상의 저작재산권은 제출자에게 있음.\n * 제출자 본인 동의 후 유튜브에 게시될 예정이며, 동의하지 않는 경우 입상에 제한이 있을 수 있음.\n※ 보험연구원은 유튜브 및 홈페이지에 게시하기 위해 제출한 영상 및 홍보물의 일부를 한시적(1년 이내)으로 가공하여 사용할 수 있음.\n * 제출자 본인 동의 후 제출한 영상 및 홍보물을 가공할 예정이며, 동의하지 않는 경우 입상에 제한이 있을 수 있음.\n※ 보험연구원에 대한 홍보물은 보험연구원 담당자의 확인 후에 게시함.\n※ 제출한 영상 및 홍보물에 따라 시상인원은 조정될 수 있음.\n※ 활동비 및 시상금의 제세공과금은 본인이 부담함.\n※ 온라인 서포터즈 발대식 및 해단식(시상식)은 원칙적으로 참석하여야 하며, 활동 기간 중 오프라인 모임이 있을 수 있음.\n※ 해단식(시상식) 및 일본문화탐방 일정은 추후 확정하여 안내할 예정임.\n\n■ 문의처\n- 보험연구원 D-커뮤니케이션팀 온라인서포터즈 담당\n- E-mail : kirise@kiri.or.kr\n\n\n"}, {"contest_title": "[무료] 금융보안원 금융보안아카데미 제2기 교육생 모집", "contest_host": "금융보안원, 금융보안포럼", "contest_target_participants": "대학생, 기타", "contest_reception_period": "2024-05-27 ~ 2024-06-17", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1,200만원, 1등 상금 : ", "contest_homepage": "https://www.fsec.or.kr/bbs/detail?menuNo=66&bbsNo=11489", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240528141440_d60179f7.jpg", "contest_detail_text": ""}, {"contest_title": "[하나금융그룹] 하나 디지털 파워온 프로젝트 3기 모집", "contest_host": "하나금융그룹/아이들과미래재", "contest_target_participants": "대학생, 기타", "contest_reception_period": "2024-05-27 ~ 2024-06-23", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "다양한 혜택, 1등 상금 : ", "contest_homepage": "https://hana-digital.co.kr/", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240527002326_6ce07a69.jpg", "contest_detail_text": ""}, {"contest_title": "제1회 헤럴드 디자인 공모전", "contest_host": "헤럴드", "contest_target_participants": "제한없음", "contest_reception_period": "2024-05-23 ~ 2024-07-10", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1천만원이하, 1등 상금 : 500만원", "contest_homepage": "https://www.heraldcontest.com/", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240524005132_cdee3c40.png", "contest_detail_text": "제1회 헤럴드 디자인 공모전\n\n(부제: 헤럴드 에코 이모티콘 공모전)\n\n지구온난화와 기상이변 등 세계적으로 환경문제가 대두되고 있는 가운데 환경과 디자인을 중심으로 건강한 미래를 만들어나가는 미디어그룹 헤럴드가 ‘에코 이모티콘(가칭 에코티콘)’을 주제로 제1회 헤럴드 디자인 공모전을 진행합니다.\n이번 공모전은 환경문제에 대한 인식을 높이고 환경보호와 지속 가능한 삶에 대한 메시지를 전달하는 것을 목적으로 합니다. 지속 가능한 환경에 대한 인식을 높이는데 기여할 수 있는 이번 공모전에 많은 참여 부탁 드립니다. \n\n■ 제목 : 헤럴드 에코티콘 공모전\n\n■ 접수기간 : 2024. 5. 23(목) ~ 7.10(수)\n\n■ 공모자격 : 대한민국 국민 누구나 (※ 개인 또는 3인 이내 구성 팀)\n\n■ 공모분야 : 환경 관련 이모티콘 제작\n\n■ 공모주제 : 에너지, 재활용, 생물다양성 등 지구환경 보호를 위한 모든 주제 \n\n■ 시상규모 : 990만원 \n- 대상 1명(팀)(500만원)\n- 우수상 4명(팀)(각 100만원)\n- 참가상 30명(각 3만원 상당의 네이버페이포인트)\n\n■ 심사절차&일정\n- 공모 접수 : 2024년 5월 23일(목) ~ 7월 10일(수)\n- 1차 스케치 심사 : 2024년 7월 22일(월) ~ 7월 26일(금)\n- 2차 본선 진출자 발표 : 2024년 7월 30일(화) 예정\n- 2차 프리젠테이션 심사 : 2024년 8월 14일(수) 예정\n- 최종 수상자 발표 : 2024년 8월 20일(화) 예정\n- 시상식 : 2024년 10월 8일(화) 디자인포럼\n※ 심사 절차 및 일정은 대ㆍ내외 사정으로 변동될 수 있음.\n\n■ 심사방법\n- 사전심사 : 출품 규격 및 공모 적합성 여부 검토\n- 1차 스케치 심사 : 주제 적합성(40), 확장 가능성(30), 창의성(30)\n- 2차 프리젠테이션 심사 : 주제 적합성(20), 대중성(30), 완성도(50)\n※ 출품 규격에 맞지 않는 출품작은 탈락 또는 감점당할 수 있음.\n※ 동점자 발생 시 심사위원 논의 거쳐 결정\n※ 출품작 수준, 출품 수를 고려하여 시상 작품 수가 조정되거나, 선정되지 않을 수 있음. \n\n■ 제출서류\n- 참가신청서 1부\n- 개인정보 수집ㆍ이용 동의서 1부 \n- 작품활용동의서 1부 \n\n■ 제출사항\n- 작품 규격 : 360px X 360px / 72dpi / RGB / PNG와 GIF 로 구성된 이모티콘 \n (기본형 PNG 1종 1장과 캐릭터를 활용한 4장의 GIF 모션형 자유 장면 & 2차 프리젠테이션 진출 시 제출하게 될 총 16장 이모티콘들의 스토리보드)\n※ 2차 프리젠테이션 진출 시엔 총 16장의 GIF 모션형 자유 장면과 캐릭터 원본파일 필요\n\n■ 제출방법\n- 공모전 운영 페이지에서 응모 \n- 홈페이지 : https://www.heraldcontest.com \n\n■ 유의사항\n- 1인당 응모 건수 제한은 없으나, 수상작은 1인 1개(고득점)로 한정\n- 심사 결과에 따라 수상작이 없을 경우 시상 내역 변경 가능\n- 공모에 출품한 작품(미입상작 포함)과 신청서는 일절 반환하지 않음. 수상 작품의 저작권은 ㈜헤럴드 에 있으며 향후 10년간 홍보, 배포, 전시, 2차 가공(원작자 이름 포함) 등의 목적으로 무상으로 사용될 수 있음.\n- 출품작은 본인 순수 창작물이여야 하며, 타 공모전에 발표되지 않았거나 상품 등록이 되어있지 않은 독창적 작품이여야 함.\n- 타인의 저작권 등 기타 권리를 침해하여 문제가 제기될 경우 응모자 본인에게 모든 책임이 있음.\n- 추후 타 공모전에 당선되었거나 표절 등의 결격사유 발생 시 입상이 취소되며 시상금은 전액 환수조치 함.\n- 공모규격 및 접수에 미달된 작품은 심사에서 제외 될 수 있음.\n- 심사위원회 심의 후 입상 작품이 없을 경우 시상하지 않을 수 있음.\n- 응모자는 응모와 동시에 추후 입상 시 공모전 요강에 기재되어 있는 저작물 이용 조건의 범위 안에서 저작물 이용을 허락한 것으로 보고, 입상자의 저작재산권에 대한 이용료는 입상에 따른 시상금으로 대체\n- 상금은 제세공과금을 제외한 금액을 지급. 팀 수상 시 대표자에게 전달\n\n\n\n"}, {"contest_title": "2024 우지커피 영상공모전", "contest_host": "우지커피", "contest_target_participants": "제한없음", "contest_reception_period": "2024-05-01 ~ 2024-06-23", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "3천만원~1천만원, 1등 상금 : 1,000만원", "contest_homepage": "https://www.instagram.com/oozy.coffee_official/p/C6f4V5dypry/", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240523002642_2a65dc28.jpg", "contest_detail_text": "2024 우지커피 영상공모전\n\n■ 공모개요\n- 우지커피만의 차별화된 장점을 표현하는 모든 주제를 영상으로 만들어 주세요\n\n■ 공모주제\n- 우지커피만의 차별화된 장점을 표현하는 모든 주제\n 1) 3가지 가치 활용한 주제 \n • 합리적인 가격: 합리적인 가격에 퀄리티 있는 다양한 메뉴들을 즐길 수 있는 우지커피\n • 공간적인 인테리어: 시각적으로 항상 편안한 느낌으로 언제나 오고 싶은 우지커피\n • 일상적인 동행: 고객 가맹점주 임직원까지 언제나 함께한다는 마음으로 최선을 다하는 우지커피\n 2) 우지커피 슬로건 및 로고 심볼을 활용한 주제 \n • OOZY IS ALWAYS TOGETHER IN YOUR EVERYDAY LIFE\n • 우지커피는 당신의 일상에 언제나 함께합니다. \n 3) 우지커피 브랜드를 표현할 수 있는 모든 주제\n* 우지커피 로고 파일 다운로드는 첨부파일 참고\n\n■ 기간 및 일정\n- 공모기간: 2024년 5월 1일(수) ~ 6월 23일(일)\n- 수상작 발표: 2024년 6월 26일(수)\n\n■ 지원자격\n- 전 국민 누구나 자유롭게 참여 가능\n\n■ 접수방법\n- 담당자 전자메일로 발송( health0301@fiamg.co.kr )\n ※ 해시태그 #우지커피 #우지커피공모전 #우지커피광고 태그\n 본인 인스타그램 혹은 유튜브 채널에 업로드 후 개인정보 수집, 저작물 사용 동의서, 원본 파일과 함께 담당자 메일로 발송\n * 저작물 사용동의서 파일 다운로드는 첨부파일 참고\n\n■ 출품규격\n- CF 홍보 영상 (30초 내외)\n- 바이럴 영상(1분~3분)\n- 영상 AVI,MP4,MOV 중 택 1\n\n■ 심사기준\n- 작품성, 완성도, 창의성, 대중성, 브랜드 적합성, 마케팅 활용도, 조회수 등 종합적인 평가\n\n■ 시상내역\n- 대상(1명) 상금 1,000만원 \n- 최우수상(1명) 상금 500만원\n- 우수상(3명) 상금 100만원 \n- 장려상(20명) 우지커피 5만원 교환권\n\n■ 유의사항\n- 복수의 작품응모는 가능하나, 중복수상은 불가함.\n- 본 영상 외, 메이킹영상(NG영상, 팀 홍보영상 등) 또한 자유롭게 출품할 수 있음. \n- 접수된 사항은 수정 및 취소 불가하며, 접수된 서류는 일체 반환하지 않음. \n 다만, 입상하지 않은 응모작에 대해서는 공모 종료일로부터 3개월 이내에 모두 폐기함.\n- 수상작의 지적재산권에대한 이용료는 시상금으로 대체하며, 상금에 대한 제세공과금은 수상자가 부담함.\n- 수상작과 관련하여 저작권, 초상권 등 법적 분쟁 발생 시 응모자 본인에게 책임이 있음.\n- 수상작은 작품의 원본 파일을 반드시 제출 해야함.\n- 수상자는 공모전에 출품한 작품의 모든 저작권, 소유권이 “우지커피”에 귀속됨을 확인함 (출품작 및 수상작은 추후 주최사의 홍보 콘텐츠로 활용될 수 있음. )\n- 영상제작 시 저작권 및 초상권 문제가 없는(혹은 저작권자의 사용승인을 받은) 소스를 사용하시길 권장함.\n 추후 관련 문제가 발생할 경우, 책임은 출품자 본인에게 있음. \n- 참가신청 기재 내용에 허위사실이 발견되거나 기 입상된 작품, 표적작품으로 판명될 경우 심사에서 제외되며, 시상 후에라도 수상자 선정 취소(시상금 전액 반납) 처리함\n- “수상자”는 AI를 통하여 작품을 제작할 경우 제출시에 AI로 제작한 항목(카피, 이미지, 제작, 영상등)을 공유하여야 하고, 이를 고지하지 않거나 차후 저작권에 문제가 생길 경우 “수상자”는 “회사”를 면책하고, 분쟁에 따른 모든 민형사상 책임질 것을 확인함.\n- 적합한 작품이 없을 경우 수상자를 선정하지 않거나, 수상금에 변동이 있을 수 있음.\n- 심사내용은 비공개를 원칙으로 함\n- 상기 내용은 주최사 및 사무국 사정으로 인해 변경될 수 있음. \n\n■ 매장 촬영 희망 시 촬영 협조 매장\n- 촬영 가능시간 : 11시~14시 제외한 나머지 영업 시간 내에서 촬영 가능(3일 전 예약)_메뉴는 직접 구입 후 촬영 \n- 1.문정테라타워점\n- 2.대학로점\n- 3.석촌점\n- 4.일산식사점\n- 5.시흥MTV점\n- 6.운정가람마을점\n- 7.인천검암점\n- 8.원주무실점\n- 9.춘천터미널점\n\n■ 문의\n- 이메일 문의: health0301@fiamg.co.kr(문의사항은 이메일로만 주세요!!!) \n\n\n"}, {"contest_title": "2024 CJ대한통운 미래기술 챌린지", "contest_host": "CJ대한통운", "contest_target_participants": "일반인, 대학생, 기타", "contest_reception_period": "2024-05-20 ~ 2024-06-16", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "3천만원~1천만원, 1등 상금 : ", "contest_homepage": "https://www.cjlogistics.com/ko/main", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240521170223_36c0f3c6.jpg", "contest_detail_text": "2024 CJ대한통운 미래기술 챌린지\n\nCJ대한통운 TES물류기술연구소와 함께할 인재를 찾습니다. \nTechnology 로봇이 사람처럼 일합니다.\nEngineering Data로 미래를 봅니다.\nSystem & Solution 시스템이 사람을 리딩합니다.\n\n■ 참가 대상\n- 로봇/설비 시뮬레이션, 물류센터/배송라우팅 최적화 모바일 어플리케이션 개발 분야에 관심과 경험이 있으며 2년 이내 입사 가능한 학사/석사 졸업예정자 및 기졸업자 (개인 또는 팀원 최대 3명까지) \n\n■ 참가 신청\n- CJ대한통운 대표 홈페이지 상단 배너 링크 \n- 신청 기간 : 5/20(월) ~ 6/16(일)\n- 참가 신청 바로가기 ▶ https://www.cjlogistics.com/ko/main\n\n■ 시상내역 및 채용특전 \n- 총 상금 2,100만원 \n- 본선 진출 후 입사지원 시 서류전형/적성검사 면제 \n\n■ 문의처 : apply.cjl@cj.net \n\n※ 대회 홍보영상 : https://youtu.be/FV32FO7yBRs?si=ZI7i1NhxfNB-fDdf\n\n\n\n"}, {"contest_title": "2024 ACC 민주·인권·평화 웹툰 공모전", "contest_host": "국립아시아문화전당", "contest_target_participants": "제한없음", "contest_reception_period": "2024-04-01 ~ 2024-06-16", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "3천만원~1천만원, 1등 상금 : 500만원", "contest_homepage": "https://www.acc.go.kr/main/board/board.do?PID=0701&boardID=NOTICE&category=1&action=Read&idx=2188", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240408002447_4d062079.jpg", "contest_detail_text": "2024 ACC 민주·인권·평화 웹툰 공모전\n\n1980년 5·18민주화운동의 최후 항쟁지 옛 전남도청 일원에 위치한 국립아시아문화전당은 민주·인권·평화의 가치를 문화예술로 승화하여 세계인과 공유해 오고 있습니다. 민주·인권·평화의 가치 확산 및 5·18민주화운동을 기념하고자 을 진행합니다. 여러분들의 많은 관심과 참여 바랍니다.\n\n■ 공모주제\n1) 국내 민주, 인권, 평화와 관련된 역사적 사건 소재로 긍정적 가치 전달\n2) 5·18민주화운동 최후의 항쟁지로써의 ACC의 의미와 소개\n※ 2개 주제 중 선택하여 응모\n\n■ 공모기간\n- 2024. 4. 1.(월) ~ 2024. 6. 16.(일) ※ 접수기간 동일\n\n■ 응모자격\n- 누구나(개인 또는 팀)\n※ 팀은 5인 이내 참여 가능, 1인(팀)당 출품작 수 제한 없으나, 중복 시상은 불가함\n\n■ 응모부분\n1) (청년·일반부)만 19세 이상 누구나 ※2005년 6월 16일 이전(포함) 출생자\n2) (유아·어린이·청소년부)만 18세 이하 누구나 ※2005년 6월 17일 이후(포함) 출생자\n※ 공모기간 종료일(2024.6.16.) 기준이며, 팀의 경우 팀내 최연장자 나이 기준 적용\n\n■ 작품형식\n1) < 2023 ACC 민주·인권·평화 캐릭터 공모전 > 대상 수상 캐릭터를 활용한 웹툰\n ※ 청년·일반부 대상 [민주동이, 인권동이, 평화동이]/유아·어린이·청소년부 대상 [인절미, 오일이, 일팔이] 활용, 자세한 캐릭터 확인은 [참고] 또는 ACC 누리집 확인\n2 )응모 참여자(팀)의 순수 창작 웹툰\n ※ 2개 형식 중 선택하여 응모\n\n■ 작품 규격\n- 스트롤 뷰\n * 웹 게재용 스크롤 형식\n * 10스크롤 이상 단편 웹툰 완성작(완결)\n * 가로 720px, 세로 제한 없음\n * 해상도 300dpi,\n * jpg, png 파일 제출\n- 컷 뷰\n * 웹 게재용 정사각형 비율\n * 10컷 이상 단편 웹툰 완성작(완결)\n * 가로×세로, 720px×720px\n * 해상도 300dpi\n * jpg, png 파일 제출\n※ 수기작품(손그림 등) 및 디지털 작업 모두 가능, 수기작품(손그림)의 경우 스캔하여 제출, 수상작은 추후 원본 파일 제출(AI, PSD, 손그림 원본 등)\n※ 수상하는 작품(디지털 작업 등)은 레이어 분리된 파일 제출(AI, PSD 등)\n\n■ 시상내역: 총 20작품 선정 / 총 상금 18백만원\n- 청년·일반부(10)\n * 대 상 : 1명(팀) / 문체부장관상 / 500만원 \n * 최우수상 : 1명(팀) / 전당장상 / 200만원 \n * 우 수 상 : 3명(팀) / 전당장상 / 각 100만원\n * 장 려 상 : 5명(팀) / 전당장상 / 각 50만원\n- 유아·어린이·청소년부(10)\n * 대 상 : 1명(팀) / 문체부장관상 / 200만원\n * 최우수상 : 1명(팀) / 전당장상 / 100만원\n * 우 수 상 : 3명(팀) / 전당장상 / 각 50만원\n * 장 려 상 : 5명(팀) / 전당장상 / 각 20만원\n\n■ 문의\n- 국립아시아문화전당 교류홍보과 062-601-4264 / 52songa@korea.kr\n※ 평일10:00~17:00(휴게시간 11:30~13:00 제외, 주말 및 공휴일 문의 불가) 이외 시간은 메일로 문의\n\n\n"}, {"contest_title": "제임스 다이슨 어워드 2024 (James Dyson Award 2024)", "contest_host": "제임스 다이슨 재단", "contest_target_participants": "일반인, 대학생, 기타", "contest_reception_period": "2024-04-01 ~ 2024-07-17", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "5천만원이상, 1등 상금 : 30,000 파운드(한화 약 5,000만원) ", "contest_homepage": "https://www.jamesdysonaward.org/ko-KR/", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240404165713_b7f94582.jpg", "contest_detail_text": "제임스 다이슨 어워드 2024 (James Dyson Award 2024)\n\n[다이슨] 국제 엔지니어링 및 디자인 공모전 제임스 다이슨 어워드 2024 참가자 모집 (~2024.07.17)\n\n■ 주최사 / 활동명\n- 제임스 다이슨 재단/ 국제 엔지니어링 및 디자인 공모전 제임스 다이슨 어워드 2024\n\n■ 활동내용\n일상 속 문제를 해결하는 차세대 엔지니어를 위한 무대!\n국내에서는 9회를 맞이한 제임스 다이슨 어워드(James Dyson Award)는 차세대 엔지니어들에게 영감을 주고 계속해서 연구, 개발을 할 수 있도록 장려하기 위해 글로벌 기술 기업 다이슨에서 매년 진행하는 국제 엔지니어링 및 디자인 공모전입니다. 일상에서 마주하는 크고 작은 문제에 대해 ‘이렇게 해결하면 어떨까?’하고 아이디어가 마구 샘 솟는 분들, 톡톡 튀는 아이디어를 실현하는 과정에 흥미를 느끼는 분들, \n주저하지 마시고 제임스 다이슨 어워드 2024에 지원하세요. 여러분들의 창의적인 아이디어를 기다립니다!\n\n■ 참가 자격\n- 엔지니어링 혹은 디자인 분야 전공 대학(원)생 또는 최근 4년 이내 졸업자 \n- 개인 또는 팀으로 지원 가능 \n* 팀을 구성하여 출품하는 경우, 팀장은 엔지니어링 혹은 디자인 전공자여야 하나 팀원은 엔지니어링 혹은 디자인 관련 수업을 한 학기 이상 수강한 이력이 있는 비전공자여도 지원 가능 \n\n■ 참가 비용 유/무 : 무\n\n■ 모집 기간 : ~2024년 7월 17일 23시59분까지(한국 시간) \n\n■ 시상 혜택 \n* 참가자 대상참가 증서 제공 \n* 언론 및 각종 미디어 노출 기회 제공 \n- 국제전 우승작: 30,000 파운드(한화 약 5,000만원) \n- 지속가능성 부문 우승작: 30,000 파운드(한화 약 5,000만원) \n- 국제전 우승 후보작: 5,000 파운드(한화 약 800만원) \n- 국내전 우승작: 5,000 파운드(한화 약 800만원) \n\n■ 지원 및 심사 일정\n- 공모전 지원: 2024년 3월 6일(수) ~ 7월 17일(수) 23시 59분까지(KST) \n- 국내전 현장 심사일: 2024년 7월 31일 (수) \n- 국내전 우승작 및 입상작 발표: 2024년 9월 11일(수) \n- 국제전 우승 후보작 발표: 2024년 10월 16일(수) \n- 국제전 최종 및 지속가능성 부문 우승작 발표: 2024년 11월 13일(수) \n\n■ 지원 방법\n- 공모전 지원은 제임스 다이슨 어워드 공식 홈페이지 통해 접수 및 지원 가능\nhttps://www.jamesdysonaward.org/ko-KR/\n\n■ 참고사항\n- 시제품 출품은 필수 사항이나, 제품의 작동 원리와 개발 방법에 대한 명확한 설명을 담은 이미지, 영상 등 다양한 자료를 활용한 스케치 혹은 도면으로 대체하여 제출 가능 \n- 시제품 출품은 현장 심사 대상자에 한해 제출 \n\n■ 문의처\n- 앨리슨DysonKR@allisonworldwide.com\n\n일상의 문제를 해결하는 여러분의 창의적인 아이디어를 기다립니다.\n공모전에 대한 보다 자세한 내용은 제임스 다이슨 어워드 공식 홈페이지에서 확인가능합니다. (하단 링크 참조)\nhttps://www.jamesdysonaward.org/ko-KR/\n\n\n"}, {"contest_title": "2024년 외교 공공데이터 활용 경진대회", "contest_host": "외교부/한국국제협력단,한국국제교류재단,한·아프리카재", "contest_target_participants": "제한없음", "contest_reception_period": "2024-05-16 ~ 2024-07-15", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1천만원이하, 1등 상금 : 300만원", "contest_homepage": "https://www.mofa.go.kr/www/brd/m_4075/view.do?seq=369087&page=1", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240517041407_b8aeae63.jpg", "contest_detail_text": "2024년 외교 공공데이터 활용 경진대회\n\n외교부(산하기관 포함, 이하 외교부)는 외교 공공데이터를 활용한 민간의 국가·사회 현안 해결 아이디어 발굴을 위해 <2024년 외교 공공데이터 활용 경진대회>를 실시합니다. 국민 여러분의 많은 관심과 참여를 바랍니다.\n\n■ 공모 부문 : 공공데이터 활용 아이디어 기획\n- 공공데이터를 활용한 창의적 아이디어 보유 및 제안\n- 공공데이터포털에 개방되어 있는 외교부 공공데이터* 및 기타 외교 관련 데이터(해외 오픈데이터 등) 활용 가능 \n * 외교부, 한국국제협력단, 한국국제교류재단, 한·아프리카재단 개방데이터\n\n■ 추진 일정\n- 공모 접수 : ’24.5.16. ~ 7.15. 18:00 까지\n- 서류 및 발표평가 : 7월 중순 ~ 8월 초\n- 결과 발표 및 시상 : 8월 중 ※ 추진일정은 진행상황에 따라 변경될 수 있으며 심사결과는 개별통보\n\n■ 주최/주관 : 외교부 / 한국국제협력단, 한국국제교류재단, 한·아프리카재단\n\n■ 참가 자격 : 공공데이터를 활용하여 창업 아이디어를 기획한 모든 (예비)창업자 누구나\n※ 개인 또는 팀(최대 4인)참여, 참가자(팀) 당 1개 공모 가능\n※ 동일한 아이템으로 기존 창업경진대회에 입상한 경우 또는 타인의 아이템을 도용해 저작권을 침해한 경우 입상 취소\n\n■ 공모 방법\n(서류) 참가신청서, 아이디어 기획서, 개인정보 수집․이용 동의서 ※ 제출 양식은 붙임 참조\n(접수) 국민생각함 또는 이메일 (opendata@kf.or.kr)을 이용한 직접접수 \n ※ 국민생각함 바로가기\n(발표) 발표원고(자유양식, 프리젠테이션 파일), 발표대상자 해당\n\n■ 심사방법 : 서류 및 발표 심사\n(서류평가) 해당 부분 10건 이내 선정, 합격자 개별 통보\n(발표평가) 별도 개별 안내\n\n■ 심사기준 : 공공데이터 활용, 구체성, 독창성, 발전가능성, 사회적가치 창출 등 고려\n※ 국가중점데이터 활용(활용도 및 적절성)에 대한 가산점 2점 부여\n\n■ 시상 내역 : 총 5명(팀) 선발, 상장(장관상, 이사장상) 및 상금 수여\n- 최우수상(장관상/1점): 상금 300만원 ※ 범정부 본선 추천\n- 우수상(장관상/1점): 상금 200만원 \n- 장려상(주관기관* 이사장상/3점): 상금 각 100만원 * 한국국제협력단, 한국국제교류재단, 한·아프리카재단\n※ 자세한 내용은 공모전사이트 참고\n\n■ 유의 및 안내사항\n- 수상 기준에 미달하는 경우, 시상규모를 축소 또는 변경할 수 있음\n- 공모내역은 향후 외교 공공데이터 정책에 활용될 수 있음\n- 타인의 지식재산권을 침해하거나 유사대회에서 중복 수상한 경우 입상 취소\n- 경진대회에 제출된 자료의 저작권은 출품한 저작자에게 있으며, 주최기관 및 주관기관은 사용권을 가짐\n\n■ 문의\n- 02-2100-7159, 064-804-1087\n- 이메일 : opendata@kf.or.kr\n\n"}, {"contest_title": "제12회 K-해커톤", "contest_host": "과학기술정보통신부/정보통신산업진흥원, SWKORE", "contest_target_participants": "대학생", "contest_reception_period": "2024-05-13 ~ 2024-06-14", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "3천만원~1천만원, 1등 상금 : 300만원", "contest_homepage": "http://www.k-hackathon.com/", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240409175404_3e408620.jpg", "contest_detail_text": "제12회 K-해커톤\n\n■ 공모 주제\n- 전국적인 사회문제와 공익 문제 및 지역의 특수성을 반영한 자유 주제\n\n■ 지원 자격 \n- 1분과 : 애플리케이션 솔루션(SW, AI, IoT)\n- 2분과 : 메타버스 플랫폼 활용 솔루션(VR/AR, 메타버스 등)\n※ 2~5인 이하 팀 자격으로 참가 가능\n\n■ 모집 기간\n- 2024년 5월 13일(월) ~ 6월 14일(금)\n\n■ 대회 일정\n- 예선 : 2024년 6월 17일(월) ~ 7월4일(목) 온라인\n- 본선 : 8월 중 / 오프라인\n- 결선 : 10월 중 수도권 / 오프라인\n※ 결선 수상팀 혜택!\n 1. COSS 대학 연합 내 교육/창업기간/인프라 지원\n 2. 특허 및 창업 교육지원\n 3. 오프라인 전시회 개최 \n\n■ 공모전 지원방법\n- 온라인 참가 신청 및 제출, 관련 내용은 대회 홈페이지 참고\n\n■ 시상내역 \n- 1분과\n 대상(1) : 300만원 + 과학기술정보통신부 장관상\n 최우수상(1) : 200만원 + 정보통신산업진흥원 원장상\n 우수상(2) : 각 100만원 + 학회장상\n 장려상(3) : 각 50만원 + 소프트웨어교육혁신센터 이사장상\n- 2분과\n 대상(1) : 300만원 + 과학기술정보통신부 장관상\n 최우수상(1) : 200만원 + 정보통신산업진흥원 원장상\n 우수상(2) : 각 100만원 + 학회장상\n 장려상(3) : 각 50만원 + 소프트웨어교육혁신센터 이사장상\n\n■ 문의 사항\n- T. 070-7525-0500\n- E. swkorea@swkorea.org\n\n\n"}, {"contest_title": "코드게이트 AI 아이디어랩", "contest_host": "사단법인코드게이트보안포럼, 금융보안원/사단법인코드게이트보안포럼, 금융보안", "contest_target_participants": "일반인, 대학생, 청소년, 기타", "contest_reception_period": "2024-04-29 ~ 2024-06-0", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "3천만원~1천만원, 1등 상금 : 1,000만원", "contest_homepage": "https://www.코드게이트ai아이디어랩.com", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240513154832_cd7ed9e4.jpg", "contest_detail_text": "코드게이트 AI 아이디어랩\n\n■ 참가 자격\n- 제한 없음. AI에 관심 있는 누구나\n * 접수일 기준 만 14세 미만 미성년자는 법정대리인의 동의 필요\n- 개인 또는 팀 구성(4인 이내), 자유 참가\n\n■ 공모 주제\n- 보안 문제 해결을 위한 AI 활용 기술 또는 서비스 아이디어\n- 산업, 문화, 경제 등 사회 전반적인 보안 문제\n- 현재 또는 미래에 발생 가능성이 있는 문제\n- 공격·방어 기술 모두 가능\n (예시1) 보이스피싱, 딥페이크·딥보이스, 전자상거래, SNS 투자사기, 디지털 범죄, 웜GPT 등 새로운 유형의 사이버 위협에 대하여 AI를 활용한 예방 대책\n (예시2) AI 시스템 보안을 위한 AI 활용 방안\n\n■ 시상 내역 : 총상금 2,000만원\n- 1위 : 1팀 / 1,000만원 / 금융보안원장상 / 상장 및 상금\n- 2위 : 1팀 / 500만원 / 금융보안원장상 / 상장 및 상금\n- 3위 : 1팀 / 300만원 / 금융보안원장상 / 상장 및 상금\n- 인기상 : 2팀 / 각 100만원 / 금융보안원장상 / 상장 및 상금\n * 인기상은 본선 진출작을 대상으로 공모전의 시상식이 개최되는 8.30(금) 「코드게이트 2024」 행사 당일 일반참가자의 투표로 선정하며, 당일 폐회식에서 별도 시상\n ** 인기상 선정을 위해 본선 진출작은 「코드게이트 2024」에 참석한 일반참가자에게 공개됨\n\n■ 공모 일정\n- (1차)서류제출 및 기획서 평가 : 4.29.(월)~6.7.(금) 17:00 마감 / 결과발표 6.17.(월)\n- (2차)예선 : 6.18.(화)~7.19.(금) 17:00 마감 / 결과발표 7.29.(월)\n- (3차)본선 : 8.29.(목) / 결과발표 동일\n- 시상식 : 8.30.(금)\n ※ 일정은 신청 접수 현황에 따라 다소 변동될 수 있음\n ※ 신청 마감일에는 접수 지연이 발생할 수 있으므로 사전에 접수 권장\n\n■ 제출 형식 및 심사 방법 \n[심사방식]\n ① (1차) 서류 및 기획서 평가 ∣ 온라인 응모페이지 제출\n - 기획서 5pg 이내, 지정 양식의 항목에 따라 작성\n - 기획서는 PDF로 저장하여 제출\n - 제출 서류 : 참가자서약서, 기획서\n - 본선 진출 팀의 3배수(30팀) 선발\n ※ 1차는 블라인드 평가로 진행\n ※ 기획서 양식에 이름, 팀명, 소속 등 참가자를 식별할 수 있는 정보는 기재하지 말 것\n ② (2차) 예선심사 1차 기획서 통과 팀에 한함 ∣ 온라인 응모페이지 제출\n - 1차 기획서 구체화 영상 제출\n - 5분 이내 영상, 응모사이트에 업로드 (mp4, 최대 500mb)\n - 최종 본선 진출 10팀 선발\n ※ 기획서 구체화 영상의 시간 길이를 반드시 준수해야 하며, 미준수 시 탈락 처리\n ③ (3차) 본선 발표∣서울, 코엑스 그랜드볼룸 101호\n - 오프라인 PT 발표평가\n - 발표 15분, Q&A 10분\n[심사위원]\n - 정보보호 및 AI 분야 전문가 7인\n[심사기준]\n - 창의적이고 우수한 아이디어 선발을 위한 종합평가\n\n■ 접수 방법\n- 「코드게이트 AI 아이디어랩 공모전」응모페이지를 통한 온라인 신청 \n ※ 신청 접수 시 ‘공모전 참가 확인서’는 별도 발급하지 않음\n\n■ 문의 사항\n- ˹코드게이트 AI 아이디어랩 공모전˼ 운영사무국\n- 연락처: 031-212-2027\n- 이메일: idea.codegate@gmail.com\n\n\n"}, {"contest_title": "2024년 성불평등 개선을 위한 성평등 영상콘텐츠 공모전", "contest_host": "제주특별자치도/제주영상·문화산업진흥", "contest_target_participants": "일반인, 대학생, 청소년, 기타", "contest_reception_period": "2024-07-01 ~ 2024-07-19", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1천만원이하, 1등 상금 : 200만원", "contest_homepage": "https://ofjeju.kr/communication/notifications.htm?qType=title&act=view&seq=2072112", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240513014830_cd88bedb.jpg", "contest_detail_text": ""}, {"contest_title": "K-디지털 챌린지 NET 챌린지 캠프 시즌 11 (네트워크 응용분야 아이디어 공모전)", "contest_host": "과학기술정보통신부/한국지능정보사회진흥원, KOREN연구협력포", "contest_target_participants": "대학생", "contest_reception_period": "2024-06-05 ~ 2024-06-26", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "3천만원~1천만원, 1등 상금 : 500만원", "contest_homepage": "https://www.koren.kr/kor/Alram/contyView.asp?s=34&page=1", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240524003509_f5acbc87.jpg", "contest_detail_text": "K-디지털 챌린지 NET 챌린지 캠프 시즌 11 (네트워크 응용분야 아이디어 공모전)\n\n■ 접수기간 : 2024.06.05 09:00 ~ 2024.06.26 18:00\n\n■ 공모분야\n- 네트워크 응용분야의 상용화가 가능한 ICT 신기술 및 서비스 관련 아이디어\n※ KOREN(차세대 네트워크 선도 연구시험망)이란?\n산업체·학계·연구기관 등이 연구·개발을 목적으로 무료로 이용할 수 있는 광대역, 고품질의 연구시험망으로, 미래 네트워크 관련 기술의 시험검증과 첨단 ICT 국제 공동 협력 기반을 조성하는 비영리 선도시험 네트워크 환경\n\n■ 접수방법\n- 본 공모전 페이지를 통한 온라인 접수\n- 제출서류 : 참가신청서 및 기획안 1부\n * 둘 중 하나라도 미제출 시 접수되지 않사오니, 유의하여 주시기 바랍니다.\n- 접수기간 : 2024. 6. 5(수), 9:00 ~ 6.26(수), 18:00까지 (3주간)\n * 자세한 사항은 첨부된 안내 자료를 참고해주십시오. \n * 선정된 팀은 오는 7월 5일(금), 본 공고 하단의 [공모전 결과보기] 페이지에서 결과를 확인하실 수 있습니다.\n\n■ 추진일정\n- 온라인 설명회 : 6. 4.(화) / KOREN Youtube 게시\n- 공모전 접수 및 선정 : 6. 5.(수) ~ 6. 26.(수) / 7.5.(금) 결과 발표\n- 아이디어 개발지원 : 8월 ~ 9월\n- 최종평가(9월말)\n- 시상식(11월)\n * 주요일정은 사전 공지 후 변경될 수 있습니다.\n\n■ 공모전 시상내역\n- 대상 : 과기부장관상 / 1팀 / 500만원\n- 금상 : NIA원장상 / 1팀 / 300만원\n- 은상 : KOREN 협력기관상 / 4팀 / 각 100만원\n- 특별상 : 통신사상(KT, SKB, LGU+) / 3팀\n* 특별상(통신사상)은 대상/금상/은상과 중복수상 가능\n* 시상내역 및 일정은 내부 사정에 따라 변경될 수 있음\n* (유의사항) 아이디어에 대한 저작권 침해 등 법적인 책임은 응모자 본인에게 있으며, 타 공모전에 출품한 작품이거나 표절이 발생할 경우, 평가 및 심사에서 제외될 뿐만 아니라 시상 이후 표절이 확인된 경우에도 수상 취소와 함께 시상내역 또한 환수됨\n\n■ 응모자격\n- 대학생 또는 대학원생\n* 전공무관, 개인 또는 팀(6인 이하)으로 참가. 단, 석사 재학생까지 제한\n\n■ 지원내용\n- 10팀을 선정하여 KOREN 기반 아이디어 개발 및 구현을 위한 연구개발비(팀별 200만원), 해커톤 캠프, KOREN 자원 등을 제공하고 최종 결과물 평가를 통해 우수팀 시상\n\n■ 문의사항\nNET 챌린지 캠프 사무국\nE. netcc@koren.kr\nT. 031-5182-9172, 9173\n\n\n"}, {"contest_title": "미래융합 인재발굴 소프트웨어 챌린지", "contest_host": "과학기술정보통신부", "contest_target_participants": "일반인, 대학생, 청소년, 기타", "contest_reception_period": "2024-06-04 ~ 2024-06-23", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1천만원이하, 1등 상금 : 300만원", "contest_homepage": "http://www.gsia-sw.kr/01_sub/01_sub.html", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240604001831_cb7d5b65.jpg", "contest_detail_text": ""}, {"contest_title": "[무료] 금융보안원 금융보안아카데미 제2기 교육생 모집", "contest_host": "금융보안원, 금융보안포럼", "contest_target_participants": "대학생, 기타", "contest_reception_period": "2024-05-27 ~ 2024-06-17", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1,200만원, 1등 상금 : ", "contest_homepage": "https://www.fsec.or.kr/bbs/detail?menuNo=66&bbsNo=11489", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240528141440_d60179f7.jpg", "contest_detail_text": ""}, {"contest_title": "[하나금융그룹] 하나 디지털 파워온 프로젝트 3기 모집", "contest_host": "하나금융그룹/아이들과미래재", "contest_target_participants": "대학생, 기타", "contest_reception_period": "2024-05-27 ~ 2024-06-23", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "다양한 혜택, 1등 상금 : ", "contest_homepage": "https://hana-digital.co.kr/", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240527002326_6ce07a69.jpg", "contest_detail_text": ""}, {"contest_title": "[SCPC 2024] 제10회 삼성전자 대학생 프로그래밍 경진대회", "contest_host": "삼성전자", "contest_target_participants": "대학생", "contest_reception_period": "2024-06-04 ~ 2024-07-04", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "5천만원이상, 1등 상금 : 2,000만원", "contest_homepage": "https://research.samsung.com/scpc", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240604015536_6b1852dd.jpg", "contest_detail_text": "[SCPC 2024] 제10회 삼성전자 대학생 프로그래밍 경진대회\n\n■ 공모개요\n제10회 삼성전자 대학생 프로그래밍 경진대회\n상금 총 1억 원의 SCPC 2024 접수 시작!\n\n■ 공모주제\n- C, C++, Java를 사용하여 제한시간 내에 알고리즘 문제를 풀고 소스코드 제출\n\n■ 기간 및 일정\n- 참가 접수 : 2024. 6. 4 (화) ~ 7. 4 (목) *온라인 접수\n- 1차 예선 : 2024. 7. 5 (금) ~ 7. 6 (토)\n- 2차 예선 : 2024. 7. 27 (토)\n- 본선 대회 : 2024. 8. 31 (토)\n\n■ 지원자격\n- 대학(원) 재학 또는 휴학생 (전공 및 학년 제한 없음)\n\n■ 접수방법\n- 삼성리서치 SCPC페이지에서 내용확인 후 접수\n\n■ 공모전 시상내역 : 총 38명 / 총 상금 1억원 규모\n- 1등 (1명) : 2,000만원\n- 2등 (2명) : 각 1,000만원\n- 3등 (5명) : 각 400만원\n- 4등 (10명) : 각 200만원\n- 5등 (20명) : 각 100만원\n\n■ 문의처\n- 코드그라운드 웹사이트에서 문의 바랍니다.\n\n\n"}, {"contest_title": "일상을 바꿀 생성형 AI 및 대화형 GPT 기술 활용 공모전", "contest_host": "세종사이버대학교 소프트웨어공학과", "contest_target_participants": "일반인, 대학생, 기타", "contest_reception_period": "2024-05-27 ~ 2024-07-12", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1천만원이하, 1등 상금 : 30만원", "contest_homepage": "https://dept.sjcu.ac.kr/computer/news/notice.do?mode=view&articleNo=126344", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240529125640_3e528ce4.jpg", "contest_detail_text": ""}, {"contest_title": "2024 여수시 e스포츠 페스티벌", "contest_host": "여수시", "contest_target_participants": "기타", "contest_reception_period": "2024-05-20 ~ 2024-06-23", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1천만원이하, 1등 상금 : 200만원", "contest_homepage": "https://blog.naver.com/yeosu_city/223453966172", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240525203154_67e161f7.jpg", "contest_detail_text": "2024 여수시 e스포츠 페스티벌\n\n2024 여수시 e스포츠 페스티벌 참가신청페이지 입니다.\n\n■ 시상내역 : 총상금(1000만원)\n- 청소년부\n * 리그오브레전드 200만원(1등) 100만원(2등) 50만원(3등)\n * FC온라인 50만원(1등) 30만원(2등) 20만원(3등)\n- 일반부\n * 리그오브레전드 200만원(1등) 100만원(2등) 50만원(3등)\n * FC온라인 50만원(1등) 30만원(2등) 20만원(3등)\n * 스타크래프트 50만원(1등) 30만원(2등) 20만원(3등)\n\n■ 모집기간 : 2024.05.20 ~ 2024.06.23 23:59\n\n■ 모집 대상 : 전국 아마추어 선수 누구나 (e스포츠관련학과 참여불가)\n\n■ 예선 일정 : 청소년부 (06.29~30) 일반부 (07.06~07) \n*종목별 자세한 일정은 참가팀에 한하여 안내예정입니다.\n\n■ 결선 일정 : 2024.07.20(토) - 청소년부 , 07.21(일) - 일반부\n\n■ 결선 장소 : 전남 여수시 흥국체육관\n\n■ BJ 백크 , 유튜버 제드99 관련 이벤트 사항\n- 페스티벌 방문일자 : 2024.07.20(토) 개막식\n- 이벤트 매치내용 : 사전 접수 후 현장 추첨을 통해 백크팀,제드99팀을 나누어 소환사협곡 5:5 매치 대결\n- 사인회 진행 : 이벤트 매치 이후 현장 추첨을 통하여 별도로 선별된 인원에 한하여 사인회 진행(약 50명진행예정)\n\n\n"}, {"contest_title": "2024년 신구대학교 VR게임콘텐츠과 전국 중·고교생 게임 공모전", "contest_host": "신구대학교 VR게임콘텐츠과", "contest_target_participants": "청소년", "contest_reception_period": "2024-06-03 ~ 2024-08-14", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "다양한 혜택, 1등 상금 : ", "contest_homepage": "https://vr.shingu.ac.kr/cms/FR_BBS_CON/BoardView.do?MENU_ID=250&CONTENTS_NO=1&SITE_NO=37&BOARD_SEQ=5&BBS_SEQ=137", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240511043208_88bf99fb.jpg", "contest_detail_text": "2024년 신구대학교 VR게임콘텐츠과 전국 중·고교생 게임 공모전\n\n2024년 신구대학교 VR게임콘텐츠과에서는 전국 중·고교생 게임 공모전을 실시합니다.\n\n■ 대회 개요\n- 전국 중·고교생들을 대상으로 게임 콘텐츠 제작 장려와 우수 미래 인재의 발굴\n\n■ 응모 자격\n- 1명에서 6명 이내로 이루어진 중·고교생 팀 또는 개인\n- 중학교 재학생, 중학교 졸업생 및 검정고시 합격생, 고등학교 재학생, 고등학교 졸업생, 검정고시 합격생 및 학교 밖 청소년 등\n\n■ 접수 방법\n1) 온라인 신청서 작성 후 소개 플레이 영상, 발표 영상, 기획서(선택)와 함께 제출\n2) 플레이 영상과 발표 영상은 개인 유튜브 채널에 업로드 후 온라인 신청서에 URL을 기재하여 제출(필수)\n ○ 영상 길이 제한 없음\n ○ 영상 제목\n 1) 2024 신구대학교 전국 중·고교생 게임 공모전 출품작, 팀 명, 게임 명, 플레이 영상\n 2) 2024 신구대학교 전국 중·고교생 게임 공모전 출품작, 팀 명, 게임 명, 발표 영상\n3) 기획서 제출(선택)\n ○ 기획서 제출은 아래 이메일에 제출합니다.\n ○ 받는 사람 이메일: game@shingu.ac.kr\n ○ 이메일 제목: 2024 신구대학교 전국 중·고교생 게임 공모전, 팀 명(인원 수), 게임 명\n ○ 보내는 사람 이메일은 신청서에 작성한 팀 대표 학생 또는 팀원 이메일\n ○ 기획서 용량 제한: 10MB\n ○ 페이지 수 제한: 1~10쪽, 양식은 MS Word, gksrmg, PPT 등(별도의 양식은 없음, 자유롭게 작성하여 제출)\n ○ 기획서 목차 예시: 팀 명, 게임 명, 게임 소개, 장르, 게임의 목표 및 규칙, 게임의 독특한 특징, 아이템, 레벨, 모드 등의 게임의 기능 소개, 주요 캐릭터 및 세계관 소개, 팀 원 별 상세 역할, 제작 기간(제작 시작 일자 ~ 제작 마무리 일자), 향후 계획, 감사의 말 등\n ○ 제출 URL: https://forms.gle/c8dgFWthYutvdzaa9\n\n■ 공모전 진행 계획\n1) 예선\n ○ 제출된 플레이 영상, 발표 영상, 기획서에 대한 심사\n2) 본선 및 시상식\n ○ 예선 제출작 중 우수작을 선발하여 본선 시상식 진행\n ○ 발표 진행 방식은 5분 발표\n ○ 본선 발표를 위한 PPT 작성, 플레이 데모를 위한 실행 파일 지참 필수(PC 게임 이외의 경우 실행 가능한 기기 지참)\n ○ 발표 장소: 경기도 판교(세부 장소는 추후 공지)\n ○ 본선 발표는 주최 측 상황에 따라 생략 또는 변경 가능하며 예선 심사 결과로 최종 시상할 수 있음\n\n■ 심사 기준 및 심사 위원\n1) 심사 기준\n ○ 창의성 40%, 흥미도 30%, 완성도 30%\n2) 심사 위원\n ○ 신구대학교 VR게임콘텐츠과 교수진 및 현직 게임 개발자\n\n■ 접수 기간 및 결과 공지\n1) 접수 기간\n ○ 2024. 6. 3.(월)~2024. 8. 14.(수) 17:00까지\n2) 결과 공지 일시 및 방법\n ○ 예선: 2024. 8. 21.(수) 18시, 신구대학교 VR게임콘텐츠과 홈페이지 공지 사항에 게재 및 본선 진출자에 한해 개별 문자 발송\n ○ 본선: 2024. 9. 7.(토) 본선(본선 방식 및 시간, 세부 장소는 신구대학교 VR게임콘텐츠과 홈페이지 공지 사항에 게재 및 본선 진출자에 한해 개별 문자 발송) 후 멘토링 및 시상식 진행\n\n■ 주최 및 공동 주관\n1) 주최\n ○ 신구대학교 VR게임콘텐츠과\n2) 공동 주관\n ○ 신구대학교 VR게임콘텐츠과, 성남시 청소년재단, 성남시 게임힐링센터, 성남시 청년지원센터 청년이봄\n\n■ 시상 내역\n○ 대상: 1팀, 신구대학교 총장상\n○ 최우수상: 1팀, 성남시 청소년재단 대표이사상\n○ 우수상: 성남게임힐링센터장상\n○ 장려상: 성남시 청년지원센터장상\n * 수상자 특전: 본선에서 심사 위원(교수진 및 현업 게임 개발자)의 게임에 대한 멘토링\n * 모든 수상자에게는 상장 및 부상 수여\n * 작춤의 수준이 수상 기준에 미치지 못하는 경우, 일부 상은 수상자가 없을 수 있음\n\n\n"}, {"contest_title": "24년상반기 SW개발공모전(SW개발아이디어 및 결과물)", "contest_host": "한국스마트정보교육원/한국스마트정보교육", "contest_target_participants": "일반인, 대학생", "contest_reception_period": "2024-01-31 ~ 2024-06-28", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "1천만원이하, 1등 상금 : 50만원", "contest_homepage": "http://ksmart.or.kr/?c=5/9&type=notice&mod=view&type=notice&idx=bbs_content_24013000003", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240131173219_1d7ab858.jpg", "contest_detail_text": "24년상반기 SW개발공모전(SW개발아이디어 및 결과물)\n\n■ 공모 주제\n[1] 주제\n 1) 생성형AI 응용 SW (웹,어플 결과물)\n 2) H/W연계 가정 또는 구현한 IOT 응용 SW (웹,어플 결과물)\n 3) 정부,기업 Open API 응용 SW (웹,어플 결과물)\n 4) 기타 응용 SW (웹,어플 결과물)\n\n■ 참가 분류1\n1)아이디어만 제출\n2)아이디어 및 결과물(실행 가능 방법 및 개발문서,DB,소스코드 압축)\n\n■ 참가 분류2 \n 1)개인\n 2)팀\n\n■ 지원 자격\n- 대학 재학생 / 졸업생 개인 또는 팀\n- 일반인 개인 또는 팀\n\n■ 모집 기간\n- 2024년 6월 28일까지 (신청서 접수 시 아이디어 및 결과물 최종 제출)\n\n■ 지원 방법\n- 제출방법(이메일) : sugang@ksmart.or.kr\n제출시 제목 : 24년1회차_참가자명(팀명)__공모전신청서(마감일_24년06월28일)_한국스마트정보교육원_제출일자\n첨부파일 : 1. 공통 :공모전서 2. 결과물 : 압축파일 하나로 제출\n1)아이디어만 제출 또는 2)아이디어 및 결과물 공통 내용\n* 공통 : 프로젝트명 / 사용자(개인,기업,정부) / 목적(적합성,독창성,사업화가능성,서비스확장성,기타) / 기대효과 / 주요기능 / 보완해야 할 점\n2)아이디어 및 결과물 추가 제출\n* 실행 가능 경로(실행방법 소개) / SW개발환경 / 개발문서 / 소스코드 / DB(ERD,백업파일)\n\n■ 평가 기준\n1)필수작성 적합성-목적-10점\n2)필수작성 독창성-목적-10점\n3)필수작성 사업성-기대-10점\n4)필수작성 서비스확장성- 기대-10점\n5)필수작성 강점-10점\n6)필수작성 주요기능-10점\n7)결과물 제출 필수작성 SW개발환경 -10점\n8)결과물 제출 필수작성 개발문서 등 -10점\n9)결과물 제출 필수작성 소스코드 -20점\n10)선택사항 보완해야 할 점\n\n■ 활동 혜택\n- 무료 교육 및 전북IT기업협회 회원사 취업지원\n\n■ 평가 방법 및 평가위원\n- 평가 방법 : 평가 배점 기준으로 평가 함\n- 평가 위원 : 전북IT기업협회 회원사 대표 및 임원진\n\n■ 시상\n- 상장 : 1등(최우수상) / 2등(우수상) / 3등(장려상)\n- 상금(상품) : \n 1등(최우수상 1명 또는 1팀) : 50만원 (상금 또는 상품)\n 2등(우수상 1명 또는 1팀) : 20만원 (상금 또는 상품)\n 3등(장려상 3명 또는 3팀) : 10만원 (상금 또는 상품)\n * 전북IT기업협회 회원사 후원금 추가 지급\n\n■ 문의 사항\n- 063-717-1008\n\n\n"}, {"contest_title": "K-디지털 챌린지 2024년 메타버스 개발자 경진대회", "contest_host": "과학기술정보통신부", "contest_target_participants": "제한없음", "contest_reception_period": "2024-05-07 ~ 2024-06-0", "contest_decision_period": null, "contest_competition_area": null, "contest_award": "5천만원이상, 1등 상금 : 2,000만원", "contest_homepage": "https://www.metaversedev.kr/", "contest_how_to_apply": null, "contest_fee": null, "contest_image": "https://www.wevity.com/upload/contest/20240514031202_cf4fab9a.jpg", "contest_detail_text": "K-디지털 챌린지 2024년 메타버스 개발자 경진대회\n\n■ 참가자격 : 메타버스 서비스·콘텐츠 개발에 관심 있는 누구나(성인/학생)\n- 학생부(초·중·고), 성인부(대학(원)생, 일반인)\n※ 초·중·고·대학(원)생 및 일반인 1인 또는 팀 단위 신청 모두 가능(최대 5인), 법인 참여 불가\n※ 19세 이하 청소년 및 검정고시 합격자 등도 참여 가능\n\n■ 참가분야\n- 취업과제, 창업과제, 자유과제로 구분\n\n■ 참가 접수 : 2024.05.07.(화) ~ 2024.06.07.(금)\n\n■ 문의처\n- Tel : 02-305-5002\n- E-mail : contest2024@metaversedev.kr\n\n\n"}] \ No newline at end of file