Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
46ffd35
Feat: image 사이즈 정의
sleepyhoon Jan 16, 2026
6154b5e
Refactor: extractExtension 유틸 메서드로 분리
sleepyhoon Jan 16, 2026
c1b3fd8
Refactor: @Value 어노테이션 대신 @ConfigurationProperties 사용
sleepyhoon Jan 16, 2026
b891d4b
Feat: 이미지 공통 필드를 담는 ImageFile 구현
sleepyhoon Jan 16, 2026
c31d155
Feat: 이미지 상태 enum 추가
sleepyhoon Jan 16, 2026
b2d13a1
Refactor: 디렉토리 이동
sleepyhoon Jan 16, 2026
d488ade
Feat: 채팅 이미지 엔티티 구현
sleepyhoon Jan 16, 2026
9e121f4
Feat: 유저 프로필 엔티티 구현
sleepyhoon Jan 16, 2026
30bdc32
Fix: 반환값에 이미지 id 추가
sleepyhoon Jan 16, 2026
76a078e
Fix: 서비스 로직 수정
sleepyhoon Jan 16, 2026
3891ea4
Test: Test 수정
sleepyhoon Jan 16, 2026
8790f85
Fix: 원본 이미지는 origin 경로에 저장되도록 수정
sleepyhoon Jan 16, 2026
419e4f6
Feat: index 적용
sleepyhoon Jan 17, 2026
24b6493
Docs: 주석 추가
sleepyhoon Jan 17, 2026
14e679e
Docs: 문서화 보강
sleepyhoon Jan 17, 2026
90c92c4
Fix: 업로드 시 presigned url 사용하지 않고 File 직접 업로드로 변경
sleepyhoon Jan 20, 2026
cc32e41
Fix: 사진 업로드 반영한 추상화 완료
sleepyhoon Jan 21, 2026
64a3da2
Fix: 프로필 사진 업로드 로직 완성
sleepyhoon Jan 23, 2026
12fe24c
Fix: 프로필 수정 로직 변경
sleepyhoon Jan 27, 2026
5b04a29
Fix: objectKey 반환 시 domain + bucket 이름 추가 로직 구현
sleepyhoon Jan 27, 2026
4d775dc
Test: Test 수정
sleepyhoon Jan 27, 2026
0722fb5
Docs: 문서화 내용 수정
sleepyhoon Jan 27, 2026
b37518b
Docs: 주석 보강
sleepyhoon Jan 27, 2026
f8615bf
Fix: copilot PR 반영
sleepyhoon Jan 27, 2026
643f05f
Fix: 문서 수정
sleepyhoon Jan 27, 2026
a81521f
Fix: 전략 패턴 수정
sleepyhoon Feb 7, 2026
ba5853a
Fix: API 분리
sleepyhoon Feb 11, 2026
dff2d92
Fix: 문서화 완료
sleepyhoon Feb 11, 2026
cb91e34
Fix: image service 간소화
sleepyhoon Feb 12, 2026
e962152
test: test 수정
sleepyhoon Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions src/asciidoc/api/chat.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

=== 채팅방 API
''''
==== 1. 채팅방 정보 조회
==== - 채팅방 정보 조회

*Description* +

Expand Down Expand Up @@ -65,7 +65,28 @@ include::{snippets}/chat-room-controller-rest-docs-test/get-chat-room-info_succe
include::{snippets}/chat-room-controller-rest-docs-test/get-chat-room-info_success/response-fields.adoc[]
include::{snippets}/chat-room-controller-rest-docs-test/get-chat-room-info_success/http-response.adoc[]

'''
''''
==== - 채팅을 통한 이미지 전송

''''
*Description* +

채팅방에서 사용할 이미지를 업로드합니다.

- *HTTP Method*: `POST`
- *Endpoint*: `/files/image/chat`
- *Content-Type*: `multipart/form-data`
- *Requires Authentication*: Yes

*REQUEST* +
include::{snippets}/chat-room-controller-rest-docs-test/up-load-chat-image_success/http-request.adoc[]
include::{snippets}/chat-room-controller-rest-docs-test/up-load-chat-image_success/request-parts.adoc[]

*RESPONSE* +
include::{snippets}/chat-room-controller-rest-docs-test/up-load-chat-image_success/response-fields.adoc[]
include::{snippets}/chat-room-controller-rest-docs-test/up-load-chat-image_success/http-response.adoc[]


