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 new file mode 100644 index 00000000..bd248b8a --- /dev/null +++ b/src/main/java/com/haru/api/domain/meeting/controller/MeetingController.java @@ -0,0 +1,44 @@ +package com.haru.api.domain.meeting.controller; + +import com.haru.api.domain.meeting.dto.MeetingRequestDTO; +import com.haru.api.domain.meeting.dto.MeetingResponseDTO; +import com.haru.api.domain.meeting.service.MeetingService; +import com.haru.api.domain.user.security.jwt.SecurityUtil; +import com.haru.api.global.apiPayload.ApiResponse; +import com.haru.api.global.apiPayload.code.status.ErrorStatus; +import com.haru.api.global.apiPayload.exception.GeneralException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequestMapping("/api/v1/meetings") +@RequiredArgsConstructor +public class MeetingController { + + private final MeetingService meetingService; + + @PostMapping( + consumes = {MediaType.MULTIPART_FORM_DATA_VALUE }, + produces = MediaType.APPLICATION_JSON_VALUE + ) + public ApiResponse createMeeting( + @RequestPart("agendaFile") MultipartFile agendaFile, + @RequestPart("request") MeetingRequestDTO.createMeetingRequest request) { + + // file업로드가 되지 않는 경우 controller단에서 요청 처리 + if (agendaFile == null || agendaFile.isEmpty()) { + throw new GeneralException(ErrorStatus.MEETING_FILE_NOT_FOUND); + } + Long userId = SecurityUtil.getCurrentUserId(); + + MeetingResponseDTO.createMeetingResponse response = meetingService.createMeeting(userId, agendaFile, request); + + return ApiResponse.onSuccess(response); + } +} diff --git a/src/main/java/com/haru/api/domain/meeting/converter/MeetingConverter.java b/src/main/java/com/haru/api/domain/meeting/converter/MeetingConverter.java new file mode 100644 index 00000000..46ffe763 --- /dev/null +++ b/src/main/java/com/haru/api/domain/meeting/converter/MeetingConverter.java @@ -0,0 +1,18 @@ +package com.haru.api.domain.meeting.converter; + +import com.haru.api.domain.meeting.dto.MeetingResponseDTO; +import com.haru.api.domain.meeting.entity.Meetings; +import org.springframework.stereotype.Component; + +@Component +public class MeetingConverter { + + // Entity -> ResponseDTO + public static MeetingResponseDTO.createMeetingResponse toCreateMeetingResponse(Meetings meeting) { + return MeetingResponseDTO.createMeetingResponse.builder() + .meetingId(meeting.getId()) + .title(meeting.getTitle()) + .updatedAt(meeting.getCreatedAt()) + .build(); + } +} diff --git a/src/main/java/com/haru/api/domain/meeting/dto/MeetingRequestDTO.java b/src/main/java/com/haru/api/domain/meeting/dto/MeetingRequestDTO.java new file mode 100644 index 00000000..e686956b --- /dev/null +++ b/src/main/java/com/haru/api/domain/meeting/dto/MeetingRequestDTO.java @@ -0,0 +1,14 @@ +package com.haru.api.domain.meeting.dto; + +import lombok.Builder; +import lombok.Getter; + +public class MeetingRequestDTO { + + @Getter + @Builder + public static class createMeetingRequest{ + private Long workspaceId; + private String title; + } +} 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 new file mode 100644 index 00000000..7dfbe0f4 --- /dev/null +++ b/src/main/java/com/haru/api/domain/meeting/dto/MeetingResponseDTO.java @@ -0,0 +1,19 @@ +package com.haru.api.domain.meeting.dto; + +import com.haru.api.domain.meeting.entity.Meetings; +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDateTime; + + +public class MeetingResponseDTO { + @Getter + @Builder + public static class createMeetingResponse{ + private String title; + private Long meetingId; + private LocalDateTime updatedAt; + + } +} diff --git a/src/main/java/com/haru/api/domain/meeting/entity/Meetings.java b/src/main/java/com/haru/api/domain/meeting/entity/Meetings.java new file mode 100644 index 00000000..1119b088 --- /dev/null +++ b/src/main/java/com/haru/api/domain/meeting/entity/Meetings.java @@ -0,0 +1,53 @@ +package com.haru.api.domain.meeting.entity; + +import com.haru.api.domain.user.entity.Users; +import com.haru.api.domain.workspace.entity.Workspace; +import com.haru.api.global.common.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.DynamicInsert; +import org.hibernate.annotations.DynamicUpdate; + +@Entity +@Table(name = "meetings") +@Getter +@DynamicUpdate +@DynamicInsert +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Meetings extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 50) + private String title; + + //file을 직접 저장하면 db의 용량이 커지고 조회때마다 io가 커지므로 저장하지 않도록 함 + //private String agendaFile; + + private String agendaResult; + + @Column(columnDefinition="TEXT") + private String proceeding; + + @ManyToOne(fetch = FetchType.LAZY) + private Users users; + + @ManyToOne(fetch = FetchType.LAZY) + private Workspace workspaces; + + private Meetings(String title, String agendaResult, Users users, Workspace workspaces) { + this.title = title; + this.agendaResult = agendaResult; + this.users = users; + this.workspaces = workspaces; + } + + public static Meetings createInitialMeeting(String title, String agendaResult, Users users, Workspace workspaces) { + return new Meetings(title, agendaResult, users, workspaces); + } + + public void updateProceeding(String proceeding) { + this.proceeding = proceeding; + } +} diff --git a/src/main/java/com/haru/api/domain/meeting/repository/MeetingRepository.java b/src/main/java/com/haru/api/domain/meeting/repository/MeetingRepository.java new file mode 100644 index 00000000..84898c84 --- /dev/null +++ b/src/main/java/com/haru/api/domain/meeting/repository/MeetingRepository.java @@ -0,0 +1,9 @@ +package com.haru.api.domain.meeting.repository; + +import com.haru.api.domain.meeting.entity.Meetings; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MeetingRepository extends JpaRepository { +} diff --git a/src/main/java/com/haru/api/domain/meeting/service/MeetingService.java b/src/main/java/com/haru/api/domain/meeting/service/MeetingService.java new file mode 100644 index 00000000..70282a7c --- /dev/null +++ b/src/main/java/com/haru/api/domain/meeting/service/MeetingService.java @@ -0,0 +1,10 @@ +package com.haru.api.domain.meeting.service; + +import com.haru.api.domain.meeting.dto.MeetingRequestDTO; +import com.haru.api.domain.meeting.dto.MeetingResponseDTO; +import org.springframework.web.multipart.MultipartFile; + +public interface MeetingService { + + public MeetingResponseDTO.createMeetingResponse createMeeting(Long userId, MultipartFile agendaFile, MeetingRequestDTO.createMeetingRequest request); +} diff --git a/src/main/java/com/haru/api/domain/meeting/service/MeetingServiceImpl.java b/src/main/java/com/haru/api/domain/meeting/service/MeetingServiceImpl.java new file mode 100644 index 00000000..54b20946 --- /dev/null +++ b/src/main/java/com/haru/api/domain/meeting/service/MeetingServiceImpl.java @@ -0,0 +1,58 @@ +package com.haru.api.domain.meeting.service; + +import com.haru.api.domain.meeting.converter.MeetingConverter; +import com.haru.api.domain.meeting.dto.MeetingRequestDTO; +import com.haru.api.domain.meeting.dto.MeetingResponseDTO; +import com.haru.api.domain.meeting.entity.Meetings; +import com.haru.api.domain.meeting.repository.MeetingRepository; +import com.haru.api.domain.user.entity.Users; +import com.haru.api.domain.user.repository.UserRepository; +import com.haru.api.domain.workspace.entity.Workspace; +import com.haru.api.domain.workspace.repository.WorkspaceRepository; +import com.haru.api.global.apiPayload.code.status.ErrorStatus; +import com.haru.api.global.apiPayload.exception.handler.MemberHandler; +import com.haru.api.global.apiPayload.exception.handler.TempHandler; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MeetingServiceImpl implements MeetingService{ + + private final UserRepository userRepository; + private final WorkspaceRepository workspaceRepository; + private final MeetingRepository meetingRepository; + + @Override + @Transactional + public MeetingResponseDTO.createMeetingResponse createMeeting( + Long userId, + MultipartFile agendaFile, + MeetingRequestDTO.createMeetingRequest request) + { + Users foundUser = userRepository.findById(userId) + .orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + + Workspace foundWorkspace = workspaceRepository.findById(request.getWorkspaceId()) + .orElseThrow(() -> new TempHandler(ErrorStatus.WORKSPACE_NOT_FOUND)); + + // agendaFile을 openAi 활용하여 요약 - 미구현 + String agendaResult = "안건지 요약 - 미구현"; + + + Meetings newMeetings = Meetings.createInitialMeeting( + request.getTitle(), + agendaResult, + foundUser, + foundWorkspace + ); + + Meetings savedMeeting = meetingRepository.save(newMeetings); + + + return MeetingConverter.toCreateMeetingResponse(savedMeeting); + } +} diff --git a/src/main/java/com/haru/api/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/haru/api/global/apiPayload/code/status/ErrorStatus.java index db25cdb6..13f83053 100644 --- a/src/main/java/com/haru/api/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/haru/api/global/apiPayload/code/status/ErrorStatus.java @@ -25,6 +25,12 @@ public enum ErrorStatus implements BaseErrorCode { MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER1001", "사용자가 없습니다."), REFRESHTOKEN_NOT_EQUAL(HttpStatus.BAD_REQUEST, "MEMBER1002", "리프레시 토큰이 일치하지 않습니다."), + // Workspace 관련 에러 + WORKSPACE_NOT_FOUND(HttpStatus.BAD_REQUEST,"WORKSPACE1001", "워크스페이스가 없습니다."), + + // AI회의 Meetings 관련 에러 + MEETING_FILE_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEETING1001", "안건지가 업로드되지 않았습니다."), + // 예시,,, ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE4001", "게시글이 없습니다.");