Skip to content

Commit 82e2bd4

Browse files
committed
[EA3-162] feature: 출석 기능 구현
1 parent 2ffba36 commit 82e2bd4

File tree

6 files changed

+123
-11
lines changed

6 files changed

+123
-11
lines changed

src/main/java/grep/neogul_coder/domain/attendance/Attendance.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33
import grep.neogul_coder.global.entity.BaseEntity;
44
import jakarta.persistence.*;
5+
import lombok.Builder;
6+
import lombok.Getter;
57

6-
import java.time.LocalDate;
8+
import java.time.LocalDateTime;
79

10+
@Getter
811
@Entity
912
public class Attendance extends BaseEntity {
1013

1114
@Id
1215
@GeneratedValue(strategy = GenerationType.IDENTITY)
13-
private Long attendanceId;
16+
private Long id;
1417

1518
@Column(nullable = false)
1619
private Long studyId;
@@ -19,5 +22,22 @@ public class Attendance extends BaseEntity {
1922
private Long userId;
2023

2124
@Column(nullable = false)
22-
private LocalDate attendanceDate;
25+
private LocalDateTime attendanceDate;
26+
27+
protected Attendance() {}
28+
29+
@Builder
30+
private Attendance(Long studyId, Long userId, LocalDateTime attendanceDate) {
31+
this.studyId = studyId;
32+
this.userId = userId;
33+
this.attendanceDate = attendanceDate;
34+
}
35+
36+
public static Attendance create(Long studyId, Long userId) {
37+
return Attendance.builder()
38+
.studyId(studyId)
39+
.userId(userId)
40+
.attendanceDate(LocalDateTime.now())
41+
.build();
42+
}
2343
}
Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
package grep.neogul_coder.domain.attendance.controller;
22

33
import grep.neogul_coder.domain.attendance.controller.dto.response.AttendanceResponse;
4+
import grep.neogul_coder.domain.attendance.service.AttendanceService;
5+
import grep.neogul_coder.global.auth.Principal;
46
import grep.neogul_coder.global.response.ApiResponse;
5-
import org.springframework.web.bind.annotation.GetMapping;
6-
import org.springframework.web.bind.annotation.PostMapping;
7-
import org.springframework.web.bind.annotation.RequestMapping;
8-
import org.springframework.web.bind.annotation.RestController;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
9+
import org.springframework.web.bind.annotation.*;
910

1011
import java.util.List;
1112

12-
@RequestMapping("/api/attendances")
13+
@RequestMapping("/api/studies/{studyId}/attendances")
14+
@RequiredArgsConstructor
1315
@RestController
1416
public class AttendanceController implements AttendanceSpecification {
1517

18+
private final AttendanceService attendanceService;
19+
1620
@GetMapping
1721
public ApiResponse<List<AttendanceResponse>> getAttendances() {
1822
return ApiResponse.success(List.of(new AttendanceResponse()));
1923
}
2024

2125
@PostMapping
22-
public ApiResponse<Void> createAttendance() {
23-
return ApiResponse.noContent();
26+
public ApiResponse<Long> createAttendance(@PathVariable("studyId") Long studyId,
27+
@AuthenticationPrincipal Principal userDetails) {
28+
Long userId = userDetails.getUserId();
29+
Long id = attendanceService.createAttendance(studyId, userId);
30+
return ApiResponse.success(id);
2431
}
2532
}

src/main/java/grep/neogul_coder/domain/attendance/controller/AttendanceSpecification.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package grep.neogul_coder.domain.attendance.controller;
22

33
import grep.neogul_coder.domain.attendance.controller.dto.response.AttendanceResponse;
4+
import grep.neogul_coder.global.auth.Principal;
45
import grep.neogul_coder.global.response.ApiResponse;
56
import io.swagger.v3.oas.annotations.Operation;
67
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -14,5 +15,5 @@ public interface AttendanceSpecification {
1415
ApiResponse<List<AttendanceResponse>> getAttendances();
1516

1617
@Operation(summary = "출석 체크", description = "스터디에 출석을 합니다.")
17-
ApiResponse<Void> createAttendance();
18+
ApiResponse<Long> createAttendance(Long studyId, Principal userDetails);
1819
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package grep.neogul_coder.domain.attendance.exception.code;
2+
3+
import grep.neogul_coder.global.response.code.ErrorCode;
4+
import lombok.Getter;
5+
import org.springframework.http.HttpStatus;
6+
7+
@Getter
8+
public enum AttendanceErrorCode implements ErrorCode {
9+
10+
ATTENDANCE_ALREADY_CHECKED("A001", HttpStatus.BAD_REQUEST, "출석은 하루에 한 번만 가능합니다.");
11+
12+
private final String code;
13+
private final HttpStatus status;
14+
private final String message;
15+
16+
AttendanceErrorCode(String code, HttpStatus status, String message) {
17+
this.code = code;
18+
this.status = status;
19+
this.message = message;
20+
}
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package grep.neogul_coder.domain.attendance.repository;
2+
3+
import grep.neogul_coder.domain.attendance.Attendance;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.data.jpa.repository.Query;
6+
import org.springframework.data.repository.query.Param;
7+
8+
import java.time.LocalDateTime;
9+
10+
public interface AttendanceRepository extends JpaRepository<Attendance, Long> {
11+
@Query("select count(a) > 0 from Attendance a where a.studyId = :studyId and a.userId = :userId and a.attendanceDate between :startOfDay and :endOfDay")
12+
boolean existsTodayAttendance(@Param("studyId") Long studyId,
13+
@Param("userId") Long userId,
14+
@Param("startOfDay") LocalDateTime startOfDay,
15+
@Param("endOfDay") LocalDateTime endOfDay);
16+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package grep.neogul_coder.domain.attendance.service;
2+
3+
import grep.neogul_coder.domain.attendance.Attendance;
4+
import grep.neogul_coder.domain.attendance.repository.AttendanceRepository;
5+
import grep.neogul_coder.domain.study.Study;
6+
import grep.neogul_coder.domain.study.repository.StudyRepository;
7+
import grep.neogul_coder.global.exception.business.BusinessException;
8+
import grep.neogul_coder.global.exception.business.NotFoundException;
9+
import lombok.RequiredArgsConstructor;
10+
import org.springframework.stereotype.Service;
11+
import org.springframework.transaction.annotation.Transactional;
12+
13+
import java.time.LocalDate;
14+
import java.time.LocalDateTime;
15+
import java.time.LocalTime;
16+
17+
import static grep.neogul_coder.domain.attendance.exception.code.AttendanceErrorCode.*;
18+
import static grep.neogul_coder.domain.study.exception.code.StudyErrorCode.*;
19+
20+
@Transactional(readOnly = true)
21+
@RequiredArgsConstructor
22+
@Service
23+
public class AttendanceService {
24+
25+
private final AttendanceRepository attendanceRepository;
26+
private final StudyRepository studyRepository;
27+
28+
@Transactional
29+
public Long createAttendance(Long studyId, Long userId) {
30+
Study study = studyRepository.findByIdAndActivatedTrue(studyId)
31+
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));
32+
33+
validateNotAlreadyChecked(studyId, userId);
34+
35+
Attendance attendance = Attendance.create(studyId, userId);
36+
attendanceRepository.save(attendance);
37+
return attendance.getId();
38+
}
39+
40+
private void validateNotAlreadyChecked(Long studyId, Long userId) {
41+
LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
42+
LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX);
43+
if (attendanceRepository.existsTodayAttendance(studyId, userId, startOfDay, endOfDay)) {
44+
throw new BusinessException(ATTENDANCE_ALREADY_CHECKED);
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)