diff --git a/src/main/java/io/oeid/mogakgo/common/swagger/template/ChatSwagger.java b/src/main/java/io/oeid/mogakgo/common/swagger/template/ChatSwagger.java index e418e484..14c18345 100644 --- a/src/main/java/io/oeid/mogakgo/common/swagger/template/ChatSwagger.java +++ b/src/main/java/io/oeid/mogakgo/common/swagger/template/ChatSwagger.java @@ -2,14 +2,18 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.core.properties.swagger.error.SwaggerChatErrorExamples; import io.oeid.mogakgo.core.properties.swagger.error.SwaggerProjectErrorExamples; import io.oeid.mogakgo.core.properties.swagger.error.SwaggerUserErrorExamples; +import io.oeid.mogakgo.domain.chat.application.dto.res.ChatDataRes; import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomDataRes; import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomPublicRes; import io.oeid.mogakgo.exception.dto.ErrorResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; @@ -28,11 +32,11 @@ public interface ChatSwagger { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "채팅방 목록 조회 성공"), @ApiResponse(responseCode = "404", description = "요청한 유저가 존재하지 않음", - content = @Content( - mediaType = APPLICATION_JSON_VALUE, - schema = @Schema(implementation = ErrorResponse.class), - examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) - )) + content = @Content( + mediaType = APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) + )) }) ResponseEntity> getChatRoomList(@Parameter(hidden = true) Long userId); @@ -40,16 +44,40 @@ public interface ChatSwagger { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "채팅방 상세 조회 성공"), @ApiResponse(responseCode = "404", description = "요청한 데이터가 유효하지 않음", - content = @Content( - mediaType = APPLICATION_JSON_VALUE, - schema = @Schema(implementation = ErrorResponse.class), - examples = { - @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND), - @ExampleObject(name = "E030301", value = SwaggerProjectErrorExamples.PROJECT_NOT_FOUND), - @ExampleObject(name = "E110301", value = SwaggerChatErrorExamples.CHAT_ROOM_NOT_FOUND) - } - )) + content = @Content( + mediaType = APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND), + @ExampleObject(name = "E030301", value = SwaggerProjectErrorExamples.PROJECT_NOT_FOUND), + @ExampleObject(name = "E110301", value = SwaggerChatErrorExamples.CHAT_ROOM_NOT_FOUND) + } + )) }) - ResponseEntity getChatRoomDetailData(@Parameter(hidden = true) Long userId, + ResponseEntity getChatRoomDetailData( + @Parameter(hidden = true) Long userId, @Parameter(in = ParameterIn.PATH) String chatRoomId); + + @Operation(summary = "채팅 내용 조회", description = "채팅방의 채팅 내용을 조회하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "채팅 내용 조회 성공"), + @ApiResponse(responseCode = "404", description = "요청한 데이터가 유효하지 않음", + content = @Content( + mediaType = APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND), + @ExampleObject(name = "E110301", value = SwaggerChatErrorExamples.CHAT_ROOM_NOT_FOUND) + } + )) + }) + @Parameters({ + @Parameter(name = "cursorId", description = "기준이 되는 커서 ID", example = "1"), + @Parameter(name = "pageSize", description = "요청할 데이터 크기", example = "5", required = true), + @Parameter(name = "sortOrder", description = "정렬 방향", example = "ASC"), + }) + ResponseEntity> getChatData( + @Parameter(in = ParameterIn.PATH) String chatRoomId, + @Parameter(hidden = true) Long userId, + @Parameter(hidden = true) CursorPaginationInfoReq pageable); } diff --git a/src/main/java/io/oeid/mogakgo/common/swagger/template/ProjectSwagger.java b/src/main/java/io/oeid/mogakgo/common/swagger/template/ProjectSwagger.java index 06961b05..0f751f8d 100644 --- a/src/main/java/io/oeid/mogakgo/common/swagger/template/ProjectSwagger.java +++ b/src/main/java/io/oeid/mogakgo/common/swagger/template/ProjectSwagger.java @@ -10,6 +10,7 @@ import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailAPIRes; import io.oeid.mogakgo.domain.project.presentation.dto.req.ProjectCreateReq; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectIdRes; +import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectInfoAPIRes; import io.oeid.mogakgo.domain.project_join_req.presentation.dto.res.projectJoinRequestRes; import io.oeid.mogakgo.exception.dto.ErrorResponse; import io.swagger.v3.oas.annotations.Operation; @@ -179,4 +180,53 @@ ResponseEntity> getJoinRequest( content = @Content(schema = @Schema(implementation = ProjectDensityRankRes.class))), }) ResponseEntity getDensityRankProjects(); + + @Operation(summary = "사용자가 생성한 프로젝트 리스트 조회", description = "회원이 자신이 만든 프로젝트 리스트를 조회할 때 사용하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "프로젝트 리스트 조회 성공"), + @ApiResponse(responseCode = "403", description = "본인의 프로젝트 카드만 조회 할 수 있음.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E030101", value = SwaggerProjectErrorExamples.PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION))), + @ApiResponse(responseCode = "404", description = "요청한 데이터가 존재하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) + )) + }) + @Parameters({ + @Parameter(name = "cursorId", description = "기준이 되는 커서 ID", example = "1"), + @Parameter(name = "pageSize", description = "요청할 데이터 크기", example = "5", required = true), + @Parameter(name = "sortOrder", description = "정렬 방향", example = "ASC"), + }) + ResponseEntity> getProjectsByCreator( + @Parameter(hidden = true) Long userId, + @Parameter(description = "프로젝트를 생성한 사용자 ID", required = true) Long creatorId, + @Parameter(hidden = true) CursorPaginationInfoReq pageable + ); + + @Operation(summary = "사용자가 생성한 프로젝트 상세 정보 조회", description = "회원이 자신이 생성한 프로젝트의 상세 정보를 조회할 때 사용하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "프로젝트 조회 성공"), + @ApiResponse(responseCode = "403", description = "본인의 프로젝트 카드만 조회 할 수 있음.", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E030101", value = SwaggerProjectErrorExamples.PROJECT_JOIN_REQUEST_FORBIDDEN_OPERATION))), + @ApiResponse(responseCode = "404", description = "요청한 데이터가 존재하지 않음", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = ErrorResponse.class), + examples = { + @ExampleObject(name = "E030301", value = SwaggerProjectErrorExamples.PROJECT_NOT_FOUND), + @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND) + })) + }) + ResponseEntity getById( + @Parameter(hidden = true) Long userId, + @Parameter(description = "조회하려는 프로젝트 ID", required = true) Long projectId, + @Parameter(description = "프로젝트 생성자 ID", required = true) Long id + ); } diff --git a/src/main/java/io/oeid/mogakgo/common/swagger/template/UserSwagger.java b/src/main/java/io/oeid/mogakgo/common/swagger/template/UserSwagger.java index 82fef30f..a2836b2f 100644 --- a/src/main/java/io/oeid/mogakgo/common/swagger/template/UserSwagger.java +++ b/src/main/java/io/oeid/mogakgo/common/swagger/template/UserSwagger.java @@ -3,6 +3,7 @@ import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import io.oeid.mogakgo.core.properties.swagger.error.SwaggerUserErrorExamples; +import io.oeid.mogakgo.domain.user.application.dto.res.UserJandiRateRes; import io.oeid.mogakgo.domain.user.presentation.dto.req.UserSignUpApiReq; import io.oeid.mogakgo.domain.user.presentation.dto.req.UserUpdateApiReq; import io.oeid.mogakgo.domain.user.presentation.dto.res.UserDevelopLanguageApiRes; @@ -13,6 +14,7 @@ import io.oeid.mogakgo.exception.dto.ErrorResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.Schema; @@ -109,5 +111,18 @@ ResponseEntity> userDevelopLanguageApi( }) ResponseEntity userUpdateApi(@Parameter(hidden = true) Long userId, UserUpdateApiReq request); + + + @Operation(summary = "회원 잔디 점수 조회", description = "회원의 잔디 점수를 조회할 때 사용하는 API") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "회원 잔디 점수 조회 성공", + content = @Content(schema = @Schema(implementation = UserJandiRateRes.class))), + @ApiResponse(responseCode = "404", description = "해당 유저가 존재하지 않음", + content = @Content( + mediaType = APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ErrorResponse.class), + examples = @ExampleObject(name = "E020301", value = SwaggerUserErrorExamples.USER_NOT_FOUND))), + }) + ResponseEntity userJandiRateApi(@Parameter(in = ParameterIn.PATH) Long userId); } diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/domain/Achievement.java b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/Achievement.java similarity index 59% rename from src/main/java/io/oeid/mogakgo/domain/achievement/domain/Achievement.java rename to src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/Achievement.java index 6052841c..16ea0038 100644 --- a/src/main/java/io/oeid/mogakgo/domain/achievement/domain/Achievement.java +++ b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/Achievement.java @@ -1,6 +1,7 @@ -package io.oeid.mogakgo.domain.achievement.domain; +package io.oeid.mogakgo.domain.achievement.domain.entity; -import io.oeid.mogakgo.domain.achievement.domain.enums.AchievementType; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.RequirementType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EnumType; @@ -9,7 +10,6 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -33,16 +33,18 @@ public class Achievement { @Column(name = "img_url") private String imgUrl; + @Column(name = "progress_level") + private Integer progressLevel; + @Enumerated(EnumType.STRING) - @Column(name = "type") - private AchievementType achievementType; - - @Builder - private Achievement(String title, String description, String imgUrl, - AchievementType achievementType) { - this.title = title; - this.description = description; - this.imgUrl = imgUrl; - this.achievementType = achievementType; - } + @Column(name = "requirement_type") + private RequirementType requirementType; + + @Column(name = "requirement_value") + private Integer requirementValue; + + @Enumerated(EnumType.STRING) + @Column(name = "activity_type") + private ActivityType activityType; + } diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/UserAchievement.java b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/UserAchievement.java new file mode 100644 index 00000000..d0053180 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/UserAchievement.java @@ -0,0 +1,51 @@ +package io.oeid.mogakgo.domain.achievement.domain.entity; + +import io.oeid.mogakgo.domain.user.domain.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Getter +@Entity +@Table(name = "user_achievement_tb") +@NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +public class UserAchievement { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "achievement_id") + private Achievement achievement; + + // TODO: 필요하면 개발하다가 추가 +// @Column(name = "progress_count") +// private Integer progressCount; + + @Column(name = "completed") + private Boolean completed; + + @CreatedDate + @Column(name = "created_at") + private LocalDateTime createdAt; + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/UserActivity.java b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/UserActivity.java new file mode 100644 index 00000000..b5d1aa66 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/UserActivity.java @@ -0,0 +1,48 @@ +package io.oeid.mogakgo.domain.achievement.domain.entity; + +import io.oeid.mogakgo.domain.achievement.domain.entity.enums.ActivityType; +import io.oeid.mogakgo.domain.user.domain.User; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Getter +@Entity +@Table(name = "user_activity_tb") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@EntityListeners(AuditingEntityListener.class) +public class UserActivity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User user; + + @Enumerated(EnumType.STRING) + @Column(name = "activity_type") + private ActivityType activityType; + + @CreatedDate + @Column(name = "created_at") + private LocalDateTime createdAt; + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/enums/ActivityType.java b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/enums/ActivityType.java new file mode 100644 index 00000000..94eef4de --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/enums/ActivityType.java @@ -0,0 +1,31 @@ +package io.oeid.mogakgo.domain.achievement.domain.entity.enums; + +import java.util.List; +import lombok.Getter; + +@Getter +public enum ActivityType { + + FROM_ONE_STEP(List.of(2, 3, 4)), + GOOD_PERSON_GOOD_MEETUP(List.of(5, 6, 7)), + LIKE_E(List.of(8, 9, 10)), + MY_DESTINY(List.of(11)), + CAPTURE_FAIL_EXIST(List.of(12)), + RUN_AWAY_FROM_MONSTAR_BALL(List.of(13)), + PLEASE_GIVE_ME_MOGAK(List.of(14, 15, 16)), + BRAVE_EXPLORER(List.of(17)), + NOMAD_CODER(List.of(18)), + CATCH_ME_IF_YOU_CAN(List.of(19, 20, 21)), + LEAVE_YOUR_MARK(List.of(22)), + WHAT_A_POPULAR_PERSON(List.of(23, 24, 25)), + CONTACT_WITH_GOD(List.of(26)), + FRESH_DEVELOPER(List.of(28, 29, 30)), + ; + + private final List includedDbIdList; + + ActivityType(List includedDbIdList) { + this.includedDbIdList = includedDbIdList; + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/enums/RequirementType.java b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/enums/RequirementType.java new file mode 100644 index 00000000..ee757b34 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/entity/enums/RequirementType.java @@ -0,0 +1,8 @@ +package io.oeid.mogakgo.domain.achievement.domain.entity.enums; + +public enum RequirementType { + SEQUENCE, + ACCUMULATE, + ; + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/domain/enums/AchievementType.java b/src/main/java/io/oeid/mogakgo/domain/achievement/domain/enums/AchievementType.java deleted file mode 100644 index 3f2faaad..00000000 --- a/src/main/java/io/oeid/mogakgo/domain/achievement/domain/enums/AchievementType.java +++ /dev/null @@ -1,6 +0,0 @@ -package io.oeid.mogakgo.domain.achievement.domain.enums; - -public enum AchievementType { - ONCE - -} diff --git a/src/main/java/io/oeid/mogakgo/domain/achievement/infrastructure/AchievementJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/achievement/infrastructure/AchievementJpaRepository.java index cac992d4..8b1f5e56 100644 --- a/src/main/java/io/oeid/mogakgo/domain/achievement/infrastructure/AchievementJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/achievement/infrastructure/AchievementJpaRepository.java @@ -1,10 +1,10 @@ package io.oeid.mogakgo.domain.achievement.infrastructure; -import io.oeid.mogakgo.domain.achievement.domain.Achievement; +import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface AchievementJpaRepository extends JpaRepository { -} \ No newline at end of file +} diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatIdSequenceGeneratorService.java b/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatIdSequenceGeneratorService.java new file mode 100644 index 00000000..2914e089 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatIdSequenceGeneratorService.java @@ -0,0 +1,26 @@ +package io.oeid.mogakgo.domain.chat.application; + +import static org.springframework.data.mongodb.core.FindAndModifyOptions.options; +import static org.springframework.data.mongodb.core.query.Criteria.where; +import static org.springframework.data.mongodb.core.query.Query.query; + +import io.oeid.mogakgo.domain.chat.entity.document.ChatIdSequence; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ChatIdSequenceGeneratorService { + + private final MongoOperations mongoOperations; + + public Long generateSequence(String collectionName) { + ChatIdSequence counter = mongoOperations.findAndModify( + query(where("_id").is(collectionName)), new Update().inc("seq", 1), + options().returnNew(true).upsert(true), ChatIdSequence.class); + return !Objects.isNull(counter) ? counter.getSeq() : 1; + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatService.java b/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatService.java index 19ca7d41..d5b4fc9f 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatService.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatService.java @@ -1,10 +1,13 @@ package io.oeid.mogakgo.domain.chat.application; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.domain.chat.application.dto.req.ChatRoomCreateReq; +import io.oeid.mogakgo.domain.chat.application.dto.res.ChatDataRes; import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomCreateRes; import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomDataRes; import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomPublicRes; -import io.oeid.mogakgo.domain.chat.entity.document.ChatRoom; +import io.oeid.mogakgo.domain.chat.entity.ChatRoom; import io.oeid.mogakgo.domain.chat.infrastructure.ChatRepository; import io.oeid.mogakgo.domain.chat.infrastructure.ChatRoomRoomJpaRepository; import io.oeid.mogakgo.domain.matching.exception.MatchingException; @@ -52,14 +55,24 @@ public ChatRoomCreateRes createChatRoom(Long creatorId, ChatRoomCreateReq reques } // 채팅방 조회 - public ChatRoomDataRes findAllChatInChatRoom(Long userId, String chatRoomId) { + public CursorPaginationResult findAllChatInChatRoom(Long userId, String chatRoomId, CursorPaginationInfoReq pageable) { var user = userCommonService.getUserById(userId); - var chatRoom = chatRoomRepository.findById(chatRoomId) - .orElseThrow(() -> new MatchingException(ErrorCode404.CHAT_ROOM_NOT_FOUND)); + var chatRoom = findChatRoomById(chatRoomId); + chatRoom.validateContainsUser(user); + return chatRepository.findAllByCollection(chatRoomId, pageable); + } + + public ChatRoomDataRes findChatRoomDetailData(Long userId, String chatRoomId) { + var user = userCommonService.getUserById(userId); + var chatRoom = findChatRoomById(chatRoomId); chatRoom.validateContainsUser(user); var project = projectRepository.findById(chatRoom.getProject().getId()) .orElseThrow(() -> new ProjectException(ErrorCode404.PROJECT_NOT_FOUND)); - var chatList = chatRepository.findAllByCollection(chatRoomId); - return ChatRoomDataRes.of(project.getMeetingInfo(), chatList); + return ChatRoomDataRes.from(project.getMeetingInfo()); + } + + private ChatRoom findChatRoomById(String chatRoomId) { + return chatRoomRepository.findById(chatRoomId) + .orElseThrow(() -> new MatchingException(ErrorCode404.CHAT_ROOM_NOT_FOUND)); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatWebSocketService.java b/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatWebSocketService.java index 5082dabd..7d5570ee 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatWebSocketService.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/application/ChatWebSocketService.java @@ -3,8 +3,8 @@ import static io.oeid.mogakgo.exception.code.ErrorCode500.CHAT_WEB_SOCKET_ERROR; +import io.oeid.mogakgo.domain.chat.entity.ChatRoom; import io.oeid.mogakgo.domain.chat.entity.document.ChatMessage; -import io.oeid.mogakgo.domain.chat.entity.document.ChatRoom; import io.oeid.mogakgo.domain.chat.entity.enums.ChatStatus; import io.oeid.mogakgo.domain.chat.exception.ChatException; import io.oeid.mogakgo.domain.chat.infrastructure.ChatRepository; diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/application/vo/ChatData.java b/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatDataRes.java similarity index 65% rename from src/main/java/io/oeid/mogakgo/domain/chat/application/vo/ChatData.java rename to src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatDataRes.java index cdd0b1c3..fede07d8 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/application/vo/ChatData.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatDataRes.java @@ -1,4 +1,4 @@ -package io.oeid.mogakgo.domain.chat.application.vo; +package io.oeid.mogakgo.domain.chat.application.dto.res; import io.oeid.mogakgo.domain.chat.entity.document.ChatMessage; import io.oeid.mogakgo.domain.chat.entity.enums.ChatMessageType; @@ -11,8 +11,10 @@ @Schema(description = "채팅 데이터") @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) -public class ChatData { +public class ChatDataRes { + @Schema(description = "채팅 ID") + private Long id; @Schema(description = "메시지 타입") private ChatMessageType messageType; @Schema(description = "보낸 사람 ID") @@ -22,8 +24,8 @@ public class ChatData { @Schema(description = "생성 시간") private LocalDateTime createdAt; - public static ChatData from(ChatMessage chatMessage) { - return new ChatData(chatMessage.getMessageType(), chatMessage.getSenderId(), - chatMessage.getMessage(), chatMessage.getCreatedAt()); + public static ChatDataRes from(ChatMessage chatMessage) { + return new ChatDataRes(chatMessage.getId(), chatMessage.getMessageType(), + chatMessage.getSenderId(), chatMessage.getMessage(), chatMessage.getCreatedAt()); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomCreateRes.java b/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomCreateRes.java index 7faa2598..a59a0325 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomCreateRes.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomCreateRes.java @@ -1,6 +1,6 @@ package io.oeid.mogakgo.domain.chat.application.dto.res; -import io.oeid.mogakgo.domain.chat.entity.document.ChatRoom; +import io.oeid.mogakgo.domain.chat.entity.ChatRoom; import io.oeid.mogakgo.domain.chat.entity.enums.ChatStatus; import lombok.Getter; diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomDataRes.java b/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomDataRes.java index 978d10fa..1074fa6f 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomDataRes.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomDataRes.java @@ -1,28 +1,28 @@ package io.oeid.mogakgo.domain.chat.application.dto.res; -import io.oeid.mogakgo.domain.chat.application.vo.ChatData; -import io.oeid.mogakgo.domain.chat.application.vo.ChatRoomProjectInfo; -import io.oeid.mogakgo.domain.chat.entity.document.ChatMessage; import io.oeid.mogakgo.domain.project.domain.entity.vo.MeetingInfo; import io.swagger.v3.oas.annotations.media.Schema; -import java.util.List; +import java.time.LocalDateTime; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; -@Schema(description = "채팅방 데이터 조회 응답") +@Schema(description = "채팅방 프로젝트 정보") @Getter @AllArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor public class ChatRoomDataRes { - private ChatRoomProjectInfo project; - private List data; + @Schema(description = "프로젝트 설명") + private String meetDetail; + @Schema(description = "프로젝트 시작 시간") + private LocalDateTime meetStartTime; + @Schema(description = "프로젝트 종료 시간") + private LocalDateTime meetEndTime; - public static ChatRoomDataRes of(MeetingInfo meetingInfo, List data) { - ChatRoomProjectInfo project = new ChatRoomProjectInfo(meetingInfo.getMeetDetail(), - meetingInfo.getMeetStartTime(), meetingInfo.getMeetEndTime()); - return new ChatRoomDataRes(project, data.stream().map(ChatData::from).toList()); + public static ChatRoomDataRes from(MeetingInfo meetingInfo) { + return new ChatRoomDataRes(meetingInfo.getMeetDetail(), meetingInfo.getMeetStartTime(), + meetingInfo.getMeetEndTime()); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomPublicRes.java b/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomPublicRes.java index 6a80338e..6dda8cce 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomPublicRes.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/application/dto/res/ChatRoomPublicRes.java @@ -5,23 +5,32 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; import java.util.List; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.Setter; @Schema(description = "채팅방 리스트 조회 응답") @Getter -@AllArgsConstructor public class ChatRoomPublicRes { @Schema(description = "프로젝트 ID") private Long projectId; @Schema(description = "채팅방 ID") private String chatRoomId; + @Setter @Schema(description = "마지막 메시지") private String lastMessage; + @Setter @Schema(description = "마지막 메시지 생성 시간") private LocalDateTime lastMessageCreatedAt; @Schema(description = "채팅방 상태") private ChatStatus status; private List profiles; + + public ChatRoomPublicRes(Long projectId, String chatRoomId, ChatStatus status, + List profiles) { + this.projectId = projectId; + this.chatRoomId = chatRoomId; + this.status = status; + this.profiles = profiles; + } } diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/application/vo/ChatRoomProjectInfo.java b/src/main/java/io/oeid/mogakgo/domain/chat/application/vo/ChatRoomProjectInfo.java deleted file mode 100644 index ad285193..00000000 --- a/src/main/java/io/oeid/mogakgo/domain/chat/application/vo/ChatRoomProjectInfo.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.oeid.mogakgo.domain.chat.application.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; -import lombok.AllArgsConstructor; -import lombok.Getter; - -@Schema(description = "채팅방 프로젝트 정보") -@Getter -@AllArgsConstructor -public class ChatRoomProjectInfo { - - @Schema(description = "프로젝트 설명") - private String meetDetail; - @Schema(description = "프로젝트 시작 시간") - private LocalDateTime meetStartTime; - @Schema(description = "프로젝트 종료 시간") - private LocalDateTime meetEndTime; -} diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatRoom.java b/src/main/java/io/oeid/mogakgo/domain/chat/entity/ChatRoom.java similarity index 97% rename from src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatRoom.java rename to src/main/java/io/oeid/mogakgo/domain/chat/entity/ChatRoom.java index d661d1be..25b1e12d 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatRoom.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/entity/ChatRoom.java @@ -1,4 +1,4 @@ -package io.oeid.mogakgo.domain.chat.entity.document; +package io.oeid.mogakgo.domain.chat.entity; import io.oeid.mogakgo.domain.chat.entity.enums.ChatStatus; import io.oeid.mogakgo.domain.chat.exception.ChatException; diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatIdSequence.java b/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatIdSequence.java new file mode 100644 index 00000000..15e154b0 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatIdSequence.java @@ -0,0 +1,15 @@ +package io.oeid.mogakgo.domain.chat.entity.document; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Setter +@Getter +@Document(collection = "chat_sequence") +public class ChatIdSequence { + @Id + private String id; + private Long seq; +} diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatMessage.java b/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatMessage.java index 657ca5ff..8ab2ca9a 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatMessage.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/entity/document/ChatMessage.java @@ -3,17 +3,22 @@ import io.oeid.mogakgo.domain.chat.entity.enums.ChatMessageType; import java.time.LocalDateTime; import lombok.Getter; +import lombok.Setter; +import org.springframework.data.mongodb.core.mapping.Document; @Getter +@Document public class ChatMessage { - + @Setter + private Long id; private ChatMessageType messageType; private String chatRoomId; private Long senderId; private String message; private LocalDateTime createdAt; - public ChatMessage(ChatMessageType messageType, String chatRoomId, Long senderId, String message) { + public ChatMessage(ChatMessageType messageType, String chatRoomId, Long senderId, + String message) { this.messageType = messageType; this.chatRoomId = chatRoomId; this.senderId = senderId; diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/handler/CustomWebSocketHandler.java b/src/main/java/io/oeid/mogakgo/domain/chat/handler/CustomWebSocketHandler.java index c6b4e579..7eb976a6 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/handler/CustomWebSocketHandler.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/handler/CustomWebSocketHandler.java @@ -3,9 +3,10 @@ import static io.oeid.mogakgo.exception.code.ErrorCode500.CHAT_WEB_SOCKET_ERROR; import com.fasterxml.jackson.databind.ObjectMapper; +import io.oeid.mogakgo.domain.chat.application.ChatIdSequenceGeneratorService; import io.oeid.mogakgo.domain.chat.application.ChatWebSocketService; +import io.oeid.mogakgo.domain.chat.entity.ChatRoom; import io.oeid.mogakgo.domain.chat.entity.document.ChatMessage; -import io.oeid.mogakgo.domain.chat.entity.document.ChatRoom; import io.oeid.mogakgo.domain.chat.exception.ChatException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,6 +24,7 @@ public class CustomWebSocketHandler implements WebSocketHandler { private final ObjectMapper objectMapper; private final ChatWebSocketService chatWebSocketService; + private final ChatIdSequenceGeneratorService sequenceGeneratorService; @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { @@ -44,6 +46,7 @@ public void handleMessage(WebSocketSession session, WebSocketMessage message) } default -> chatWebSocketService.sendMessageToEachSocket(chatRoom.getId(), textMessage); } + chatMessage.setId(sequenceGeneratorService.generateSequence(chatMessage.getChatRoomId())); chatWebSocketService.saveChatMessage(chatMessage, chatRoom.getId()); } diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRepository.java b/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRepository.java index 50a5b616..0f2bd131 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRepository.java @@ -1,10 +1,13 @@ package io.oeid.mogakgo.domain.chat.infrastructure; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; +import io.oeid.mogakgo.domain.chat.application.dto.res.ChatDataRes; import io.oeid.mogakgo.domain.chat.entity.document.ChatMessage; -import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Repository; @@ -24,9 +27,24 @@ public ChatMessage save(ChatMessage chatMessage, String collectionName) { return mongoTemplate.save(chatMessage, collectionName); } - public List findAllByCollection(String collectionName) { + public CursorPaginationResult findAllByCollection(String collectionName, + CursorPaginationInfoReq pageable) { Query query = new Query(); - query.with(Sort.by(Sort.Order.desc("createdAt"))); - return mongoTemplate.find(query, ChatMessage.class, collectionName); + query.limit(pageable.getPageSize()).addCriteria(cursorIdCondition(pageable.getCursorId())) + .with(Sort.by(Sort.Order.desc("createdAt"))); + var result = mongoTemplate.find(query, ChatMessage.class, collectionName).stream() + .map(ChatDataRes::from).toList(); + return CursorPaginationResult.fromDataWithHasNext(result, pageable.getPageSize(), + hasNext(pageable.getCursorId(), pageable.getPageSize(), collectionName)); + } + + private Criteria cursorIdCondition(Long cursorId) { + return cursorId != null ? Criteria.where("id").lt(cursorId) : Criteria.where("id").gt(0L); + } + + private boolean hasNext(Long cursorId, int pageSize, String collectionName) { + return cursorId == null ? mongoTemplate.estimatedCount(collectionName) > pageSize + : mongoTemplate.count(new Query(Criteria.where("id").lt(cursorId)), ChatMessage.class, + collectionName) > pageSize; } } diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRoomCustomRepositoryImpl.java b/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRoomCustomRepositoryImpl.java index 02ba1299..c6ebd618 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRoomCustomRepositoryImpl.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRoomCustomRepositoryImpl.java @@ -1,12 +1,14 @@ package io.oeid.mogakgo.domain.chat.infrastructure; -import static io.oeid.mogakgo.domain.chat.entity.document.QChatRoom.chatRoom; -import static io.oeid.mogakgo.domain.user.domain.QUser.user; + + +import static io.oeid.mogakgo.domain.chat.entity.QChatRoom.chatRoom; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomPublicRes; import io.oeid.mogakgo.domain.chat.application.vo.ChatUserInfo; +import io.oeid.mogakgo.domain.user.domain.QUser; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; @@ -18,13 +20,15 @@ public class ChatRoomCustomRepositoryImpl implements ChatRoomCustomRepository { private final JPAQueryFactory jpaQueryFactory; public List findAllChatRoomByUserId(Long userId) { + + QUser sender = new QUser("sender"); + QUser creator = new QUser("creator"); + return jpaQueryFactory.select( Projections.constructor( ChatRoomPublicRes.class, chatRoom.project.id, chatRoom.id, - null, - null, chatRoom.status, Projections.list(List.of( Projections.constructor( @@ -42,7 +46,7 @@ public List findAllChatRoomByUserId(Long userId) { ) ) ) - ).from(chatRoom).join(chatRoom.creator, user).join(chatRoom.sender, user) + ).from(chatRoom).join(chatRoom.creator, creator).join(chatRoom.sender, sender) .where(chatRoom.creator.id.eq(userId).or(chatRoom.sender.id.eq(userId))) .fetch(); } diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRoomRoomJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRoomRoomJpaRepository.java index e11a1e61..4ed03099 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRoomRoomJpaRepository.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/infrastructure/ChatRoomRoomJpaRepository.java @@ -1,6 +1,6 @@ package io.oeid.mogakgo.domain.chat.infrastructure; -import io.oeid.mogakgo.domain.chat.entity.document.ChatRoom; +import io.oeid.mogakgo.domain.chat.entity.ChatRoom; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/io/oeid/mogakgo/domain/chat/presentation/ChatController.java b/src/main/java/io/oeid/mogakgo/domain/chat/presentation/ChatController.java index 3803eadc..e916f352 100644 --- a/src/main/java/io/oeid/mogakgo/domain/chat/presentation/ChatController.java +++ b/src/main/java/io/oeid/mogakgo/domain/chat/presentation/ChatController.java @@ -1,18 +1,25 @@ package io.oeid.mogakgo.domain.chat.presentation; import io.oeid.mogakgo.common.annotation.UserId; +import io.oeid.mogakgo.common.base.CursorPaginationInfoReq; +import io.oeid.mogakgo.common.base.CursorPaginationResult; import io.oeid.mogakgo.common.swagger.template.ChatSwagger; import io.oeid.mogakgo.domain.chat.application.ChatService; +import io.oeid.mogakgo.domain.chat.application.dto.res.ChatDataRes; import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomDataRes; import io.oeid.mogakgo.domain.chat.application.dto.res.ChatRoomPublicRes; +import jakarta.validation.Valid; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; + +// TODO: FIX SWAGGER @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/chat") @@ -25,9 +32,16 @@ public ResponseEntity> getChatRoomList(@UserId Long user return ResponseEntity.ok(chatService.findAllChatRoomByUserId(userId)); } - @GetMapping("/{chatRoomId}") + @GetMapping("detail/{chatRoomId}") public ResponseEntity getChatRoomDetailData(@UserId Long userId, @PathVariable String chatRoomId) { - return ResponseEntity.ok(chatService.findAllChatInChatRoom(userId, chatRoomId)); + return ResponseEntity.ok(chatService.findChatRoomDetailData(userId, chatRoomId)); + } + + @GetMapping("/{chatRoomId}") + public ResponseEntity> getChatData( + @PathVariable String chatRoomId, + @UserId Long userId, @Valid @ModelAttribute CursorPaginationInfoReq pageable) { + return ResponseEntity.ok(chatService.findAllChatInChatRoom(userId, chatRoomId, pageable)); } } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java index c98d0faf..cb265bb4 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardLikeRepositoryCustomImpl.java @@ -58,6 +58,8 @@ public CursorPaginationResult getLikeInfoBySender( cursorIdCondition(pageable.getCursorId()), senderIdEq(senderId) ) + // 최근순 + .orderBy(profileCardLike.id.desc()) .limit(pageable.getPageSize() + 1) .fetch(); @@ -83,6 +85,6 @@ private BooleanExpression receiveridEq(Long receiverId) { } private BooleanExpression cursorIdCondition(Long cursorId) { - return cursorId != null ? profileCardLike.id.gt(cursorId) : null; + return cursorId != null ? profileCardLike.id.lt(cursorId) : null; } } diff --git a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardRepositoryCustomImpl.java b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardRepositoryCustomImpl.java index a8d898d6..7684dd55 100644 --- a/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardRepositoryCustomImpl.java +++ b/src/main/java/io/oeid/mogakgo/domain/profile/infrastructure/ProfileCardRepositoryCustomImpl.java @@ -35,6 +35,8 @@ public CursorPaginationResult findByConditionWithPaginati regionEq(region), deletedProfileCardEq() ) + // 최근순 + .orderBy(profileCard.id.desc()) .limit(pageable.getPageSize() + 1) .fetch(); @@ -69,7 +71,7 @@ private BooleanExpression userIdEq(Long userId) { } private BooleanExpression cursorIdCondition(Long cursorId) { - return cursorId != null ? profileCard.id.gt(cursorId) : null; + return cursorId != null ? profileCard.id.lt(cursorId) : null; } private BooleanExpression deletedProfileCardEq() { diff --git a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java index 8adb4acf..3c4dbe7c 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/application/ProjectService.java @@ -22,6 +22,7 @@ import io.oeid.mogakgo.domain.project.presentation.dto.req.ProjectCreateReq; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDensityRankRes; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailAPIRes; +import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectInfoAPIRes; import io.oeid.mogakgo.domain.project_join_req.exception.ProjectJoinRequestException; import io.oeid.mogakgo.domain.project_join_req.infrastructure.ProjectJoinRequestJpaRepository; import io.oeid.mogakgo.domain.project_join_req.presentation.dto.res.projectJoinRequestRes; @@ -150,6 +151,23 @@ public CursorPaginationResult getRandomOrderedProjectsByReg return projects; } + public CursorPaginationResult getByCreatorId( + Long userId, Long creatorId, CursorPaginationInfoReq pageable + ) { + User user = getUser(userId); + validateProjectCardCreator(user, creatorId); + + return projectJpaRepository.findByCreatorIdWithPagination(creatorId, pageable); + } + + public ProjectDetailAPIRes getByProjectId(Long userId, Long id, Long projectId) { + User user = getUser(userId); + + validateProjectCardCreator(user, id); + + return ProjectDetailAPIRes.from(getProject(projectId)); + } + public ProjectDensityRankRes getDensityRankProjects() { List regionRankList = projectJpaRepository.getDensityRankProjectsByRegion(DENSITY_RANK_LIMIT); diff --git a/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/Project.java b/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/Project.java index 0fa3b11e..f940fc51 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/Project.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/domain/entity/Project.java @@ -101,7 +101,7 @@ public void cancel(User tokenUser, boolean projectHasReq) { validateAvailableCancel(tokenUser); // 매칭 준비중이지만 요청이 있을때는 잔디력 감소 if (projectHasReq) { - this.creator.decreaseJandiRate(); + this.creator.updateJandiRateByCancel(); } this.projectStatus = ProjectStatus.CANCELED; } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustom.java b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustom.java index a3fa995b..7f70b44a 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustom.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustom.java @@ -5,6 +5,7 @@ import io.oeid.mogakgo.domain.geo.domain.enums.Region; import io.oeid.mogakgo.domain.project.domain.entity.enums.ProjectStatus; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailAPIRes; +import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectInfoAPIRes; import java.util.List; public interface ProjectRepositoryCustom { @@ -13,5 +14,9 @@ CursorPaginationResult findByConditionWithPagination( Long userId, Region region, ProjectStatus projectStatus, CursorPaginationInfoReq pageable ); + CursorPaginationResult findByCreatorIdWithPagination( + Long userId, CursorPaginationInfoReq pageable + ); + List getDensityRankProjectsByRegion(int limit); } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustomImpl.java b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustomImpl.java index 20c73887..0bd67e07 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustomImpl.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/infrastructure/ProjectRepositoryCustomImpl.java @@ -13,6 +13,7 @@ import io.oeid.mogakgo.domain.project.domain.entity.enums.ProjectStatus; import io.oeid.mogakgo.domain.project.presentation.dto.res.MeetingInfoResponse; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailAPIRes; +import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectInfoAPIRes; import io.oeid.mogakgo.domain.user.domain.UserDevelopLanguageTag; import io.oeid.mogakgo.domain.user.domain.UserWantedJobTag; import io.oeid.mogakgo.domain.user.presentation.dto.res.UserPublicApiResponse; @@ -43,6 +44,8 @@ public CursorPaginationResult findByConditionWithPagination projectStatusEq(projectStatus), createdAtEq(today) ) + // 최근순 + .orderBy(project.id.desc()) .limit(pageable.getPageSize() + 1) .fetch(); @@ -78,6 +81,37 @@ public CursorPaginationResult findByConditionWithPagination ); } + @Override + public CursorPaginationResult findByCreatorIdWithPagination( + Long userId, CursorPaginationInfoReq pageable + ) { + List entities = jpaQueryFactory.selectFrom(project) + .innerJoin(project.creator, user) + .on(project.creator.id.eq(user.id)) + .where( + userIdEq(userId), + deletedAtEq() + ) + .orderBy(project.id.desc()) + .limit(pageable.getPageSize() + 1) + .fetch(); + + List result = entities.stream().map( + project -> new ProjectInfoAPIRes( + project.getId(), + project.getProjectStatus(), + project.getCreator().getAvatarUrl(), + project.getMeetingInfo().getMeetDetail(), + project.getMeetingInfo().getMeetStartTime(), + project.getMeetingInfo().getMeetEndTime() + ) + ).toList(); + + return CursorPaginationResult.fromDataWithExtraItemForNextCheck( + result, pageable.getPageSize() + ); + } + @Override public List getDensityRankProjectsByRegion(int limit) { return jpaQueryFactory.select(project.creatorInfo.region) @@ -90,7 +124,7 @@ public List getDensityRankProjectsByRegion(int limit) { } private BooleanExpression cursorIdCondition(Long cursorId) { - return cursorId != null ? project.id.gt(cursorId) : null; + return cursorId != null ? project.id.lt(cursorId) : null; } private BooleanExpression userIdEq(Long userId) { @@ -110,4 +144,8 @@ private BooleanExpression createdAtEq(LocalDate today) { .and(project.createdAt.month().eq(today.getMonthValue())) .and(project.createdAt.dayOfMonth().eq(today.getDayOfMonth())); } + + private BooleanExpression deletedAtEq() { + return project.deletedAt.isNull(); + } } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/presentation/ProjectController.java b/src/main/java/io/oeid/mogakgo/domain/project/presentation/ProjectController.java index 8b66ceed..03750d30 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/presentation/ProjectController.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/presentation/ProjectController.java @@ -10,6 +10,7 @@ import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDensityRankRes; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectDetailAPIRes; import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectIdRes; +import io.oeid.mogakgo.domain.project.presentation.dto.res.ProjectInfoAPIRes; import io.oeid.mogakgo.domain.project_join_req.presentation.dto.res.projectJoinRequestRes; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -78,4 +79,20 @@ public ResponseEntity getDensityRankProjects() { return ResponseEntity.ok().body(projectService.getDensityRankProjects()); } + @GetMapping("/list/{creatorId}") + public ResponseEntity> getProjectsByCreator( + @UserId Long userId, @PathVariable Long creatorId, + @Valid @ModelAttribute CursorPaginationInfoReq pageable + ) { + return ResponseEntity.ok() + .body(projectService.getByCreatorId(userId, creatorId, pageable)); + } + + @GetMapping("{projectId}/{id}") + public ResponseEntity getById( + @UserId Long userId, @PathVariable Long projectId, @PathVariable Long id + ) { + return ResponseEntity.ok().body(projectService.getByProjectId(userId, id, projectId)); + } + } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectDetailAPIRes.java b/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectDetailAPIRes.java index de231dd3..18bf7bbf 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectDetailAPIRes.java +++ b/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectDetailAPIRes.java @@ -1,6 +1,10 @@ package io.oeid.mogakgo.domain.project.presentation.dto.res; +import io.oeid.mogakgo.domain.project.domain.entity.Project; +import io.oeid.mogakgo.domain.project.domain.entity.ProjectTag; import io.oeid.mogakgo.domain.project.domain.entity.enums.ProjectStatus; +import io.oeid.mogakgo.domain.user.domain.UserDevelopLanguageTag; +import io.oeid.mogakgo.domain.user.domain.UserWantedJobTag; import io.oeid.mogakgo.domain.user.presentation.dto.res.UserPublicApiResponse; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; @@ -46,4 +50,31 @@ public static ProjectDetailAPIRes of(Long projectId, UserPublicApiResponse creat meetingInfo ); } + + public static ProjectDetailAPIRes from(Project project) { + return ProjectDetailAPIRes.of(project.getId(), + new UserPublicApiResponse( + project.getCreator().getId(), + project.getCreator().getUsername(), + project.getCreator().getGithubId(), + project.getCreator().getAvatarUrl(), + project.getCreator().getGithubUrl(), + project.getCreator().getBio(), + project.getCreator().getJandiRate(), + project.getCreator().getAchievement() != null ? project.getCreator() + .getAchievement().getTitle() : null, + project.getCreator().getUserDevelopLanguageTags().stream().map( + UserDevelopLanguageTag::getDevelopLanguage).map(String::valueOf).toList(), + project.getCreator().getUserWantedJobTags().stream().map( + UserWantedJobTag::getWantedJob).map(String::valueOf).toList() + ), + project.getProjectStatus(), + project.getProjectTags().stream().map(ProjectTag::getContent).toList(), + new MeetingInfoResponse( + project.getMeetingInfo().getMeetStartTime(), + project.getMeetingInfo().getMeetEndTime(), + project.getMeetingInfo().getMeetDetail() + ) + ); + } } diff --git a/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectInfoAPIRes.java b/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectInfoAPIRes.java new file mode 100644 index 00000000..08e85c87 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/project/presentation/dto/res/ProjectInfoAPIRes.java @@ -0,0 +1,38 @@ +package io.oeid.mogakgo.domain.project.presentation.dto.res; + +import io.oeid.mogakgo.domain.project.domain.entity.enums.ProjectStatus; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Schema(description = "사용자가 생성한 프로젝트 리스트 조회 응답 DTO") +@Getter +@AllArgsConstructor +public class ProjectInfoAPIRes { + + @Schema(description = "생성한 프로젝트 ID", example = "11", implementation = Long.class) + @NotNull + private final Long projectId; + + @Schema(description = "프로젝트 상태") + @NotNull + private final ProjectStatus projectStatus; + + @Schema(description = "프로젝트 생성자 Url") + @NotNull + private final String creatorAvatorUrl; + + @Schema(description = "프로젝트 위치 상세") + @NotNull + private final String projectLocationDetail; + + @Schema(description = "프로젝트 시작 시간") + @NotNull + private final LocalDateTime projectStartTime; + + @Schema(description = "프로젝트 종료 시간") + @NotNull + private final LocalDateTime projectEndTime; +} diff --git a/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestRepositoryCustomImpl.java b/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestRepositoryCustomImpl.java index a2e05315..3114f1ef 100644 --- a/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestRepositoryCustomImpl.java +++ b/src/main/java/io/oeid/mogakgo/domain/project_join_req/infrastructure/ProjectJoinRequestRepositoryCustomImpl.java @@ -84,11 +84,13 @@ public CursorPaginationResult getBySenderIdWithP ) { List entities = jpaQueryFactory.selectFrom(projectJoinRequest) .where( - cursorIdCondition(pageable.getCursorId()), + cursorIdConditionForDesc(pageable.getCursorId()), senderIdEq(senderId), projectIdEq(projectId), requestStatusEq(requestStatus) ) + // 최근순 + .orderBy(projectJoinRequest.id.desc()) .limit(pageable.getPageSize() + 1) .fetch(); @@ -125,4 +127,8 @@ private BooleanExpression cursorIdCondition(Long cursorId) { return cursorId != null ? projectJoinRequest.id.gt(cursorId) : null; } + private BooleanExpression cursorIdConditionForDesc(Long cursorId) { + return cursorId != null ? projectJoinRequest.id.lt(cursorId) : null; + } + } diff --git a/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewService.java b/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewService.java new file mode 100644 index 00000000..48e1ab90 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/review/application/ReviewService.java @@ -0,0 +1,56 @@ +package io.oeid.mogakgo.domain.review.application; + +import io.oeid.mogakgo.domain.project.infrastructure.ProjectJpaRepository; +import io.oeid.mogakgo.domain.review.application.dto.req.ReviewCreateReq; +import io.oeid.mogakgo.domain.review.application.dto.res.ReviewCreateRes; +import io.oeid.mogakgo.domain.review.domain.Review; +import io.oeid.mogakgo.domain.review.exception.ReviewException; +import io.oeid.mogakgo.domain.review.infrastructure.ReviewJpaRepository; +import io.oeid.mogakgo.domain.user.application.UserCommonService; +import io.oeid.mogakgo.exception.code.ErrorCode400; +import io.oeid.mogakgo.exception.code.ErrorCode404; +import java.time.Duration; +import java.time.LocalDateTime; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ReviewService { + + private final ReviewJpaRepository reviewRepository; + private final ProjectJpaRepository projectRepository; + private final UserCommonService userCommonService; + + @Transactional + public ReviewCreateRes createNewReview(ReviewCreateReq request) { + reviewRepository.findReviewByProjectData(request.getSenderId(), request.getReceiverId(), + request.getProjectId()).ifPresent(review -> { + throw new ReviewException(ErrorCode400.REVIEW_ALREADY_EXISTS); + }); + var sender = userCommonService.getUserById(request.getSenderId()); + var receiver = userCommonService.getUserById(request.getReceiverId()); + var project = projectRepository.findById(request.getProjectId()) + .orElseThrow(() -> new ReviewException(ErrorCode404.PROJECT_NOT_FOUND)); + var review = reviewRepository.save(Review.builder() + .sender(sender) + .receiver(receiver) + .project(project) + .rating(request.getRating()) + .build() + ); + receiver.updateJandiRateByReview(review.getRating(), + calculateProjectTime(project.getMeetingInfo().getMeetStartTime(), + project.getMeetingInfo().getMeetEndTime())); + return ReviewCreateRes.from(review); + } + + private double calculateProjectTime(LocalDateTime meetStartTime, LocalDateTime meetEndTime) { + Duration duration = Duration.between(meetStartTime, meetEndTime); + double hours = duration.toHours(); + double minutes = duration.toMinutes() % 60; + return hours + minutes / 60; + } + +} diff --git a/src/main/java/io/oeid/mogakgo/domain/review/application/dto/req/ReviewCreateReq.java b/src/main/java/io/oeid/mogakgo/domain/review/application/dto/req/ReviewCreateReq.java new file mode 100644 index 00000000..2aa0ba1f --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/review/application/dto/req/ReviewCreateReq.java @@ -0,0 +1,14 @@ +package io.oeid.mogakgo.domain.review.application.dto.req; + +import io.oeid.mogakgo.domain.review.domain.enums.ReviewRating; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ReviewCreateReq { + private Long senderId; + private Long receiverId; + private Long projectId; + private ReviewRating rating; +} diff --git a/src/main/java/io/oeid/mogakgo/domain/review/application/dto/res/ReviewCreateRes.java b/src/main/java/io/oeid/mogakgo/domain/review/application/dto/res/ReviewCreateRes.java new file mode 100644 index 00000000..6f14a1e9 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/review/application/dto/res/ReviewCreateRes.java @@ -0,0 +1,30 @@ +package io.oeid.mogakgo.domain.review.application.dto.res; + +import io.oeid.mogakgo.domain.review.domain.Review; +import io.oeid.mogakgo.domain.review.domain.enums.ReviewRating; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ReviewCreateRes { + private Long id; + private Long senderId; + private Long receiverId; + private Long projectId; + private ReviewRating rating; + private LocalDateTime createdAt; + + public static ReviewCreateRes from(Review review) { + return new ReviewCreateRes( + review.getId(), + review.getSender().getId(), + review.getReceiver().getId(), + review.getProject().getId(), + review.getRating(), + review.getCreatedAt() + ); + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/review/domain/Review.java b/src/main/java/io/oeid/mogakgo/domain/review/domain/Review.java new file mode 100644 index 00000000..7c9d9719 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/review/domain/Review.java @@ -0,0 +1,81 @@ +package io.oeid.mogakgo.domain.review.domain; + +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import io.oeid.mogakgo.domain.project.domain.entity.Project; +import io.oeid.mogakgo.domain.review.domain.enums.ReviewRating; +import io.oeid.mogakgo.domain.review.exception.ReviewException; +import io.oeid.mogakgo.domain.user.domain.User; +import io.oeid.mogakgo.exception.code.ErrorCode400; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; + +@Getter +@Entity +@Table(name = "review_tb") +@NoArgsConstructor(access = PROTECTED) +public class Review { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne + @JoinColumn(name = "sender_id") + private User sender; + + @ManyToOne + @JoinColumn(name = "receiver_id") + private User receiver; + + @ManyToOne + @JoinColumn(name = "project_id") + private Project project; + + @Enumerated(EnumType.STRING) + @Column(name = "rating") + private ReviewRating rating; + + @CreationTimestamp + @Column(name = "created_at") + LocalDateTime createdAt; + + @Builder + private Review(User sender, User receiver, Project project, ReviewRating rating) { + validateUsers(sender, receiver); + this.sender = sender; + this.receiver = receiver; + this.project = validateProject(project); + this.rating = rating; + } + + private void validateUsers(User sender, User receiver) { + if (sender == null || receiver == null) { + throw new ReviewException(ErrorCode400.REVIEW_SENDER_OR_RECEIVER_NOT_FOUND); + } + if (sender.getId().equals(receiver.getId())) { + throw new ReviewException(ErrorCode400.REVIEW_USER_DUPLICATED); + } + } + + private Project validateProject(Project project) { + if (project == null) { + throw new ReviewException(ErrorCode400.REVIEW_PROJECT_NOT_NULL); + } + return project; + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/review/domain/enums/ReviewRating.java b/src/main/java/io/oeid/mogakgo/domain/review/domain/enums/ReviewRating.java new file mode 100644 index 00000000..29deeb42 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/review/domain/enums/ReviewRating.java @@ -0,0 +1,18 @@ +package io.oeid.mogakgo.domain.review.domain.enums; + +import lombok.Getter; + +@Getter +public enum ReviewRating { + ONE(-2), + TWO(-1), + THREE(1), + FOUR(2), + FIVE(3); + + private final int value; + + ReviewRating(int value) { + this.value = value; + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/review/exception/ReviewException.java b/src/main/java/io/oeid/mogakgo/domain/review/exception/ReviewException.java new file mode 100644 index 00000000..9eba9500 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/review/exception/ReviewException.java @@ -0,0 +1,11 @@ +package io.oeid.mogakgo.domain.review.exception; + +import io.oeid.mogakgo.exception.code.ErrorCode; +import io.oeid.mogakgo.exception.exception_class.CustomException; + +public class ReviewException extends CustomException { + + public ReviewException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/review/infrastructure/ReviewJpaRepository.java b/src/main/java/io/oeid/mogakgo/domain/review/infrastructure/ReviewJpaRepository.java new file mode 100644 index 00000000..f01b4769 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/review/infrastructure/ReviewJpaRepository.java @@ -0,0 +1,14 @@ +package io.oeid.mogakgo.domain.review.infrastructure; + +import io.oeid.mogakgo.domain.review.domain.Review; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +@Repository +public interface ReviewJpaRepository extends JpaRepository { + + @Query("select r from Review r where r.sender.id = ?1 and r.receiver.id = ?2 and r.project.id = ?3") + Optional findReviewByProjectData(Long id, Long id1, Long id2); +} \ No newline at end of file diff --git a/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java b/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java index c53bc647..fc4d493a 100644 --- a/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java +++ b/src/main/java/io/oeid/mogakgo/domain/user/application/UserService.java @@ -1,12 +1,13 @@ package io.oeid.mogakgo.domain.user.application; -import io.oeid.mogakgo.domain.achievement.domain.Achievement; +import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; import io.oeid.mogakgo.domain.achievement.infrastructure.AchievementJpaRepository; import io.oeid.mogakgo.domain.profile.application.ProfileCardService; import io.oeid.mogakgo.domain.profile.application.dto.req.UserProfileCardReq; import io.oeid.mogakgo.domain.user.application.dto.req.UserSignUpRequest; import io.oeid.mogakgo.domain.user.application.dto.req.UserUpdateReq; import io.oeid.mogakgo.domain.user.application.dto.res.UserDevelopLanguageRes; +import io.oeid.mogakgo.domain.user.application.dto.res.UserJandiRateRes; import io.oeid.mogakgo.domain.user.application.dto.res.UserProfileResponse; import io.oeid.mogakgo.domain.user.application.dto.res.UserSignUpResponse; import io.oeid.mogakgo.domain.user.application.dto.res.UserUpdateRes; @@ -82,6 +83,7 @@ public UserProfileResponse getUserProfile(Long userId) { } // TODO: 이후 AchievementException 구현 시 추가 필요! + @Transactional public UserUpdateRes updateUserInfos(Long userId, UserUpdateReq request) { User user = userCommonService.getUserById(userId); Achievement achievement = achievementRepository.findById(request.getAchievementId()) @@ -105,6 +107,11 @@ public void deleteUser(Long userId) { user.delete(); } + public UserJandiRateRes getUserJandiRate(Long userId) { + User user = userCommonService.getUserById(userId); + return UserJandiRateRes.of(user.getId(), user.getJandiRate()); + } + private void validateWantedJobDuplicate(List wantedJobs) { Set wantedJobSet = new HashSet<>(wantedJobs); if (wantedJobSet.size() != wantedJobs.size()) { diff --git a/src/main/java/io/oeid/mogakgo/domain/user/application/dto/res/UserJandiRateRes.java b/src/main/java/io/oeid/mogakgo/domain/user/application/dto/res/UserJandiRateRes.java new file mode 100644 index 00000000..49615284 --- /dev/null +++ b/src/main/java/io/oeid/mogakgo/domain/user/application/dto/res/UserJandiRateRes.java @@ -0,0 +1,21 @@ +package io.oeid.mogakgo.domain.user.application.dto.res; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Schema(description = "유저의 잔디 점수") +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class UserJandiRateRes { + + @Schema(description = "유저 아이디") + private Long userId; + @Schema(description = "유저의 잔디 점수") + private Double jandiRate; + + public static UserJandiRateRes of(Long userId, Double jandiRate) { + return new UserJandiRateRes(userId, jandiRate); + } +} diff --git a/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java b/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java index 029b54d6..2ed682d9 100644 --- a/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java +++ b/src/main/java/io/oeid/mogakgo/domain/user/domain/User.java @@ -3,8 +3,9 @@ import static io.oeid.mogakgo.exception.code.ErrorCode400.USER_AVAILABLE_LIKE_AMOUNT_IS_FULL; import static io.oeid.mogakgo.exception.code.ErrorCode400.USER_AVAILABLE_LIKE_COUNT_IS_ZERO; -import io.oeid.mogakgo.domain.achievement.domain.Achievement; +import io.oeid.mogakgo.domain.achievement.domain.entity.Achievement; import io.oeid.mogakgo.domain.geo.domain.enums.Region; +import io.oeid.mogakgo.domain.review.domain.enums.ReviewRating; import io.oeid.mogakgo.domain.user.domain.enums.Role; import io.oeid.mogakgo.domain.user.exception.UserException; import io.oeid.mogakgo.exception.code.ErrorCode400; @@ -43,6 +44,7 @@ public class User { private static final int MAX_TAG_SIZE = 3; private static final int MAX_AVAILABLE_LIKE_COUNT = 10; + private static final double JANDI_WEIGHT = 2.5; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -111,7 +113,8 @@ public class User { @JoinColumn(name = "achievement_id") private Achievement achievement; - private User(Long githubPk, String githubId, String avatarUrl, String githubUrl, String repositoryUrl) { + private User(Long githubPk, String githubId, String avatarUrl, String githubUrl, + String repositoryUrl) { this.githubPk = githubPk; this.username = githubId; this.githubId = githubId; @@ -119,11 +122,12 @@ private User(Long githubPk, String githubId, String avatarUrl, String githubUrl, this.githubUrl = githubUrl; this.repositoryUrl = repositoryUrl; this.role = Role.ROLE_USER; - this.jandiRate = 0d; + this.jandiRate = 10d; this.signupYn = false; } - public static User of(long githubPk, String username, String avatarUrl, String githubUrl, String repositoryUrl) { + public static User of(long githubPk, String username, String avatarUrl, String githubUrl, + String repositoryUrl) { return new User(githubPk, username, avatarUrl, githubUrl, repositoryUrl); } @@ -145,7 +149,8 @@ public Collection getAuthorities() { return List.of(new SimpleGrantedAuthority(role.name())); } - public void updateGithubInfo(String githubId, String avatarUrl, String githubUrl, String repositoryUrl) { + public void updateGithubInfo(String githubId, String avatarUrl, String githubUrl, + String repositoryUrl) { this.githubId = githubId; this.avatarUrl = avatarUrl; this.githubUrl = githubUrl; @@ -200,7 +205,8 @@ public void updateRegion(Region region) { } } - public void updateUserInfos(String username, String avatarUrl, String bio, Achievement achievement) { + public void updateUserInfos(String username, String avatarUrl, String bio, + Achievement achievement) { updateUsername(username); this.avatarUrl = verifyAvatarUrl(avatarUrl); this.bio = bio; @@ -208,9 +214,12 @@ public void updateUserInfos(String username, String avatarUrl, String bio, Achie deleteAllWantJobTags(); } - //TODO : 추후 구현 필요 - public void decreaseJandiRate() { - return; + public void updateJandiRateByReview(ReviewRating rating, double time) { + this.jandiRate += rating.getValue() * time * JANDI_WEIGHT; + } + + public void updateJandiRateByCancel() { + this.jandiRate += -5 * JANDI_WEIGHT; } private boolean validateAvailableRegionUpdate(Region region) { @@ -225,6 +234,4 @@ private String verifyAvatarUrl(String avatarUrl) { } - - } diff --git a/src/main/java/io/oeid/mogakgo/domain/user/presentation/UserController.java b/src/main/java/io/oeid/mogakgo/domain/user/presentation/UserController.java index 250f33c2..0f03059e 100644 --- a/src/main/java/io/oeid/mogakgo/domain/user/presentation/UserController.java +++ b/src/main/java/io/oeid/mogakgo/domain/user/presentation/UserController.java @@ -5,6 +5,7 @@ import io.oeid.mogakgo.domain.matching.application.UserMatchingService; import io.oeid.mogakgo.domain.user.application.UserService; import io.oeid.mogakgo.domain.user.application.dto.req.UserUpdateReq; +import io.oeid.mogakgo.domain.user.application.dto.res.UserJandiRateRes; import io.oeid.mogakgo.domain.user.presentation.dto.req.UserSignUpApiReq; import io.oeid.mogakgo.domain.user.presentation.dto.req.UserUpdateApiReq; import io.oeid.mogakgo.domain.user.presentation.dto.res.UserDevelopLanguageApiRes; @@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -44,6 +46,12 @@ public ResponseEntity> userDevelopLanguageApi( return ResponseEntity.ok(response.stream().map(UserDevelopLanguageApiRes::from).toList()); } + @GetMapping("/jandi-rating/{userId}") + public ResponseEntity userJandiRateApi(@PathVariable Long userId) { + var result = userService.getUserJandiRate(userId); + return ResponseEntity.ok(result); + } + @PatchMapping public ResponseEntity userUpdateApi(@UserId Long userId, @RequestBody @Valid UserUpdateApiReq request) { diff --git a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java index 1ab1ae41..84fa77a6 100644 --- a/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java +++ b/src/main/java/io/oeid/mogakgo/exception/code/ErrorCode400.java @@ -56,6 +56,11 @@ public enum ErrorCode400 implements ErrorCode { CHAT_ROOM_CLOSED("E110101", "채팅방이 종료되어 채팅을 할 수 없습니다."), CHAT_ROOM_USER_CANNOT_DUPLICATE("E110102", "채팅방에 중복된 유저가 있습니다."), CHAT_ROOM_USER_NOT_CONTAINS("E110103", "채팅방에 해당 유저가 없습니다."), + + REVIEW_SENDER_OR_RECEIVER_NOT_FOUND("E120101", "리뷰를 작성하기 위한 유저 정보가 존재하지 않습니다."), + REVIEW_USER_DUPLICATED("E120102", "자신에 대한 리뷰는 작성할 수 없습니다."), + REVIEW_PROJECT_NOT_NULL("E120103", "리뷰를 작성하기 위한 프로젝트 정보가 존재하지 않습니다."), + REVIEW_ALREADY_EXISTS("E120104", "이미 작성된 리뷰가 존재합니다."), ; private final HttpStatus httpStatus = HttpStatus.BAD_REQUEST;