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
3 changes: 2 additions & 1 deletion backend/pirocheck/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ out/
.vscode/

### 환경 변수 ###
../../.env
../../.env
.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package backend.pirocheck.Attendance.controller;

import backend.pirocheck.Attendance.dto.request.MarkAttendanceReq;
import backend.pirocheck.Attendance.dto.response.ApiResponse;
import backend.pirocheck.Attendance.dto.response.AttendanceCodeResponse;
import backend.pirocheck.Attendance.dto.response.AttendanceSlotRes;
import backend.pirocheck.Attendance.dto.response.AttendanceStatusRes;
import backend.pirocheck.Attendance.entity.AttendanceCode;
import backend.pirocheck.Attendance.service.AttendanceService;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;

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

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/attendance")
public class AttendanceController {

private final AttendanceService attendanceService;

// 특정 유저의 출석 정보
@GetMapping("/user")
public ApiResponse<List<AttendanceStatusRes>> getAttendanceByUserId(@RequestParam Long userId) {
return ApiResponse.success(attendanceService.findByUserId(userId));
}

// 특정 유저의 특정 일자 출석 정보
@GetMapping("/user/date")
public ApiResponse<List<AttendanceSlotRes>> getAttendanceByUserIdAndDate(
@RequestParam Long userId,
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date
) {
return ApiResponse.success(attendanceService.findByUserIdAndDate(userId, date));
}

// 출석체크 시작
@PostMapping("/start")
public ApiResponse<AttendanceCodeResponse> postAttendance() {
AttendanceCode code = attendanceService.generateCodeAndCreateAttendances();
return ApiResponse.success(AttendanceCodeResponse.from(code));
}

// 현재 활성화된 출석코드 조회
@GetMapping("/active-code")
public ApiResponse<AttendanceCodeResponse> getActiveCode() {
Optional<AttendanceCode> codeOpt = attendanceService.getActiveAttendanceCode();

if (codeOpt.isEmpty()) {
return ApiResponse.error("현재 활성화된 출석코드가 없습니다");
}

return ApiResponse.success(AttendanceCodeResponse.from(codeOpt.get()));
}

// 출석코드 비교
@PostMapping("/mark")
public ApiResponse<Void> markAttendance(@RequestBody MarkAttendanceReq req) {
String result = attendanceService.markAttendance(req.getUserId(), req.getCode());

if (result.equals("출석이 성공적으로 처리되었습니다")) {
return ApiResponse.success(result, null);
} else {
return ApiResponse.error(result);
}
}

// 출석체크 종료 (코드 직접 전달)
@PutMapping("/expire")
public ApiResponse<Void> expireAttendance(@RequestParam String code) {
String result = attendanceService.exprireAttendanceCode(code);

if (result.equals("출석 코드가 성공적으로 만료되었습니다")) {
return ApiResponse.success(result, null);
} else {
return ApiResponse.error(result);
}
}

// 출석체크 종료 (가장 최근 활성화된 코드 자동 만료)
@PutMapping("/expire-latest")
public ApiResponse<Void> expireLatestAttendance() {
String result = attendanceService.expireLatestAttendanceCode();

if (result.equals("출석 코드가 성공적으로 만료되었습니다")) {
return ApiResponse.success(result, null);
} else {
return ApiResponse.error(result);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package backend.pirocheck.attendence.dto.request;
package backend.pirocheck.Attendance.dto.request;

import lombok.Getter;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package backend.pirocheck.attendence.dto.request;
package backend.pirocheck.Attendance.dto.request;

import lombok.Getter;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package backend.pirocheck.Attendance.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> {
private boolean success;
private String message;
private T data;

public static <T> ApiResponse<T> success(T data) {
return ApiResponse.<T>builder()
.success(true)
.data(data)
.build();
}

public static <T> ApiResponse<T> success(String message, T data) {
return ApiResponse.<T>builder()
.success(true)
.message(message)
.data(data)
.build();
}

public static <T> ApiResponse<T> error(String message) {
return ApiResponse.<T>builder()
.success(false)
.message(message)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package backend.pirocheck.Attendance.dto.response;

import backend.pirocheck.Attendance.entity.AttendanceCode;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AttendanceCodeResponse {
private String code;
private LocalDate date;
private int order;
private boolean isExpired;

public static AttendanceCodeResponse from(AttendanceCode attendanceCode) {
return AttendanceCodeResponse.builder()
.code(attendanceCode.getCode())
.date(attendanceCode.getDate())
.order(attendanceCode.getOrder())
.isExpired(attendanceCode.isExpired())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package backend.pirocheck.attendence.dto.response;
package backend.pirocheck.Attendance.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package backend.pirocheck.attendence.dto.response;
package backend.pirocheck.Attendance.dto.response;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package backend.pirocheck.attendence.dto.response;
package backend.pirocheck.Attendance.dto.response;

import lombok.Getter;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package backend.pirocheck.attendence.entity;
package backend.pirocheck.Attendance.entity;

import backend.pirocheck.User.entity.User;
import jakarta.persistence.*;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package backend.pirocheck.attendence.entity;
package backend.pirocheck.Attendance.entity;

import jakarta.persistence.*;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package backend.pirocheck.attendence.repository;
package backend.pirocheck.Attendance.repository;

import backend.pirocheck.attendence.entity.AttendanceCode;
import backend.pirocheck.Attendance.entity.AttendanceCode;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

@Repository
public interface AttendanceCodeRepository extends JpaRepository<AttendanceCode, Long> {
int countByDate(LocalDate date);
Optional<AttendanceCode> findByCodeAndDate(String code, LocalDate date);
List<AttendanceCode> findByDateAndIsExpiredFalse(LocalDate date);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package backend.pirocheck.attendence.repository;
package backend.pirocheck.Attendance.repository;

import backend.pirocheck.attendence.entity.Attendance;
import backend.pirocheck.attendence.entity.AttendanceCode;
import backend.pirocheck.User.entity.User;
import backend.pirocheck.Attendance.entity.Attendance;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

Expand All @@ -14,4 +14,7 @@ public interface AttendanceRepository extends JpaRepository<Attendance, Long> {
List<Attendance> findByUserId(Long userId);
List<Attendance> findByUserIdAndDate(Long userId, LocalDate date);
Optional<Attendance> findByUserIdAndDateAndOrder(Long userId, LocalDate date, int order);

// 출석 실패
int countByUserAndStatusFalse(User user);
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package backend.pirocheck.attendence.service;
package backend.pirocheck.Attendance.service;

import backend.pirocheck.User.entity.Role;
import backend.pirocheck.User.entity.User;
import backend.pirocheck.User.repository.UserRepository;
import backend.pirocheck.attendence.dto.response.AttendanceSlotRes;
import backend.pirocheck.attendence.dto.response.AttendanceStatusRes;
import backend.pirocheck.attendence.entity.Attendance;
import backend.pirocheck.attendence.entity.AttendanceCode;
import backend.pirocheck.attendence.repository.AttendanceCodeRepository;
import backend.pirocheck.attendence.repository.AttendanceRepository;
import backend.pirocheck.Attendance.dto.response.AttendanceSlotRes;
import backend.pirocheck.Attendance.dto.response.AttendanceStatusRes;
import backend.pirocheck.Attendance.entity.Attendance;
import backend.pirocheck.Attendance.entity.AttendanceCode;
import backend.pirocheck.Attendance.repository.AttendanceCodeRepository;
import backend.pirocheck.Attendance.repository.AttendanceRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -34,6 +34,13 @@ public class AttendanceService {
public AttendanceCode generateCodeAndCreateAttendances() {
LocalDate today = LocalDate.now();

// 만료되지 않은 출석 코드가 있는지 확인
List<AttendanceCode> activeAttendanceCodes = attendanceCodeRepository.findByDateAndIsExpiredFalse(today);
if (!activeAttendanceCodes.isEmpty()) {
// 만료되지 않은 코드가 있으면 해당 코드 반환
return activeAttendanceCodes.get(0);
}

// 오늘 생성된 출석코드 개수 = 현재까지 생성된 차시 수 + 1 (MAX=3)
int currentOrder = attendanceCodeRepository.countByDate(today) + 1;

Expand Down Expand Up @@ -61,48 +68,90 @@ public AttendanceCode generateCodeAndCreateAttendances() {
return attendanceCode;
}

// 현재 활성화된 출석코드 조회 함수
public Optional<AttendanceCode> getActiveAttendanceCode() {
LocalDate today = LocalDate.now();
List<AttendanceCode> activeCodes = attendanceCodeRepository.findByDateAndIsExpiredFalse(today);

if (activeCodes.isEmpty()) {
return Optional.empty();
}

return Optional.of(activeCodes.get(0));
}

// 가장 최근 활성화된 출석코드 만료처리 함수
@Transactional
public String expireLatestAttendanceCode() {
Optional<AttendanceCode> activeCodeOpt = getActiveAttendanceCode();

if (activeCodeOpt.isEmpty()) {
return "현재 활성화된 출석코드가 없습니다";
}

AttendanceCode code = activeCodeOpt.get();
code.setExpired(true);
attendanceCodeRepository.save(code);

return "출석 코드가 성공적으로 만료되었습니다";
}

// 출석코드 만료처리 함수
@Transactional
public boolean exprireAttendanceCode(String code) {
public String exprireAttendanceCode(String code) {
Optional<AttendanceCode> codeOpt = attendanceCodeRepository.findByCodeAndDate(code, LocalDate.now());

if (codeOpt.isEmpty()) {
return false;
return "존재하지 않는 출석 코드입니다";
}

AttendanceCode attendanceCode = codeOpt.get();

if (attendanceCode.isExpired()) {
return false;
return "이미 만료된 출석 코드입니다";
}

attendanceCode.setExpired(true);
attendanceCodeRepository.save(attendanceCode);

return true;
return "출석 코드가 성공적으로 만료되었습니다";
}

// 출석처리 함수
@Transactional
public boolean markAttendance(Long userId, String inputCode) {
public String markAttendance(Long userId, String inputCode) {
// 1. 출석코드 일치 비교
Optional<AttendanceCode> validCodeOpt = attendanceCodeRepository.findByCodeAndDate(inputCode, LocalDate.now());

if (validCodeOpt.isEmpty()) return false;
if (validCodeOpt.isEmpty()) {
return "출석 코드가 존재하지 않습니다. 현재 출석 체크가 진행중이 아닙니다";
}

AttendanceCode code = validCodeOpt.get();

if (code.isExpired()) {
return "출석 코드가 만료되었습니다";
}

// 2. 해당 유저의 출석 레코드 조회
Optional<Attendance> attendanceOpt = attendanceRepository.findByUserIdAndDateAndOrder(userId, code.getDate(), code.getOrder());

if (attendanceOpt.isEmpty()) return false;
if (attendanceOpt.isEmpty()) {
return "출석 정보를 찾을 수 없습니다";
}

// 3. 출석 처리
Attendance attendance = attendanceOpt.get();

// 이미 출석한 경우
if (attendance.isStatus()) {
return "이미 출석처리가 완료되었습니다";
}

attendance.setStatus(true);
attendanceRepository.save(attendance);

return true;
return "출석이 성공적으로 처리되었습니다";
}

// 유저의 전체 출석 현황을 조회하는 함수
Expand Down
Loading