Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ public class CalendarEvent extends BaseEntity {
@Column(nullable = false)
private String content;

@Column(nullable = false)
private LocalTime eventTime;

@Enumerated(EnumType.STRING)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public interface UserChecklistMapper {
@Mapping(target = "userNo", source = "userNo")
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
@Mapping(target = "deletedAt", ignore = true)
UserChecklist toUserChecklist(String userNo, CreateUserChecklistRequest request);

@Mapping(target = "userChecklistNo", ignore = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.project.dorumdorum.domain.checklist.domain.entity.RoomRule;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import java.util.List;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

Expand All @@ -16,4 +17,7 @@ public interface RoomRuleRepository extends JpaRepository<RoomRule, String> {
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE RoomRule rr SET rr.deletedAt = CURRENT_TIMESTAMP WHERE rr.room.roomNo = :roomNo AND rr.deletedAt IS NULL")
void deleteByRoomNo(@Param("roomNo") String roomNo);

@Query("SELECT rr FROM RoomRule rr JOIN FETCH rr.room r WHERE r.roomStatus = 'CONFIRM_PENDING' AND r.deletedAt IS NULL AND rr.deletedAt IS NULL")
List<RoomRule> findAllActiveWithRoom();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ public interface UserChecklistRepository extends JpaRepository<UserChecklist, St

Optional<UserChecklist> findByUserNo(String userNo);

boolean existsByUserNo(String userNo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public record RoomCreateRequest(
@NotNull Integer capacity,
@NotNull ResidencePeriod residencePeriod,
@NotBlank String title,
String notes,
@Valid @NotNull CreateRoomRuleRequest rule
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
import jakarta.validation.constraints.NotBlank;

public record UpdateRoomTitleRequest(
@NotBlank String title
@NotBlank String title,
String notes
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.project.dorumdorum.domain.room.application.dto.response;

import com.project.dorumdorum.global.pagination.CursorPage;

import java.util.List;

public record FindRoomsPageResponse(
List<FindRoomsResponse> items,
String nextCursor,
boolean hasNext,
Long totalCount
) {
public static FindRoomsPageResponse of(CursorPage<FindRoomsResponse> page, Long totalCount) {
return new FindRoomsPageResponse(page.items(), page.nextCursor(), page.hasNext(), totalCount);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,11 @@ public record FindRoomsResponse(
Integer currentMateCount,
LocalDateTime createdAt,
String title,
String notes,
String hostNickname,
String hostMajor,
String hostStudentYear,
RoomStatus roomStatus,
String residencePeriod, // 거주기간 (예: "학기(16주)", "반기(24주)", "계절학기")
Integer remaining
) {}
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.project.dorumdorum.domain.room.application.dto.response;

import com.project.dorumdorum.domain.room.domain.entity.RoomStatus;
import com.project.dorumdorum.domain.room.domain.entity.RoomType;
import lombok.Builder;

import java.util.List;

@Builder
public record RecommendedRoomResponse(
String roomNo,
String title,
RoomType roomType,
Integer capacity,
Integer currentMateCount,
Integer remaining,
RoomStatus roomStatus,
String residencePeriod,
String hostNickname,
String matchLabel,
int matchedCount,
int totalCount,
List<String> highlights
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.project.dorumdorum.domain.room.application.dto.response;

import java.util.List;

public record RecommendedRoomsPageResponse(
boolean hasChecklist,
List<RecommendedRoomResponse> items
) {}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.project.dorumdorum.domain.room.application.usecase;

import com.project.dorumdorum.domain.room.application.dto.request.ChecklistFilterRequest;
import com.project.dorumdorum.domain.room.application.dto.response.FindRoomsPageResponse;
import com.project.dorumdorum.domain.room.application.dto.response.FindRoomsResponse;
import com.project.dorumdorum.domain.room.domain.service.RoomService;
import com.project.dorumdorum.domain.user.domain.entity.Gender;
Expand Down Expand Up @@ -29,7 +30,7 @@ public class FindRoomsUseCase {
* - 필터와 커서 기준으로 방 목록을 조회
* - 다음 페이지 커서를 포함한 결과를 반환
*/
public CursorPage<FindRoomsResponse> execute(String userNo, ChecklistFilterRequest request) {
public FindRoomsPageResponse execute(String userNo, ChecklistFilterRequest request) {
Gender gender = userService.findById(userNo).getGender();
CursorQueryParams params = PaginationHelper.prepareCursorQuery(request.cursor(), LIMIT);

Expand All @@ -42,12 +43,18 @@ public CursorPage<FindRoomsResponse> execute(String userNo, ChecklistFilterReque
params.limitPlusOne()
);

return PaginationHelper.buildCursorPage(
CursorPage<FindRoomsResponse> page = PaginationHelper.buildCursorPage(
responses,
LIMIT,
last -> request.sortType() == ChecklistFilterRequest.SortType.REMAINING
? CursorCodec.encode(last.remaining(), last.createdAt(), last.roomNo())
: CursorCodec.encode(last.createdAt(), last.roomNo())
);

Long totalCount = request.cursor() == null
? roomService.countSearchResults(gender, request)
: null;

return FindRoomsPageResponse.of(page, totalCount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package com.project.dorumdorum.domain.room.application.usecase;

import com.project.dorumdorum.domain.checklist.domain.entity.ChecklistBase;
import com.project.dorumdorum.domain.checklist.domain.entity.RoomRule;
import com.project.dorumdorum.domain.checklist.domain.entity.UserChecklist;
import com.project.dorumdorum.domain.checklist.domain.repository.RoomRuleRepository;
import com.project.dorumdorum.domain.checklist.domain.repository.UserChecklistRepository;
import com.project.dorumdorum.domain.checklist.domain.service.UserChecklistService;
import com.project.dorumdorum.domain.room.application.dto.response.RecommendedRoomResponse;
import com.project.dorumdorum.domain.room.application.dto.response.RecommendedRoomsPageResponse;
import com.project.dorumdorum.domain.room.domain.entity.Room;
import com.project.dorumdorum.domain.user.domain.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class LoadRecommendedRoomsUseCase {

private static final double STRONG_MATCH_THRESHOLD = 0.85;
private static final double MATCH_THRESHOLD = 0.65;

private final UserChecklistService userChecklistService;
private final UserChecklistRepository userChecklistRepository;
private final RoomRuleRepository roomRuleRepository;
private final UserService userService;

public RecommendedRoomsPageResponse execute(String userNo) {
if (!userChecklistRepository.existsByUserNo(userNo)) {
return new RecommendedRoomsPageResponse(false, List.of());
}

UserChecklist my = userChecklistService.findByUserNo(userNo);
List<RoomRule> roomRules = roomRuleRepository.findAllActiveWithRoom();

List<RecommendedRoomResponse> items = roomRules.stream()
.map(rr -> buildResponse(my, rr))
.filter(r -> r != null)
.sorted(Comparator.comparingDouble(r -> -(double) r.matchedCount() / Math.max(r.totalCount(), 1)))
.toList();

return new RecommendedRoomsPageResponse(true, items);
}

private RecommendedRoomResponse buildResponse(UserChecklist my, RoomRule rr) {
List<FieldResult> fields = compareFields(my, rr);

int totalCount = (int) fields.stream().filter(FieldResult::filled).count();
if (totalCount == 0) return null;

int matchedCount = (int) fields.stream().filter(f -> f.filled() && f.match()).count();
double score = (double) matchedCount / totalCount;

String matchLabel;
if (score >= STRONG_MATCH_THRESHOLD) matchLabel = "잘 맞아요";
else if (score >= MATCH_THRESHOLD) matchLabel = "괜찮아요";
else return null;

List<String> highlights = fields.stream()
.filter(f -> f.filled() && f.match())
.limit(3)
.map(FieldResult::label)
.toList();

Room room = rr.getRoom();
String hostNickname = userService.findById(room.getHostUserNo()).getNickname();

return RecommendedRoomResponse.builder()
.roomNo(room.getRoomNo())
.title(room.getTitle())
.roomType(room.getRoomType())
.capacity(room.getCapacity())
.currentMateCount(room.getCurrentMateCount())
.remaining(room.getRemaining())
.roomStatus(room.getRoomStatus())
.residencePeriod(room.getResidencePeriod().name())
.hostNickname(hostNickname)
.matchLabel(matchLabel)
.matchedCount(matchedCount)
.totalCount(totalCount)
.highlights(highlights)
.build();
}

private List<FieldResult> compareFields(UserChecklist my, ChecklistBase room) {
List<FieldResult> results = new ArrayList<>();
addField(results, "취침", my.getBedtime(), room.getBedtime());
addField(results, "기상", my.getWakeUp(), room.getWakeUp());
addField(results, "귀가", my.getReturnHome(), room.getReturnHome());
addField(results, "청소", my.getCleaning(), room.getCleaning());
addField(results, "방에서 전화", my.getPhoneCall(), room.getPhoneCall());
addField(results, "잠귀", my.getSleepLight(), room.getSleepLight());
addField(results, "잠버릇", my.getSleepHabit(), room.getSleepHabit());
addField(results, "코골이", my.getSnoring(), room.getSnoring());
addField(results, "샤워 시간", my.getShowerTime(), room.getShowerTime());
addField(results, "방에서 취식", my.getEating(), room.getEating());
addField(results, "소등", my.getLightsOut(), room.getLightsOut());
addField(results, "본가 주기", my.getHomeVisit(), room.getHomeVisit());
addField(results, "흡연", my.getSmoking(), room.getSmoking());
addField(results, "냉장고", my.getRefrigerator(), room.getRefrigerator());
addField(results, "알람", my.getAlarm(), room.getAlarm());
addField(results, "이어폰", my.getEarphone(), room.getEarphone());
addField(results, "키스킨", my.getKeyskin(), room.getKeyskin());
addField(results, "더위", my.getHeat(), room.getHeat());
addField(results, "추위", my.getCold(), room.getCold());
addField(results, "공부", my.getStudy(), room.getStudy());
addField(results, "쓰레기통", my.getTrashCan(), room.getTrashCan());
return results;
}

private void addField(List<FieldResult> results, String label, Object myValue, Object roomValue) {
boolean filled = myValue != null && !(myValue instanceof String s && s.isBlank());
boolean match = filled && Objects.equals(myValue, roomValue);
results.add(new FieldResult(label, filled, match));
}

private record FieldResult(String label, boolean filled, boolean match) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ public void execute(String userNo, String roomNo, UpdateRoomTitleRequest request
throw new RestApiException(NO_PERMISSION_ON_ROOM);

room.updateTitle(request.title().trim());
room.updateNotes(request.notes());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public class Room extends BaseEntity {
@Column(nullable = false)
private String title;

@Column(columnDefinition = "TEXT")
private String notes;

@Column(nullable = false)
private String hostUserNo;

Expand Down Expand Up @@ -127,4 +130,8 @@ public void updateResidencePeriod(ResidencePeriod residencePeriod) {
public void updateTitle(String title) {
this.title = title;
}

public void updateNotes(String notes) {
this.notes = notes;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.project.dorumdorum.domain.room.domain.entity;

public enum RoomType {
TYPE_1, TYPE_2, TYPE_MEDICAL
TYPE_1, TYPE_2, TYPE_3, TYPE_MEDICAL
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ List<FindRoomsResponse> findByCursor(
int limitPlusOne
);

long countByFilter(Gender gender, ChecklistFilterRequest request);

Optional<FindRoomsResponse> findMyRoom(String userNo);

List<FindRoomsResponse> findLikedRooms(String userNo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public Room create(String userNo, Gender gender, RoomCreateRequest request) {
.roomType(request.roomType())
.residencePeriod(request.residencePeriod())
.title(request.title())
.notes(request.notes())
.hostUserNo(userNo)
.gender(gender)
.build();
Expand Down Expand Up @@ -56,6 +57,10 @@ public List<FindRoomsResponse> searchByCursor(
return roomRepository.findByCursor(gender, request, cursorCreatedAt, cursorId, cursorRemaining, limitPlusOne);
}

public long countSearchResults(Gender gender, ChecklistFilterRequest request) {
return roomRepository.countByFilter(gender, request);
}

public FindRoomsResponse findMyRoom(String userNo) {
return roomRepository.findMyRoom(userNo).orElse(null);
}
Expand Down
Loading
Loading