'''

=== 채팅
Expand Down
69 changes: 0 additions & 69 deletions src/asciidoc/api/file.adoc

This file was deleted.

18 changes: 18 additions & 0 deletions src/asciidoc/api/member.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
| 06 | 유저 권한 검색/검증
| 07 | token 재발급
| 08 | 토큰 인증
| 09 | 유저 프로필 사진 등록
|===

== ✨ API 문서
Expand Down Expand Up @@ -174,6 +175,23 @@ include::{snippets}/member-controller-rest-docs-test/check-availability_success_
include::{snippets}/member-controller-rest-docs-test/check-availability_success_with_username/response-fields.adoc[]
include::{snippets}/member-controller-rest-docs-test/check-availability_success_with_username/http-response.adoc[]

=== 7. 프로필 이미지 업로드
''''
*Description* +

사용자의 프로필 이미지를 업로드합니다. 기존 프로필 이미지가 존재할 경우, 새로운 이미지로 교체됩니다.

- *HTTP Method*: `POST`
- *Endpoint*: `/files/image/profile`
- *Content-Type*: `multipart/form-data`

*REQUEST* +
include::{snippets}/member-controller-rest-docs-test/up-load-profile-image_success/http-request.adoc[]
include::{snippets}/member-controller-rest-docs-test/up-load-profile-image_success/request-parts.adoc[]

*RESPONSE* +
include::{snippets}/member-controller-rest-docs-test/up-load-profile-image_success/response-fields.adoc[]
include::{snippets}/member-controller-rest-docs-test/up-load-profile-image_success/http-response.adoc[]

== ⛔ 예외 출력

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.studypals.domain.chatManage.api;

import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import lombok.RequiredArgsConstructor;

