diff --git a/README.md b/README.md
new file mode 100644
index 00000000..ce13fabf
--- /dev/null
+++ b/README.md
@@ -0,0 +1,255 @@
+# 프로젝트에 대한 간단한 설명
+## 📌Project Overview
+
+- HaRu는 소규모 팀을 위한 All-In-One 운영 관리 플랫폼입니다.,
+- 보다 효율적인 팀 운영을 위한 고민에서 시작된 HaRu는, 각자의 자리에서 치열하게 움직이는 소규모 팀들의 하루를 돕고자 합니다.,
+- 회의 진행 보조, SNS 이벤트 진행 관리, 팀 분위기 체크까지.HaRu는 모든 팀이 더 가치 있는 순간에 집중할 수 있도록 돕습니다.,
+
+## 🚀HaRu Key Features,
+
+- **AI 회의 진행 매니저**,
+ - 실시간 STT 변환,
+ - HaRu AI 회의 진행 질문 추천,
+ - 회의록 자동 생성,
+- **SNS 이벤트 어시스턴트**,
+ - Instagram 계정 연동,
+ - 이벤트 URL 등록,
+ - 이벤트 참여자 및 당첨자 리스트 추출,
+- **팀 분위기 트래커**,
+ - 설문지 작성 및 배포,
+ - 팀 분위기 리포트 자동 생성,
+ - 운영자 맞춤 HaRu 인사이트 제공,
+
+## ⚒️Technical Overview,
+
+- **FrontEnd**: Next.js · React · TypeScript · Tailwind CSS · Storybook · Vercel,
+- **BackEnd**: Spring · FastAPI · Docker · MySQL · AWS(EC2, S3, RDS) · Redis
+---
+
+---
+
+---
+
+# 사용한 브랜치 전략 및 기술 스택, 프로젝트 구조 등
+
+## 브랜치 전략
+
+
+
+- 위 git flow를 따름
+- release브랜치는 제외
+- dev 브랜치 왼편에 feature브랜치말고 refactor, fix브랜치도 존재
+
+
+
+- 브랜치
+ - `main` - 배포용 브랜치
+ - `dev` - 개발용 브랜치
+ - `feat/*` - 개발 피쳐별 브랜치 (새 기능)
+ - `fix/*` - 버그 수정 피쳐별 브랜치 (버그 수정)
+ - `refactor/*` - 리팩토링 브랜치
+- 이슈마다 브랜치 생성하고 커밋 ex) feat/#10-login
+- 이슈 해결되면 이슈 close하고 해당 브랜치 삭제
+- **main, dev로 나누고, 개발 된 것은 dev에 merge. main에 merge되면 CI/CD**
+
+### 브랜치명 컨벤션
+
+- {태그}/#{이슈번호}-{작업내용}
+ - 작업내용 : `kebab-case` , 띄워쓰기는 "-"로 구분
+ - kebab-case: 모두 소문자로 표현하며, 단어와 단어 사이에는 하이픈(-)를 사용합니다.
+
+ ex) feat/#0-project-init
+
+
+---
+
+## 서버 아키텍처
+
+
+
+## 기술 스택
+
+
+
+| Programming Languages | Java (17) / Python |
+| --- | --- |
+| Frameworks | SpringBoot / FastAPI |
+| Version Control | Git |
+| Cloud Services | AWS Route53 / EC2 / RDS(MySQL) / S3 / Docker |
+| Database & Caches | MySQL / Redis |
+| Deployment Tools | Nginx (프록시 서버) |
+| Extra Library | Swagger / WebSockets / elevenlabs |
+
+---
+
+## 프로젝트 구조
+
+### DDD
+
+### 계층 구조
+
+
+
+
+
+
+
+
+
+---
+
+
+
+---
+
+### 패키지 구조 예시
+
+```markdown
+└── src
+ ├── main
+ │ ├── java
+ │ │ └── com
+ │ │ └── haru
+ │ │ └── server
+ │ │ ├── ServerApplication.java
+ │ │ ├── domain
+ │ │ │ ├── user
+ │ │ │ │ ├── controller
+ │ │ │ │ ├── service
+ │ │ │ │ ├── repository
+ │ │ │ │ ├── converter
+ │ │ │ │ ├── dto
+ │ │ │ │ ├── exception
+ │ │ │ │ │ ├── handler
+ │ │ │ │ │ ├── validator
+ │ │ │ │ │ └── annotation
+ │ │ │ │ └── entity
+ │ │ │ ├── ...
+ │ │ │ ├── moodTracker
+ │ │ │ │ ├── controller
+ │ │ │ │ ├── service
+ │ │ │ │ ├── repository
+ │ │ │ │ ├── converter
+ │ │ │ │ ├── dto
+ │ │ │ │ **├──** exception
+ │ │ │ │ │ ├── handler
+ │ │ │ │ │ ├── validator
+ │ │ │ │ │ └── annotation
+ │ │ │ │ └── entity
+ │ │ ├── global
+ │ │ │ ├── common
+ │ │ │ │ └── entity
+ │ │ │ │ └── BaseEntity.java
+ │ │ │ ├── config
+ │ │ │ │ ├── SwaggerConfig.java
+ │ │ │ │ ├── properties
+ │ │ │ │ └── security
+ │ │ │ │ └── SecurityConfig.java
+ │ │ │ ├── apiPayload
+ │ │ │ │ ├── code
+ │ │ │ │ │ ├── Status
+ │ │ │ │ │ │ ├── ErrorStatus
+ │ │ │ │ │ │ └── SuccessStatus
+ │ │ │ │ │ ├── BaseCode.java
+ │ │ │ │ │ ├── BaseErrorCode.java
+ │ │ │ │ │ ├── ErrorReasonDTO.java
+ │ │ │ │ │ └── ReasonDTO.java
+ │ │ │ │ ├── exception
+ │ │ │ │ │ ├── ExceptionAdvice.java
+ │ │ │ │ │ └── GeneralException.java
+ │ │ │ │ └── ApiResponse.java
+ │ │ │ └── util
+ │ │ └── infra
+ │ └── resources
+ │ └── application.yml
+ │ └── application-secret.yml
+```
+
+
+
+---
+
+---
+
+---
+
+# 팀원 정보
+
+| 이름 | 닉네임 | 파트 | 소속 | 전화번호 | GitHub |
+| --- | --- | --- | --- | --- | --- |
+| 황지원 | 벨라 | PM | 중앙대학교 경영학부 | 010-4139-4130 | hwangjeewon |
+| 이수호 | 쏘 | Designer | 한양대학교 ERICA ICT학부 디자인테크놀로지 | 010-9455-9509 | Leesuho9509 |
+| 김여진 | 조이 | Frontend Developer | 숭실대학교 IT대학 글로벌미디어학부 | 010-5001-9456 | duwlsssss |
+| 박수현 | 노코 | Frontend Developer | 명지대학교 컴퓨터공학과 | 010-6631-1760 | strfunctionk |
+| 손기훈 | 제트 | Frontend Developer | 숭실대학교 IT대학 글로벌미디어학부 | 010-3947-5847 | S-Gihun |
+| 박경운 | 하늘 | Frontend Developer | 중앙대학교 소프트웨어대학 소프트웨어학부 | 010-9344-8561 | kyeoungwoon |
+| 임동재 | 포츠 | Backend Developer | 중앙대학교 소프트웨어대학 소프트웨어학부 | 010-8986-2425 | djlim2425 |
+| 김진호 | 루피 | Backend Developer | 중앙대학교 소프트웨어대학 소프트웨어학부 | 010-8828-5091 | Jinho622 |
+| 이호근 | 우디 | Backend Developer | 숭실대학교 컴퓨터학부 | 010-9842-4789 | 2ghrms |
+| 이석주 | 닉 | Backend Developer | 중앙대학교 소프트웨어대학 소프트웨어학부 | 010-4067-2687 | hknhj |
diff --git a/src/main/java/com/haru/api/domain/meeting/controller/MeetingController.java b/src/main/java/com/haru/api/domain/meeting/controller/MeetingController.java
index dc14e518..4c4b63c4 100644
--- a/src/main/java/com/haru/api/domain/meeting/controller/MeetingController.java
+++ b/src/main/java/com/haru/api/domain/meeting/controller/MeetingController.java
@@ -178,4 +178,19 @@ public ApiResponse getMeetingTranscript(
}
+
+ @Operation(summary = "회의록 음성파일 조회API", description =
+ "# [v1.0 (2025-08-14)](https://www.notion.so/AI-24f5da7802c580bc882fe01607e01bbc)" +
+ "회의록을 다운로드하는 API입니다. URL을 반환합니다."
+ )
+ @GetMapping("{meetingId}/ai-proceeding/voice")
+ public ApiResponse MeetingvoiceFile(
+ @PathVariable("meetingId") String meetingId
+ ){
+ Long userId = SecurityUtil.getCurrentUserId();
+
+ MeetingResponseDTO.proceedingVoiceLinkResponse response = meetingQueryService.MeetingVoiceFile(userId, Long.parseLong(meetingId));
+
+ return ApiResponse.onSuccess(response);
+ }
}
diff --git a/src/main/java/com/haru/api/domain/meeting/dto/MeetingResponseDTO.java b/src/main/java/com/haru/api/domain/meeting/dto/MeetingResponseDTO.java
index 92b467d3..128db837 100644
--- a/src/main/java/com/haru/api/domain/meeting/dto/MeetingResponseDTO.java
+++ b/src/main/java/com/haru/api/domain/meeting/dto/MeetingResponseDTO.java
@@ -56,6 +56,14 @@ public static class proceedingDownLoadLinkResponse {
private String downloadLink;
}
+ @Getter
+ @Builder
+ @NoArgsConstructor
+ @AllArgsConstructor
+ public static class proceedingVoiceLinkResponse {
+ private String voiceLink;
+ }
+
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
diff --git a/src/main/java/com/haru/api/domain/meeting/service/MeetingQueryService.java b/src/main/java/com/haru/api/domain/meeting/service/MeetingQueryService.java
index d3f22508..15c0e0f2 100644
--- a/src/main/java/com/haru/api/domain/meeting/service/MeetingQueryService.java
+++ b/src/main/java/com/haru/api/domain/meeting/service/MeetingQueryService.java
@@ -13,4 +13,6 @@ public interface MeetingQueryService {
MeetingResponseDTO.TranscriptResponse getTranscript(Long userId, Long meetingId);
MeetingResponseDTO.proceedingDownLoadLinkResponse downloadMeeting(Long userId, Long meetingId);
+
+ MeetingResponseDTO.proceedingVoiceLinkResponse MeetingVoiceFile(Long userId, Long meetingId);
}
diff --git a/src/main/java/com/haru/api/domain/meeting/service/MeetingQueryServiceImpl.java b/src/main/java/com/haru/api/domain/meeting/service/MeetingQueryServiceImpl.java
index db26825d..9ff4ba09 100644
--- a/src/main/java/com/haru/api/domain/meeting/service/MeetingQueryServiceImpl.java
+++ b/src/main/java/com/haru/api/domain/meeting/service/MeetingQueryServiceImpl.java
@@ -128,4 +128,31 @@ public MeetingResponseDTO.proceedingDownLoadLinkResponse downloadMeeting(Long us
.build();
}
+ @Override
+ public MeetingResponseDTO.proceedingVoiceLinkResponse MeetingVoiceFile(Long userId, Long meetingId){
+ User foundUser = userRepository.findById(userId)
+ .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND));
+
+ Meeting foundMeeting = meetingRepository.findById(meetingId)
+ .orElseThrow(() -> new MeetingHandler(ErrorStatus.MEETING_NOT_FOUND));
+
+ Workspace foundWorkspace = meetingRepository.findWorkspaceByMeetingId(meetingId)
+ .orElseThrow(() -> new WorkspaceHandler(ErrorStatus.WORKSPACE_NOT_FOUND));
+
+ UserWorkspace foundUserWorkspace = userWorkspaceRepository.findByUserIdAndWorkspaceId(userId, foundWorkspace.getId())
+ .orElseThrow(() -> new UserWorkspaceHandler(ErrorStatus.USER_WORKSPACE_NOT_FOUND));
+
+ String audioFileKeyName = foundMeeting.getAudioFileKey();
+
+ if (audioFileKeyName == null || audioFileKeyName.isBlank()) {
+ throw new MeetingHandler(ErrorStatus.MEETING_PROCEEDING_NOT_FOUND);
+ }
+
+ String presignedUrl = amazonS3Manager.generatePresignedUrl(audioFileKeyName);
+
+ return MeetingResponseDTO.proceedingVoiceLinkResponse.builder()
+ .voiceLink(presignedUrl)
+ .build();
+ }
+
}
diff --git a/src/main/java/com/haru/api/domain/user/security/googleOauth2/CustomOAuth2FailureHandler.java b/src/main/java/com/haru/api/domain/user/security/googleOauth2/CustomOAuth2FailureHandler.java
index d07fe0d6..0d4077c8 100644
--- a/src/main/java/com/haru/api/domain/user/security/googleOauth2/CustomOAuth2FailureHandler.java
+++ b/src/main/java/com/haru/api/domain/user/security/googleOauth2/CustomOAuth2FailureHandler.java
@@ -2,6 +2,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
@@ -10,14 +11,17 @@
@Component
public class CustomOAuth2FailureHandler implements AuthenticationFailureHandler {
+
+ @Value("${google-login-frontend-url}")
+ String baseUrl;
+
@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception
) throws IOException {
- String failureGoogleLoginUrl = "/auth/login/google/callback";
// 프론트엔드 URL로 리다이렉트 (query param 전달)
- response.sendRedirect("http://localhost:3000" + failureGoogleLoginUrl + "?status=fail");
+ response.sendRedirect(baseUrl + "?status=fail");
}
}
diff --git a/src/main/java/com/haru/api/domain/user/security/googleOauth2/CustomOAuth2SuccessHandler.java b/src/main/java/com/haru/api/domain/user/security/googleOauth2/CustomOAuth2SuccessHandler.java
index b4010bbb..3635b5c0 100644
--- a/src/main/java/com/haru/api/domain/user/security/googleOauth2/CustomOAuth2SuccessHandler.java
+++ b/src/main/java/com/haru/api/domain/user/security/googleOauth2/CustomOAuth2SuccessHandler.java
@@ -19,6 +19,8 @@ public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler
private int accessExpTime;
@Value("${jwt.refresh-expiration}")
private int refreshExpTime;
+ @Value("${google-login-frontend-url}")
+ String baseUrl;
private final UserCommandService userCommandService;
@Override
@@ -28,8 +30,6 @@ public void onAuthenticationSuccess(
Authentication authentication
) throws IOException {
StringBuilder redirectUrl = new StringBuilder();
- String baseUrl = "http://localhost:3000"; // 프론트엔드 URL
- String successGoogleLoginUrl = "/auth/login/google/callback";
CustomOauth2UserDetails userDetails = (CustomOauth2UserDetails) authentication.getPrincipal();
// 회원가입이든 로그인이든 똑같이 프론트엔드로 리다이렉트
Long userId = userDetails.getUser().getId();
@@ -38,7 +38,6 @@ public void onAuthenticationSuccess(
String refreshToken = userCommandService.generateAndSaveRefreshToken(key, refreshExpTime);
// 프론트엔드 URL로 리다이렉트 (query param 전달)
redirectUrl.append(baseUrl)
- .append(successGoogleLoginUrl)
.append("?status=success")
.append("&userId=").append(userId)
.append("&profileImage=").append(userDetails.getUser().getProfileImage())
diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml
index f5706bab..bd318086 100644
--- a/src/test/resources/application-test.yml
+++ b/src/test/resources/application-test.yml
@@ -48,6 +48,8 @@ invite-url: invite-url
survey-url: survey-url
+google-login-frontend-url: google-login-frontend-url
+
instagram:
client:
id: "dummy-client-id"