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
@@ -1,7 +1,6 @@
package im.toduck.domain.backoffice.domain.usecase;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
Expand Down Expand Up @@ -30,8 +29,6 @@
@UseCase
@RequiredArgsConstructor
public class StatisticsUseCase {
private static final int MAX_STATISTICS_DATE_RANGE_DAYS = 31;

private final UserService userService;
private final DiaryService diaryService;
private final RoutineService routineService;
Expand Down Expand Up @@ -81,15 +78,19 @@ public MultiDateStatisticsResponse getMultiDateStatistics(
) {
validateDateRange(startDate, endDate);

Map<StatisticsType, Map<LocalDate, Long>> statisticsByType = new EnumMap<>(StatisticsType.class);
for (StatisticsType type : types) {
statisticsByType.put(type, getDailyCountsByTypeAndDateRange(type, startDate, endDate));
}

List<DailyStatisticsResponse> statisticsDataList = new ArrayList<>();
LocalDate currentDate = startDate;

while (!currentDate.isAfter(endDate)) {
Map<StatisticsType, Long> counts = new EnumMap<>(StatisticsType.class);

for (StatisticsType type : types) {
long count = getStatisticsCountByTypeAndDate(type, currentDate);
counts.put(type, count);
counts.put(type, statisticsByType.get(type).getOrDefault(currentDate, 0L));
}

statisticsDataList.add(StatisticsMapper.toDailyStatisticsResponse(currentDate, counts));
Expand All @@ -108,11 +109,6 @@ private void validateDateRange(final LocalDate startDate, final LocalDate endDat
if (startDate.isAfter(endDate)) {
throw CommonException.from(ExceptionCode.INVALID_STATISTICS_DATE_RANGE);
}

long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
if (daysBetween > MAX_STATISTICS_DATE_RANGE_DAYS) {
throw CommonException.from(ExceptionCode.INVALID_STATISTICS_DATE_RANGE);
}
}

private long getStatisticsCountByTypeAndDate(final StatisticsType type, final LocalDate date) {
Expand Down Expand Up @@ -155,4 +151,19 @@ private long getStatisticsCountByTypeAndDateRange(
};
}

private Map<LocalDate, Long> getDailyCountsByTypeAndDateRange(
final StatisticsType type,
final LocalDate startDate,
final LocalDate endDate
) {
return switch (type) {
case NEW_USERS -> userService.getNewUsersCountByDateRangeGroupByDate(startDate, endDate);
case DELETED_USERS -> userService.getDeletedUsersCountByDateRangeGroupByDate(startDate, endDate);
case NEW_ROUTINES -> routineService.getRoutineCountByDateRangeGroupByDate(startDate, endDate);
case NEW_DIARIES -> diaryService.getDiaryCountByDateRangeGroupByDate(startDate, endDate);
case NEW_SOCIAL_POSTS -> socialBoardService.getSocialPostsCountByDateRangeGroupByDate(startDate, endDate);
case NEW_COMMENTS -> socialBoardService.getCommentsCountByDateRangeGroupByDate(startDate, endDate);
case NEW_SCHEDULES -> scheduleReadService.getSchedulesCountByDateRangeGroupByDate(startDate, endDate);
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import java.time.YearMonth;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -23,6 +25,7 @@
import im.toduck.domain.user.persistence.entity.User;
import im.toduck.global.exception.CommonException;
import im.toduck.global.exception.ExceptionCode;
import im.toduck.global.persistence.projection.DailyCount;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

Expand Down Expand Up @@ -143,4 +146,20 @@ public Diary getDiaryByIdAndUserId(Long userId, Long diaryId) {
return diaryRepository.getDiaryByUserIdAndId(userId, diaryId)
.orElseThrow(() -> CommonException.from(ExceptionCode.NOT_FOUND_DIARY));
}

@Transactional(readOnly = true)
public Map<LocalDate, Long> getDiaryCountByDateRangeGroupByDate(
final LocalDate startDate,
final LocalDate endDate
) {
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);

List<DailyCount> dailyCounts = diaryRepository.countByCreatedAtBetweenGroupByDate(
startDateTime, endDateTime
);

return dailyCounts.stream()
.collect(Collectors.toMap(DailyCount::date, DailyCount::count));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
import org.springframework.stereotype.Repository;

import im.toduck.domain.diary.persistence.entity.Diary;
import im.toduck.domain.diary.persistence.repository.querydsl.DiaryRepositoryCustom;
import im.toduck.domain.user.persistence.entity.User;
import jakarta.validation.constraints.NotNull;

@Repository
public interface DiaryRepository extends JpaRepository<Diary, Long> {
public interface DiaryRepository extends JpaRepository<Diary, Long>, DiaryRepositoryCustom {
List<Diary> findByUserIdAndDateBetweenOrderByDateDesc(Long userId, LocalDate startDate, LocalDate endDate);

Diary findByUserIdAndDate(Long userId, @NotNull(message = "날짜는 비어있을 수 없습니다.") LocalDate date);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package im.toduck.domain.diary.persistence.repository.querydsl;

import java.time.LocalDateTime;
import java.util.List;

import im.toduck.global.persistence.projection.DailyCount;

public interface DiaryRepositoryCustom {
List<DailyCount> countByCreatedAtBetweenGroupByDate(
final LocalDateTime startDateTime,
final LocalDateTime endDateTime
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package im.toduck.domain.diary.persistence.repository.querydsl;

import java.time.LocalDateTime;
import java.util.List;

import org.springframework.stereotype.Repository;

import com.querydsl.jpa.impl.JPAQueryFactory;

import im.toduck.domain.diary.persistence.entity.QDiary;
import im.toduck.global.persistence.helper.DailyCountQueryHelper;
import im.toduck.global.persistence.projection.DailyCount;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class DiaryRepositoryCustomImpl implements DiaryRepositoryCustom {
private final JPAQueryFactory queryFactory;
private final QDiary qDiary = QDiary.diary;

@Override
public List<DailyCount> countByCreatedAtBetweenGroupByDate(
final LocalDateTime startDateTime,
final LocalDateTime endDateTime
) {
return DailyCountQueryHelper.countGroupByDate(
queryFactory, qDiary, qDiary.createdAt, qDiary.count(), startDateTime, endDateTime
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import im.toduck.domain.user.persistence.entity.User;
import im.toduck.global.exception.CommonException;
import im.toduck.global.exception.ExceptionCode;
import im.toduck.global.persistence.projection.DailyCount;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

Expand Down Expand Up @@ -176,4 +177,20 @@ public long getRoutineCountByDateRange(final LocalDate startDate, final LocalDat
public long getActiveRoutineUsersCount() {
return routineRepository.countDistinctUsers();
}

@Transactional(readOnly = true)
public Map<LocalDate, Long> getRoutineCountByDateRangeGroupByDate(
final LocalDate startDate,
final LocalDate endDate
) {
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);

List<DailyCount> dailyCounts = routineRepository.countByCreatedAtBetweenGroupByDate(
startDateTime, endDateTime
);

return dailyCounts.stream()
.collect(Collectors.toMap(DailyCount::date, DailyCount::count));
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package im.toduck.domain.routine.persistence.repository.querydsl;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import im.toduck.domain.routine.persistence.entity.Routine;
import im.toduck.domain.routine.persistence.entity.RoutineRecord;
import im.toduck.domain.user.persistence.entity.User;
import im.toduck.global.persistence.projection.DailyCount;

public interface RoutineRepositoryCustom {
List<Routine> findUnrecordedRoutinesByDateMatchingDayOfWeek(
Expand All @@ -25,4 +27,9 @@ List<Routine> findRoutinesByDateBetween(
void deleteAllUnsharedRoutinesByUser(User user);

List<Routine> findActiveRoutinesWithReminderForDates(LocalDate startDate, LocalDate endDate);

List<DailyCount> countByCreatedAtBetweenGroupByDate(
final LocalDateTime startDateTime,
final LocalDateTime endDateTime
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import im.toduck.domain.routine.persistence.entity.RoutineRecord;
import im.toduck.domain.user.persistence.entity.User;
import im.toduck.global.helper.DaysOfWeekBitmask;
import im.toduck.global.persistence.helper.DailyCountQueryHelper;
import im.toduck.global.persistence.projection.DailyCount;
import lombok.RequiredArgsConstructor;

@Repository
Expand Down Expand Up @@ -141,4 +143,14 @@ private BooleanExpression routineMatchesDateRange(final LocalDate startDate, fin
Byte.class, "function('bitand', {0}, CAST({1} as byte))", qRoutine.daysOfWeekBitmask, periodDaysBitmask
).gt((byte)0);
}

@Override
public List<DailyCount> countByCreatedAtBetweenGroupByDate(
final LocalDateTime startDateTime,
final LocalDateTime endDateTime
) {
return DailyCountQueryHelper.countGroupByDate(
queryFactory, qRoutine, qRoutine.createdAt, qRoutine.count(), startDateTime, endDateTime
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -20,6 +22,7 @@
import im.toduck.domain.user.persistence.entity.User;
import im.toduck.global.exception.CommonException;
import im.toduck.global.exception.ExceptionCode;
import im.toduck.global.persistence.projection.DailyCount;
import lombok.RequiredArgsConstructor;

@Service
Expand Down Expand Up @@ -74,4 +77,19 @@ public long getSchedulesCountByDateRange(final LocalDate startDate, final LocalD
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
return scheduleRepository.countByCreatedAtBetween(startDateTime, endDateTime);
}

@Transactional(readOnly = true)
public Map<LocalDate, Long> getSchedulesCountByDateRangeGroupByDate(
final LocalDate startDate,
final LocalDate endDate
) {
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);

List<DailyCount> dailyCounts = scheduleRepository.countByCreatedAtBetweenGroupByDate(
startDateTime, endDateTime
);

return dailyCounts.stream().collect(Collectors.toMap(DailyCount::date, DailyCount::count));
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package im.toduck.domain.schedule.persistence.repository.querydsl;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import im.toduck.domain.schedule.persistence.entity.Schedule;
import im.toduck.global.persistence.projection.DailyCount;

public interface ScheduleRepositoryCustom {
List<Schedule> findSchedules(Long userId, LocalDate startDate, LocalDate endDate);

List<DailyCount> countByCreatedAtBetweenGroupByDate(
final LocalDateTime startDateTime,
final LocalDateTime endDateTime
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package im.toduck.domain.schedule.persistence.repository.querydsl;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

import org.springframework.stereotype.Repository;
Expand All @@ -10,6 +11,8 @@

import im.toduck.domain.schedule.persistence.entity.QSchedule;
import im.toduck.domain.schedule.persistence.entity.Schedule;
import im.toduck.global.persistence.helper.DailyCountQueryHelper;
import im.toduck.global.persistence.projection.DailyCount;
import lombok.RequiredArgsConstructor;

@Repository
Expand Down Expand Up @@ -55,4 +58,14 @@ private BooleanExpression isPeriodEvent(LocalDate startDate, LocalDate endDate)
.and(schedule.scheduleDate.endDate.goe(startDate)) // 조회 시작일이 일정 종료일보다 같거나 작아야 함
.and(schedule.scheduleDate.startDate.loe(endDate)); // 일정 시작일이 조회 종료일보다 같거나 작아야 함
}

@Override
public List<DailyCount> countByCreatedAtBetweenGroupByDate(
final LocalDateTime startDateTime,
final LocalDateTime endDateTime
) {
return DailyCountQueryHelper.countGroupByDate(
queryFactory, schedule, schedule.createdAt, schedule.count(), startDateTime, endDateTime
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -36,6 +38,7 @@
import im.toduck.domain.user.persistence.entity.User;
import im.toduck.global.exception.CommonException;
import im.toduck.global.exception.ExceptionCode;
import im.toduck.global.persistence.projection.DailyCount;
import im.toduck.global.util.PaginationUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand Down Expand Up @@ -285,5 +288,37 @@ public long getCommentsCountByDateRange(final LocalDate startDate, final LocalDa
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);
return commentRepository.countByCreatedAtBetween(startDateTime, endDateTime);
}

@Transactional(readOnly = true)
public Map<LocalDate, Long> getSocialPostsCountByDateRangeGroupByDate(
final LocalDate startDate,
final LocalDate endDate
) {
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);

List<DailyCount> dailyCounts = socialRepository.countByCreatedAtBetweenGroupByDate(
startDateTime, endDateTime
);

return dailyCounts.stream()
.collect(Collectors.toMap(DailyCount::date, DailyCount::count));
}

@Transactional(readOnly = true)
public Map<LocalDate, Long> getCommentsCountByDateRangeGroupByDate(
final LocalDate startDate,
final LocalDate endDate
) {
LocalDateTime startDateTime = startDate.atStartOfDay();
LocalDateTime endDateTime = endDate.atTime(LocalTime.MAX);

List<DailyCount> dailyCounts = commentRepository.countByCreatedAtBetweenGroupByDate(
startDateTime, endDateTime
);

return dailyCounts.stream()
.collect(Collectors.toMap(DailyCount::date, DailyCount::count));
}
}

Loading
Loading