Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
dcf7b4f
[Deploy] Run jar on EC2
Imggaggu May 11, 2025
3ebe4ff
Merge pull request #58 from pirogramming/backend_sj
seonjuuu May 11, 2025
c9c3ae8
[fix] React & Spring Boot 연결_1
l-wanderer01 May 11, 2025
e7a6649
[fix] React & Spring Boot 연결 시도_1
l-wanderer01 May 11, 2025
a659446
보증금페이지 withCredentials: true 추가
qkrxogmla May 11, 2025
b721ae8
Merge pull request #62 from pirogramming/frontend_th
qkrxogmla May 11, 2025
b9a2515
[Fix] add credential true
Imggaggu May 11, 2025
2d8d04c
Merge pull request #63 from pirogramming/frontend
qkrxogmla May 11, 2025
cab00a1
Merge pull request #64 from pirogramming/frontend_sj
Imggaggu May 11, 2025
13552c9
Merge pull request #65 from pirogramming/frontend
Imggaggu May 11, 2025
acba3a6
Merge pull request #66 from pirogramming/main
qkrxogmla May 11, 2025
15045de
[add]: Attendance.jsx 세션 기반 인증 요청처리 추가
NamKyeongMin May 11, 2025
2f5bb16
Merge pull request #67 from pirogramming/frontend_km
NamKyeongMin May 11, 2025
38abc48
Merge pull request #68 from pirogramming/frontend
NamKyeongMin May 11, 2025
ad78ca4
전역에서 세션만료시 로그인페이지로 api
qkrxogmla May 11, 2025
c396c58
Merge pull request #69 from pirogramming/frontend_th
qkrxogmla May 11, 2025
e112d19
fetch 절대경로 수정
qkrxogmla May 11, 2025
7611e8c
Merge pull request #70 from pirogramming/frontend
qkrxogmla May 11, 2025
bbae9a3
Merge pull request #71 from pirogramming/frontend_th
qkrxogmla May 11, 2025
70c865e
fetch usesr 수정
qkrxogmla May 11, 2025
1130607
Merge pull request #72 from pirogramming/frontend_th
qkrxogmla May 11, 2025
5c15ca1
세션만료시 로그인페이지
qkrxogmla May 11, 2025
64bad80
base url http://www.pirocheck.org/api
qkrxogmla May 11, 2025
e79f482
Merge pull request #73 from pirogramming/frontend_th
qkrxogmla May 11, 2025
c94f385
attendance api수정
qkrxogmla May 11, 2025
8b8c1f7
Merge pull request #74 from pirogramming/frontend_th
qkrxogmla May 11, 2025
55f7d52
Update deploy.yml
Imggaggu May 12, 2025
c15dd04
Update deploy.yml
Imggaggu May 12, 2025
3aa2521
Update deploy.yml
Imggaggu May 12, 2025
7c562d9
[Fix] baseurl for api
Imggaggu May 12, 2025
b633405
Merge branch 'main' into frontend_sj
Imggaggu May 12, 2025
e31d056
Merge pull request #75 from pirogramming/frontend_sj
Imggaggu May 12, 2025
0a1150b
Merge pull request #76 from pirogramming/main
Imggaggu May 12, 2025
ec1224c
[Fix]back-allowed origins url
Imggaggu May 12, 2025
7a28b54
[fix] WebConfig.java 3000번 포트 삭제 및 과제 api 경로 수정
l-wanderer01 May 12, 2025
d6e0ce2
Merge pull request #77 from pirogramming/backend_jh
l-wanderer01 May 12, 2025
bbab84e
Merge pull request #78 from pirogramming/main
l-wanderer01 May 12, 2025
c5886d7
[Fix] baseurl for api
Imggaggu May 12, 2025
48a7519
Merge pull request #79 from pirogramming/frontend_sj
Imggaggu May 12, 2025
0db8b1c
Merge pull request #80 from pirogramming/frontend_sj
Imggaggu May 12, 2025
a2b27cb
[Chore] Generation/ admin directory 분리
Imggaggu May 12, 2025
5ecc609
[Chore] Generation/ admin directory 분리
Imggaggu May 12, 2025
775eb06
Merge pull request #81 from pirogramming/frontend_admin_sj
Imggaggu May 12, 2025
65672e1
[Fix] Allow all headers for CORS
Imggaggu May 12, 2025
cf4b420
Merge pull request #83 from pirogramming/frontend
Imggaggu May 12, 2025
77a8b66
Merge pull request #82 from pirogramming/frontend_admin_sj
Imggaggu May 12, 2025
0f40135
Merge pull request #84 from pirogramming/frontend
Imggaggu May 12, 2025
953aa0b
Merge pull request #85 from pirogramming/main
Imggaggu May 12, 2025
0907577
Update WebConfig.java
Imggaggu May 12, 2025
390252f
Update WebConfig.java
Imggaggu May 12, 2025
24099ef
Update WebConfig.java
Imggaggu May 12, 2025
67dfc6c
Update WebConfig.java
Imggaggu May 12, 2025
c4c2d97
[fix] WebConfig.java Origin url 변경
l-wanderer01 May 12, 2025
5481ba9
[fix] WebConfig.java Origin url 변경
l-wanderer01 May 12, 2025
82be491
Merge branch 'deploy' into main
l-wanderer01 May 12, 2025
99dac73
Merge pull request #87 from pirogramming/main
l-wanderer01 May 12, 2025
d2d37c9
admin완성
qkrxogmla May 13, 2025
3f6db86
Merge pull request #88 from pirogramming/frontend_th
qkrxogmla May 13, 2025
aa5da55
Merge pull request #89 from pirogramming/frontend
qkrxogmla May 13, 2025
f6f6d1d
home 경로 수정'
qkrxogmla May 13, 2025
5390fe2
Merge pull request #91 from pirogramming/frontend_th
qkrxogmla May 13, 2025
8801d5a
Merge pull request #92 from pirogramming/frontend
qkrxogmla May 13, 2025
159f0ca
[feat] ManageStudents
seonjuuu May 13, 2025
707de40
[fix]css
qkrxogmla May 14, 2025
75428b1
Merge pull request #94 from pirogramming/frontend_th
qkrxogmla May 14, 2025
ef06fb4
Merge branch 'main' of https://github.com/pirogramming/PiroCheck into…
seonjuuu May 14, 2025
2add8f9
[feat] ManageStudents_StudentDetail
seonjuuu May 14, 2025
5e5d952
[feat] ManageStudents_updateShield
seonjuuu May 14, 2025
82b109c
[fix] updateDefence
seonjuuu May 14, 2025
a6f5a2c
Merge pull request #96 from pirogramming/backend_sj
seonjuuu May 14, 2025
8631e91
출석관련 관리자 로직, 응답형식 수정 및 swagger문서 추가
dietken1 May 16, 2025
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 .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
- name: Copy JAR to EC2
run: |
scp -o StrictHostKeyChecking=no -i pirocheck.pem backend/pirocheck/build/libs/*.jar ubuntu@${{ secrets.EC2_HOST }}:/home/ubuntu/


- name: Restart Spring Boot on EC2
run: |
Expand Down Expand Up @@ -106,4 +107,4 @@ jobs:
AWS_S3_BUCKET: www.pirocheck.org
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
SOURCE_DIR: frontend/dist
SOURCE_DIR: frontend/dist
Original file line number Diff line number Diff line change
Expand Up @@ -2,93 +2,88 @@

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.AttendanceMarkResponse;
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 io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
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")
@Tag(name = "출석관리", description = "학생용 출석 관련 API")
public class AttendanceController {

private final AttendanceService attendanceService;

// 특정 유저의 출석 정보
@Operation(summary = "사용자 출석 정보 조회", description = "특정 사용자의 전체 출석 정보를 조회합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자를 찾을 수 없음")
})
@GetMapping("/user")
public ApiResponse<List<AttendanceStatusRes>> getAttendanceByUserId(@RequestParam Long userId) {
public ApiResponse<List<AttendanceStatusRes>> getAttendanceByUserId(
@Parameter(description = "사용자 ID", required = true)
@RequestParam Long userId) {
return ApiResponse.success(attendanceService.findByUserId(userId));
}

// 특정 유저의 특정 일자 출석 정보
@Operation(summary = "특정 날짜 출석 정보 조회", description = "특정 사용자의 특정 날짜 출석 정보를 조회합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "조회 성공"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "404", description = "사용자 또는 날짜 정보를 찾을 수 없음")
})
@GetMapping("/user/date")
public ApiResponse<List<AttendanceSlotRes>> getAttendanceByUserIdAndDate(
@Parameter(description = "사용자 ID", required = true)
@RequestParam Long userId,
@Parameter(description = "조회할 날짜 (YYYY-MM-DD)", required = true)
@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()));
}

// 출석코드 비교
@Operation(summary = "출석 체크", description = "출석 코드를 입력하여 출석을 체크합니다.")
@ApiResponses({
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "출석 성공 또는 이미 출석 완료"),
@io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 출석 코드 또는 출석 체크 진행중이 아님")
})
@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);
public ApiResponse<AttendanceMarkResponse> markAttendance(
@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "출석 체크 요청", required = true,
content = @Content(schema = @Schema(implementation = MarkAttendanceReq.class)))
@RequestBody MarkAttendanceReq req) {
AttendanceMarkResponse response = attendanceService.markAttendance(req.getUserId(), req.getCode());

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

// 출석체크 종료 (가장 최근 활성화된 코드 자동 만료)
@PutMapping("/expire-latest")
public ApiResponse<Void> expireLatestAttendance() {
String result = attendanceService.expireLatestAttendanceCode();
// statusCode가 SUCCESS 또는 ALREADY_MARKED인 경우 성공으로 처리
boolean isSuccess = "SUCCESS".equals(response.getStatusCode()) ||
"ALREADY_MARKED".equals(response.getStatusCode());

if (result.equals("출석 코드가 성공적으로 만료되었습니다")) {
return ApiResponse.success(result, null);
if (isSuccess) {
return ApiResponse.success(response);
} else {
return ApiResponse.error(result);
// 그 외의 경우 (NO_ACTIVE_SESSION, CODE_EXPIRED, ERROR)는 오류로 처리
return ApiResponse.<AttendanceMarkResponse>builder()
.success(false)
.message(response.getMessage())
.data(response)
.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
package backend.pirocheck.Attendance.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;

@Getter
@Schema(description = "출석 체크 요청")
public class MarkAttendanceReq {
@Schema(description = "사용자 ID", example = "1")
private Long userId;

@Schema(description = "출석 코드", example = "1234")
private String code;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package backend.pirocheck.Attendance.dto.response;

import backend.pirocheck.Attendance.entity.AttendanceCode;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -12,10 +13,18 @@
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "출석 코드 응답")
public class AttendanceCodeResponse {
@Schema(description = "출석 코드", example = "1234")
private String code;

@Schema(description = "출석 날짜", example = "2025-06-24")
private LocalDate date;

@Schema(description = "출석 차시 (1, 2, 3)", example = "1")
private int order;

@Schema(description = "만료 여부", example = "false")
private boolean isExpired;

public static AttendanceCodeResponse from(AttendanceCode attendanceCode) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package backend.pirocheck.Attendance.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@Schema(description = "출석 차시별 상태")
public class AttendanceSlotRes {
@Schema(description = "출석 차시 (1, 2, 3)", example = "1")
private int order;

@Schema(description = "출석 여부", example = "true")
private boolean status;

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package backend.pirocheck.Attendance.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.Setter;

Expand All @@ -8,7 +9,14 @@

@Getter
@Setter
@Schema(description = "사용자 출석 상태")
public class AttendanceStatusRes {
@Schema(description = "출석 날짜", example = "2025-06-24")
private LocalDate date;

@Schema(description = "주차", example = "1")
private int week;

@Schema(description = "출석 차시별 상태 목록")
private List<AttendanceSlotRes> slots;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import backend.pirocheck.User.entity.Role;
import backend.pirocheck.User.entity.User;
import backend.pirocheck.User.repository.UserRepository;
import backend.pirocheck.Attendance.dto.response.AttendanceMarkResponse;
import backend.pirocheck.Attendance.dto.response.AttendanceSlotRes;
import backend.pirocheck.Attendance.dto.response.AttendanceStatusRes;
import backend.pirocheck.Attendance.entity.Attendance;
Expand Down Expand Up @@ -43,6 +44,11 @@ public AttendanceCode generateCodeAndCreateAttendances() {

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

// 하루 최대 3회 출석 체크만 허용
if (currentOrder > 3) {
throw new IllegalStateException("하루에 최대 3회까지만 출석 체크를 진행할 수 있습니다.");
}

// 1. 출석 코드 생성
String code = String.valueOf(ThreadLocalRandom.current().nextInt(1000, 10000));
Expand Down Expand Up @@ -119,39 +125,52 @@ public String exprireAttendanceCode(String code) {

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

public AttendanceMarkResponse markAttendance(Long userId, String inputCode) {
// 오늘 날짜
LocalDate today = LocalDate.now();

// 현재 활성화된 출석 코드가 있는지 확인
List<AttendanceCode> activeCodes = attendanceCodeRepository.findByDateAndIsExpiredFalse(today);

// 활성화된 출석 코드가 없는 경우
if (activeCodes.isEmpty()) {
return AttendanceMarkResponse.noActiveSession();
}

// 입력한 출석 코드와 일치하는 코드가 있는지 확인
Optional<AttendanceCode> validCodeOpt = attendanceCodeRepository.findByCodeAndDate(inputCode, today);

// 입력한 출석 코드가 존재하지 않는 경우
if (validCodeOpt.isEmpty()) {
return "출석 코드가 존재하지 않습니다. 현재 출석 체크가 진행중이 아닙니다";
return AttendanceMarkResponse.invalidCode();
}

AttendanceCode code = validCodeOpt.get();

// 입력한 출석 코드가 만료된 경우
if (code.isExpired()) {
return "출석 코드가 만료되었습니다";
return AttendanceMarkResponse.codeExpired();
}

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

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

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

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

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

return "출석이 성공적으로 처리되었습니다";
return AttendanceMarkResponse.success();
}

// 유저의 전체 출석 현황을 조회하는 함수
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

import backend.pirocheck.User.entity.User;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
Expand Down Expand Up @@ -37,4 +35,12 @@ public void updateAmounts(int descentAssignment, int descentAttendance, int asce
this.amount = Math.min(calculateAmount, 120000); // 12만원 넘어가지 않도록
}

// 방어권 업데이트
public void updateDefence(int newAscentDefence) {
this.ascentDefence = newAscentDefence;
int calculateAmount = 120000 - this.descentAssignment - this.descentAttendance + newAscentDefence;
this.amount = Math.min(calculateAmount, 120000);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package backend.pirocheck.ManageStudents.Controller;

import backend.pirocheck.ManageStudents.dto.request.DefenceUpdateReqDto;
import backend.pirocheck.ManageStudents.dto.response.ManageStudentDetailResDto;
import backend.pirocheck.ManageStudents.dto.response.ManageStudentsListResDto;
import backend.pirocheck.ManageStudents.service.ManageStudentsService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/admin/managestudent")
@RequiredArgsConstructor
public class ManageStudentsController {

private final ManageStudentsService manageStudentsService;

// 수강생 리스트 조회
@GetMapping("")
public List<ManageStudentsListResDto> getStudents(@RequestParam(required = false) String name) {
return manageStudentsService.searchMembers(name);
}

// 수강생 상세 조회
@GetMapping("/{studentId}")
public ManageStudentDetailResDto getStudentDetail(@PathVariable Long studentId) {
return manageStudentsService.getMemberDetail(studentId);
}

// 방어권 업데이트
@PatchMapping("/{studentId}/defence")
public void updateDefence(@PathVariable Long studentId, @RequestBody DefenceUpdateReqDto req) {
manageStudentsService.updateDefence(studentId, req.getDefence());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package backend.pirocheck.ManageStudents.dto.request;


import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class DefenceUpdateReqDto {
private int defence;
}
Loading