import com.studypals.domain.chatManage.dto.ChatRoomInfoRes;
import com.studypals.domain.chatManage.service.ChatRoomService;
import com.studypals.global.file.dto.ImageUploadRes;
import com.studypals.global.file.service.ImageFileService;
import com.studypals.global.responses.CommonResponse;
import com.studypals.global.responses.Response;
import com.studypals.global.responses.ResponseCode;
Expand All @@ -18,6 +22,7 @@
*
* <pre>
* - GET /chat/room/{chatRoomId} : 채팅방 정보 조회
* - POST /chat/room/{chatRoomId}/image : 채팅방 이미지 업로드
* </pre>
*
* @author jack8
Expand All @@ -29,6 +34,7 @@
public class ChatRoomController {

private final ChatRoomService chatRoomService;
private final ImageFileService imageFileService;

// 구독 이후, 해당 요청 보냄 -> 응답을 받고 정렬 마칠 때 까지, 새로운 메시지가 와도 일단 렌더링 중지, 마치고 렌더링
@GetMapping("/{chatRoomId}")
Expand All @@ -40,4 +46,14 @@ public ResponseEntity<Response<ChatRoomInfoRes>> getChatRoomInfo(

return ResponseEntity.ok(CommonResponse.success(ResponseCode.CHAT_ROOM_SEARCH, chatRoomInfo, chatRoomId));
}

@PostMapping(value = "/{chatRoomId}/image", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<Response<ImageUploadRes>> uploadChatImage(
@RequestPart("file") MultipartFile file,
@PathVariable("chatRoomId") String chatRoomId,
@AuthenticationPrincipal Long userId) {

ImageUploadRes response = imageFileService.uploadChatImage(file, chatRoomId, userId);
return ResponseEntity.ok(CommonResponse.success(ResponseCode.FILE_IMAGE_UPLOAD, response));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.studypals.domain.chatManage.dao;

import org.springframework.data.jpa.repository.JpaRepository;

import com.studypals.domain.chatManage.entity.ChatImage;

public interface ChatImageRepository extends JpaRepository<ChatImage, Long> {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,34 @@

import java.util.Map;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.springframework.stereotype.Component;

import com.studypals.domain.chatManage.dto.ChatRoomInfoRes;
import com.studypals.domain.chatManage.dto.ChatRoomInfoRes.UserInfo;
import com.studypals.domain.chatManage.dto.ChatRoomListRes;
import com.studypals.domain.chatManage.dto.ChatroomLatestInfo;
import com.studypals.domain.chatManage.entity.ChatRoomMember;
import com.studypals.global.file.ObjectStorage;

/**
* ChatRoom 에 대한 mapper 클래스입니다.
* ChatRoom 도메인 관련 mapper 입니다.
*
* @author jack8
* @since 2025-05-22
* @author sleepyhoon
* @see
* @since 2026-01-27
*/
@Mapper(componentModel = "spring")
public interface ChatRoomMapper {
/**
* 트랜잭션 내에서만 처리되어야 합니다.
*/
@Mapping(target = "userId", source = "member.id")
@Mapping(target = "imageUrl", source = "member.imageUrl")
@Mapping(target = "nickname", source = "member.nickname")
ChatRoomInfoRes.UserInfo toDto(ChatRoomMember entity);
@Component
public class ChatRoomMapper {

public ChatRoomInfoRes.UserInfo toDto(ChatRoomMember entity, ObjectStorage objectStorage) {
return UserInfo.builder()
.userId(entity.getMember().getId())
.nickname(entity.getMember().getNickname())
.role(entity.getRole())
// TODO: 채팅방 이미지도 minio로 이동해야함. 아직 구현되지 않음.
.imageUrl(objectStorage.convertKeyToFileUrl(entity.getChatRoom().getImageUrl()))
.build();
}

/**
* 단일 ChatRoomMember 객체와 최신 메시지 조회 결과를 기반으로
Expand All @@ -35,7 +40,7 @@ public interface ChatRoomMapper {
* @param latestInfos 채팅방별 최신 메시지 및 언리드 정보
* @return ChatRoomListRes.ChatRoomInfo 변환 결과
*/
default ChatRoomListRes.ChatRoomInfo toChatRoomInfo(
public ChatRoomListRes.ChatRoomInfo toChatRoomInfo(
ChatRoomMember chatRoomMember, Map<String, ChatroomLatestInfo> latestInfos) {
String chatRoomId = chatRoomMember.getChatRoom().getId();
ChatroomLatestInfo info = latestInfos.get(chatRoomId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.studypals.domain.chatManage.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;

import com.studypals.global.file.entity.ImageFile;

/**
* 채팅방(ChatRoom) 내에서 전송된 이미지의 메타데이터를 관리하는 엔티티입니다.
* <p>
* 이 엔티티는 {@link ImageFile}을 상속받아 이미지 파일의 공통 속성을 관리하며,
* {@link ChatRoom}과 다대일(Many-to-One) 관계를 맺습니다.
* 채팅 이미지는 한 번 생성되면 수정되지 않는 불변(Immutable)의 특성을 가집니다.
*
* <p><b>주요 특징:</b>
* <ul>
* <li><b>상속 관계:</b> {@link ImageFile}의 모든 속성을 상속받습니다.</li>
* <li><b>연관 관계:</b> 여러 개의 채팅 이미지가 하나의 {@link ChatRoom}에 속합니다.</li>
* <li><b>불변성:</b> 생성 후 상태가 변경되지 않습니다. (수정 기능 없음)</li>
* <li><b>인덱싱:</b> 이미지 처리 상태({@code imageStatus})와 생성일({@code createdAt})에 복합 인덱스가 설정되어 있어,
* 리사이징 등 비동기 처리 대상 조회 시 성능을 향상시킵니다.</li>
* </ul>
*
* @author sleepyhoon
* @since 2026-01-15
* @see ImageFile
* @see ChatRoom
* @see com.studypals.domain.chatManage.worker.ChatImageManager
*/
@Entity
@Getter
@SuperBuilder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(
name = "chat_image",
indexes = @Index(name = "idx_chat_image_status_created_at", columnList = "imageStatus, createdAt"))
public class ChatImage extends ImageFile {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "chat_room_id", nullable = false)
private ChatRoom chatRoom;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.studypals.domain.memberManage.worker.MemberReader;
import com.studypals.global.exceptions.errorCode.ChatErrorCode;
import com.studypals.global.exceptions.exception.ChatException;
import com.studypals.global.file.ObjectStorage;

/**
* 채팅방 진입 시 필요한 정보를 조회하는 서비스 구현 클래스입니다.
Expand Down Expand Up @@ -53,6 +54,8 @@ public class ChatRoomServiceImpl implements ChatRoomService {
private final ChatMessageReader chatMessageReader;
private final MemberReader memberReader;

private final ObjectStorage objectStorage;

/**
* 특정 유저가 특정 채팅방에 입장할 때 필요한 전체 정보를 조회합니다.
* <p>
Expand Down Expand Up @@ -112,7 +115,9 @@ public ChatRoomInfoRes getChatRoomInfo(Long userId, String chatRoomId, String ch
return ChatRoomInfoRes.builder()
.roomId(chatRoomId)
.name(chatRoom.getName())
.userInfos(members.stream().map(chatRoomMapper::toDto).toList())
.userInfos(members.stream()
.map(m -> chatRoomMapper.toDto(m, objectStorage))
.toList())
.cursor(chatCursorRes)
.logs(logs)
.build();
Expand Down
Loading