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 @@ -34,7 +34,6 @@

@Service
@RequiredArgsConstructor
@Transactional
public class AssignmentService {

private final AssignmentRepository assignmentRepository;
Expand Down Expand Up @@ -163,6 +162,7 @@ public ModifyAssignmentResponse modifyAssignment(
}

// 3. 과제 삭제
@Transactional
public DeleteAssignmentResponse deleteAssignment(Integer assignmentId) {

Assignment assignment = assignmentRepository.findById(assignmentId)
Expand All @@ -182,6 +182,7 @@ public DeleteAssignmentResponse deleteAssignment(Integer assignmentId) {
}

// 4-1. 나의 과제 조회 (부원)
@Transactional(readOnly = true)
public GetMyAssignmentsResponse getMyAssignments(
Long userId,
String week
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ Optional<Attendance> findByUserIdAndAttendanceCodeId(
List<Attendance> findByAttendanceCodeId(Integer id);


@Query("SELECT a.user.id, COUNT(a) FROM Attendance a WHERE a.user.id IN :userIds AND a.status = false GROUP BY a.user.id")
List<Object[]> countFailedAttendanceByUserIds(@Param("userIds") List<Long> userIds);

// 특정 날짜에 발급된 출석 코드의 개수를 세는 메서드
//long countByAttendanceDate(String attendanceDate);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,22 +64,18 @@ public AttendanceCode generateCodeAndCreateAttendances(LocalDate date) { // [수
throw new IllegalStateException("하루에 최대 3회까지만 출석 코드를 생성할 수 있습니다.");
}

// 1-3) 기존 활성화된 코드들 만료 처리
// 1-3) 기존 활성화된 코드들 만료 처리 + 보증금 일괄 재계산
List<AttendanceCode> activeCodes = attendanceCodeRepository.findByIsExpiredFalse();
for (AttendanceCode activeCode : activeCodes) {
activeCode.expire();
}

for (AttendanceCode activeCode : activeCodes) {
activeCode.expire();

List<Attendance> attendances =
attendanceRepository.findByAttendanceCodeId(activeCode.getId());

for (Attendance attendance : attendances) {
depositService.recalculateDeposit(attendance.getUser().getId());
}
}
List<Long> userIdsToRecalculate = activeCodes.stream()
.flatMap(activeCode -> attendanceRepository.findByAttendanceCodeId(activeCode.getId()).stream())
.map(attendance -> attendance.getUser().getId())
.distinct()
.toList();
depositService.recalculateDepositBatch(userIdsToRecalculate);


// 1-4) 4자리 랜덤 코드 생성 및 차수(Order) 계산
Expand Down Expand Up @@ -208,10 +204,12 @@ public String expireActiveAttendanceCode() {
List<Attendance> absents =
attendanceRepository.findByAttendanceCodeIdAndStatusFalse(attendanceCodeId);

// 4. 결석자 대상 보증금 재계산 (User ID 타입 Integer 반영)
for (Attendance attendance : absents) {
depositService.recalculateDeposit(attendance.getUser().getId());
}
// 4. 결석자 대상 보증금 일괄 재계산
List<Long> absentUserIds = absents.stream()
.map(attendance -> attendance.getUser().getId())
.distinct()
.toList();
depositService.recalculateDepositBatch(absentUserIds);

return "출석 코드가 성공적으로 만료되었습니다.";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import com.example.Piroin.project.domain.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface DepositRepository extends JpaRepository<Deposit, Long> {
Optional<Deposit> findByUser(User user);

Optional<Deposit> findByUserId(Long userId);

List<Deposit> findByUserIdIn(List<Long> userIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class DepositService {
Expand All @@ -40,6 +44,25 @@ public void recalculateDeposit(Long userId) {
deposit.updateAttendanceAmount(descentAttendance);
}

// 1-1. 여러 유저 보증금 일괄 재계산 (N*3 쿼리 → 3 쿼리)
@Transactional
public void recalculateDepositBatch(List<Long> userIds) {
if (userIds.isEmpty()) return;

List<Deposit> deposits = depositRepository.findByUserIdIn(userIds);
Map<Long, Integer> failCountMap = attendanceRepository.countFailedAttendanceByUserIds(userIds)
.stream()
.collect(Collectors.toMap(
row -> (Long) row[0],
row -> ((Long) row[1]).intValue()
));

for (Deposit deposit : deposits) {
int failCount = failCountMap.getOrDefault(deposit.getUser().getId(), 0);
deposit.updateAttendanceAmount(failCount * ATTENDANCE_PENALTY);
}
}

// 2. 보증금 조회 로직
public DepositResponse getMyDeposit(Long userId) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

@Service
public class QuestionEventService {
private static final long SSE_TIMEOUT_MILLIS = 60L * 60L * 1000L;
private static final long SSE_TIMEOUT_MILLIS = 3L * 60L * 1000L;

// sessionId별로 현재 질문방을 보고 있는 SSE 연결들을 보관한다.
private final Map<Long, List<SseEmitter>> sessionEmitters = new ConcurrentHashMap<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -76,17 +79,17 @@ public UpdateStudentStatusResponse updateStudentWeekStatus(
.orElseThrow(() -> new RuntimeException("사용자가 존재하지 않습니다."));

if (request.getAssignments() != null) {
for (UpdateStudentStatusRequest.AssignmentStatusRequest dto
: request.getAssignments()) {
List<Integer> itemIds = request.getAssignments().stream()
.map(UpdateStudentStatusRequest.AssignmentStatusRequest::getAssignmentItemId)
.toList();

// 해당 assignmentItem 존재하지 않을 때
AssignmentItem assignmentItem =
assignmentItemRepository.findById(dto.getAssignmentItemId())
.orElseThrow(() ->
new RuntimeException("과제 정보가 존재하지 않습니다.")
);
Map<Integer, AssignmentItem> itemMap = assignmentItemRepository.findAllById(itemIds).stream()
.collect(Collectors.toMap(AssignmentItem::getId, item -> item));

for (UpdateStudentStatusRequest.AssignmentStatusRequest dto : request.getAssignments()) {
AssignmentItem assignmentItem = Optional.ofNullable(itemMap.get(dto.getAssignmentItemId()))
.orElseThrow(() -> new RuntimeException("과제 정보가 존재하지 않습니다."));

// assignmentItem가 userId의 과제가 아닐 경우
if (!assignmentItem.getUser().getId().equals(userId)) {
throw new RuntimeException("해당 유저의 과제가 아닙니다.");
}
Expand All @@ -98,16 +101,17 @@ public UpdateStudentStatusResponse updateStudentWeekStatus(
}

if (request.getAttendances() != null) {
for (UpdateStudentStatusRequest.AttendanceStatusRequest dto
: request.getAttendances()) {
List<Long> attendanceIds = request.getAttendances().stream()
.map(dto -> dto.getAttendanceId().longValue())
.toList();

Map<Integer, Attendance> attendanceMap = attendanceRepository.findAllById(attendanceIds).stream()
.collect(Collectors.toMap(Attendance::getId, a -> a));

Attendance attendance =
attendanceRepository.findById(dto.getAttendanceId())
.orElseThrow(() ->
new RuntimeException("출석 정보가 존재하지 않습니다.")
);
for (UpdateStudentStatusRequest.AttendanceStatusRequest dto : request.getAttendances()) {
Attendance attendance = Optional.ofNullable(attendanceMap.get(dto.getAttendanceId()))
.orElseThrow(() -> new RuntimeException("출석 정보가 존재하지 않습니다."));

// assignmentId가 userId의 것이 아닐 때
if (!attendance.getUser().getId().equals(userId)) {
throw new RuntimeException("해당 유저의 출석이 아닙니다.");
}
Expand Down
7 changes: 7 additions & 0 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ spring:
username: ${RDS_USERNAME}
password: ${RDS_PASSWORD}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
leak-detection-threshold: 5000

flyway:
# repair-on-migrate: true
Expand All @@ -15,6 +21,7 @@ spring:
hibernate:
ddl-auto: validate
show-sql: true
open-in-view: false
properties:
hibernate:
format_sql: true
Expand Down
7 changes: 1 addition & 6 deletions frontend/src/pages/curriculum/CurriculumPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -318,17 +318,12 @@ function SessionForm({ day, week, onClose, onSave }) {

// ── 메인 컴포넌트 ─────────────────────────────────────
function CurriculumPage() {
const [role, setRole] = useState(null);
const role = localStorage.getItem('role') || 'MEMBER';
const [days, setDays] = useState([]);
const [showForm, setShowForm] = useState(false);
const [editDay, setEditDay] = useState(null);
const [createWeek, setCreateWeek] = useState(null);

useEffect(() => {
setRole(localStorage.getItem('role') || 'MEMBER');
}, []);

// if (role === null) return null;

const fetchDays = async () => {
try {
Expand Down
Loading