Skip to content

Commit

Permalink
Merge pull request #255 from IT-Cotato/release
Browse files Browse the repository at this point in the history
* [COT-72] Fix: 출결 결과 ENUM 설명 오류 수정 (#216)

* fix: change description of online, offline in attendanceResult

* fix: change attendanceResult status to OFFLINE when attend by offline

* [COT-79] Hotfix: 기수별 출결 기록 카운팅 이슈 해결 (#217)

* [COT-72] Feature: 프로필 링크 관련 테이블 추가 (#209)

* feat: add university, introduction column in member entity

* feat: create profile link entity

* refactor: rename hall of fame service

-> myPageService -> HallOfFameService

* feat: create memberProfile service, repository file

* feat: delete limit of introduction, university column length

* refactor: rollback name of hall of fame service

* feat: add TEXT option in introduction

* refactor: change parameter of hall of fame API

-> accessHeader -> AuthenticationPrincipal

* refactor: delete unused Builder from ProfileLink

* [COT-88] Refactor: 출결 API 필드명 통일 (#220)

* refactor: attendance response field name

* refactor: update attendance required field

* chore: remove unused import line

* [COT-87] Feature: 기수별 활동 부원 목록 추가 (#219)

* refactor: delete unused dependency in memberController

* refactor: delete unused import of GenerationMember Entity

* refactor: change static method name when migrate generationMember

* feat: develop post GenerationMember API

* feat: develop update memberRole of generationMember

* refactor: delete unused Annotation

* style: Remove unnecessary line breaks for code convention

* refactor: delete unused Annotation

* feat: change add generationMember one-by-one to list

* refactor: rename static method

migrate -> of

* refactor: delete Schema tag in request

* feat: change request field format of createGenerationMember

* refactor: rename method name of find members with validation

* refactor: rename method name of find members with validation

* [COT-63] Feature: 세션 추가 시 출결 옵션을 선택할 수 있다. (#221)

* feat: create session type column

* feat: add session type when create session

* feat: create attendance by session type

* feat: add session type field

* feat: add get session type method

* feat: validate dto field

* feat: change offline session validation method

* style: remove end line

* refactor: simplify condition line

* [COT-90] Feature: 기수별 활동 부원 삭제 (#223)

* feat: develop delete generationMember API

* refactor: change way of delete data of generationMember

-> deleteAll -> delete query

* refactor: rename method

* feat: add validation for existence of generationMember ids before deletion

* [COT-91] Feature: 세션 타입에 따른 출결 입력 제한 (#222)

* [COT-65] Feature: 기수별 활동 멤버 목록 반환 API  (#225)

* feat: find generationMemberInfos by generation API

* feat: sort GenerationMemberInfo with member name

* [COT-98] Fix: API 엔드포인트 리소스가 중복되어 내려가는 이슈 수정 (#226)

* [COT-104] Feature: 세션 수정 시 출결 옵션 선택 가능 (#228)

* feat: update session type

* feat: update attendance if not exist

* chore: remove unused import line

* feat: change update api path

* feat: use session reader

* feat: update session

* feat: update attendance deadline

* [COT-113] Feature: 세션 단건 조회 시 출결 필드 반환 (#239)

* feat: add session type field

* chore: add required description

* [COT-108] Refactor: AttendanceAdminService 추상화 (#240)

* refactor: update attendance method

* refactor: move update attendance records

* refactor: move create attendance

* refactor: move get attendance records

* refactor: move get attendance records by attendance

* refactor: change attendance admin service class name

* [COT-106] Feature: 출결 기록 수정 시 세션 타입에 맞는 기록 수정만 가능하다. (#230)

* feat: check session and attendance type when update attendance record

* feat: use session reader

* [COT-111] Feature: 랜덤 퀴즈 테이블 생성 (#232)

* feat: create random quiz table for cs quiz tutorial

* feat: delete column name of embedded field

* feat: add nullable option and enumerated annotation

* refactor: rename choices field name

* refactor: rename description of QuizCategory

* feat: add quizCategory

* feat: add quizCategory

* refactor: reorder category

* [COT-89] Feature: 세션 수정 시 비관적 X-Lock 생성 (#243)

* feat: add pessimistic x-lock while finding session by id

* feat: add pessimistic x-lock while finding attendance by sessionId

* feat: add validation of attendance time while updating session

* Revert "feat: add validation of attendance time while updating session"

This reverts commit 6fad4df.

* style: remove unnecessary line

* feat: add read only transaction in select jpql

* [COT-112] Feature: 랜덤 퀴즈 반환 API 구현 (#245)

* feat: create random quiz response data

* feat: get random quiz for visitor

* refactor: move building choice list method to choices class

* style: remove unused import

* feat: add answerNumber field in tutorial quiz response

* refactor: rename getChoices List method

* feat: change random quiz api name

* feat: change api name of spring security

* feat: delete answer info field in tutorialQuiz response

* refactor: move getChoices and imageUrl method  to randomQuiz entity

* [COT-115] Feature: 문제 풀이 신호 비동기 전송 처리 (#248)

* feat: start and access quiz with async

* chore: log disconnected user id when access and start quiz

* [COT-114] Feature: 세션 수정 시 출석시간 유효성 검사 (#244)

* [COT-119] Chore: PR Template 변경 (#249)

* [COT-123] Feature: 출석 단건 조회에 세션 타입 추가 및 기수별 목록 조회에 openStatus 제거 (#251)

* feat: 세션 단건 조회에 세션 타입 추가

* chore: map 네이밍 컨벤션

* feat: 출석에 세션이 존재하지 않는 경우 예외 발생

* feat: 기수별 출결 목록에 openStatus 필드 제거

* [COT-118] Feature: 랜덤 퀴즈 제출 API 구현 (#246)

* feat: develop reply to randomQuiz API

* style: rename parameter

quizId -> randomQuizId

* refactor: change RandomQuiz reply API from POST to GET

* refactor: change type of randomquiz result

* refactor: change type of quiz result

* refactor: change api path

* Revert "refactor: change type of quiz result"

This reverts commit fe14323.

---------

Co-authored-by: GiHun Nam <[email protected]>
  • Loading branch information
Youthhing and gikhoon authored Dec 26, 2024
2 parents 4949c96 + 7a0665e commit c6e468a
Show file tree
Hide file tree
Showing 70 changed files with 1,181 additions and 304 deletions.
17 changes: 13 additions & 4 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
## 연관된 이슈
### Motivation
<!-- 이 PR이 해결하려는 내용을 적어주세요. -->

이슈링크(url):
### Modification
<!-- 이 PR이 추가/변경 하는 내용에 대해서 최대한 자세히 작성해주세요 -->
<!-- 어떻게 문제를 해결할 것인지? -->


## ✅ 작업 내용
### Result
<!-- 이 PR 머지된 이후에 발생할 일들에 대해서 작성해주세요 -->
<!-- resolve jira issue link를 적어주세요 -->

### Test (option)
<!-- 테스트 결과를 보여주세요. -->

## 🗣 ️리뷰 요구 사항

### SQL
<!-- 해당 PR을 통해 변경될 DB Schema에 대한 DDL 등의 쿼리를 적어주세요. -->
Original file line number Diff line number Diff line change
@@ -1,36 +1,29 @@
package org.cotato.csquiz.api.attendance.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.cotato.csquiz.api.attendance.dto.AttendResponse;
import org.cotato.csquiz.api.attendance.dto.AttendanceRecordResponse;
import org.cotato.csquiz.api.attendance.dto.AttendanceResponse;
import org.cotato.csquiz.api.attendance.dto.GenerationMemberAttendanceRecordResponse;
import org.cotato.csquiz.api.attendance.dto.AttendanceTimeResponse;
import org.cotato.csquiz.api.attendance.dto.AttendancesResponse;
import org.cotato.csquiz.api.attendance.dto.MemberAttendanceRecordsResponse;
import org.cotato.csquiz.api.attendance.dto.OfflineAttendanceRequest;
import org.cotato.csquiz.api.attendance.dto.OnlineAttendanceRequest;
import org.cotato.csquiz.api.attendance.dto.GenerationMemberAttendanceRecordResponse;
import org.cotato.csquiz.api.attendance.dto.UpdateAttendanceRecordRequest;
import org.cotato.csquiz.api.attendance.dto.UpdateAttendanceRequest;
import org.cotato.csquiz.domain.attendance.service.AttendanceAdminService;
import org.cotato.csquiz.domain.attendance.service.AttendanceExcelService;
import org.cotato.csquiz.domain.attendance.service.AttendanceRecordService;
import org.cotato.csquiz.domain.attendance.service.AttendanceService;
import org.cotato.csquiz.domain.attendance.util.AttendanceExcelHeaderUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
Expand All @@ -44,9 +37,9 @@
@RequestMapping("/v2/api/attendances")
public class AttendanceController {

private final AttendanceAdminService attendanceAdminService;
private final AttendanceService attendanceService;
private final AttendanceExcelService attendanceExcelService;
private final AttendanceRecordService attendanceRecordService;
private final AttendanceService attendanceService;

@Operation(summary = "출석 단건 조회")
@GetMapping("/{attendanceId}")
Expand All @@ -57,7 +50,7 @@ public ResponseEntity<AttendanceResponse> getAttendance(@PathVariable("attendanc
@Operation(summary = "출석 정보 변경 API")
@PatchMapping
public ResponseEntity<Void> updateAttendance(@RequestBody @Valid UpdateAttendanceRequest request) {
attendanceAdminService.updateAttendanceByAttendanceId(request);
attendanceService.updateAttendance(request.attendanceId(), request.location(), request.attendTime().attendanceDeadLine(), request.attendTime().lateDeadLine());
return ResponseEntity.noContent().build();
}

Expand All @@ -72,22 +65,22 @@ public ResponseEntity<AttendanceTimeResponse> findAttendanceTimeInfo(@RequestPar
public ResponseEntity<List<GenerationMemberAttendanceRecordResponse>> findAttendanceRecords(
@RequestParam(name = "generationId") Long generationId
) {
return ResponseEntity.ok().body(attendanceAdminService.findAttendanceRecords(generationId));
return ResponseEntity.ok().body(attendanceRecordService.findAttendanceRecords(generationId));
}

@Operation(summary = "회원 출결사항 출석 단위 조회 API")
@GetMapping("/{attendance-id}/records")
public ResponseEntity<List<AttendanceRecordResponse>> findAttendanceRecordsByAttendance(
@PathVariable("attendance-id") Long attendanceId) {
return ResponseEntity.ok().body(attendanceAdminService.findAttendanceRecordsByAttendance(attendanceId));
return ResponseEntity.ok().body(attendanceRecordService.findAttendanceRecordsByAttendance(attendanceId));
}

@Operation(summary = "회원 출결사항 수정 API")
@PatchMapping("/{attendance-id}/records")
public ResponseEntity<Void> updateAttendanceRecords(
@PathVariable("attendance-id") Long attendanceId,
@RequestBody @Valid UpdateAttendanceRecordRequest request) {
attendanceAdminService.updateAttendanceRecords(attendanceId, request.memberId(), request.attendanceResult());
attendanceRecordService.updateAttendanceRecords(attendanceId, request.memberId(), request.result());
return ResponseEntity.noContent().build();
}

Expand All @@ -98,66 +91,14 @@ public ResponseEntity<AttendancesResponse> findAttendancesByGeneration(
return ResponseEntity.ok().body(attendanceService.findAttendancesByGenerationId(generationId));
}

@Operation(summary = "대면 출결 입력 API",
responses = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
@ApiResponse(
responseCode = "409",
description = "이미 출석을 완료함"
),
@ApiResponse(
responseCode = "400",
description = "출석 시간이 아님"
)
}
)
@PostMapping(value = "/records/offline")
public ResponseEntity<AttendResponse> submitOfflineAttendanceRecord(
@RequestBody @Valid OfflineAttendanceRequest request,
@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok().body(attendanceRecordService.submitRecord(request, memberId));
}

@Operation(summary = "비대면 출결 입력 API",
responses = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
@ApiResponse(
responseCode = "409",
description = "이미 출석을 완료함"
),
@ApiResponse(
responseCode = "400",
description = "출석 시간이 아님"
)
})
@PostMapping(value = "/records/online")
public ResponseEntity<AttendResponse> submitOnlineAttendanceRecord(
@RequestBody @Valid OnlineAttendanceRequest request,
@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok().body(attendanceRecordService.submitRecord(request, memberId));
}

@Operation(summary = "부원의 기수별 출결 기록 반환 API")
@GetMapping("/records/members")
public ResponseEntity<MemberAttendanceRecordsResponse> findAllRecordsByGeneration(
@RequestParam("generationId") Long generationId,
@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok().body(attendanceRecordService.findAllRecordsBy(generationId, memberId));
}

@Operation(summary = "세션별 출석 기록 엑셀 다운로드 API")
@GetMapping("/excel")
public ResponseEntity<byte[]> downloadAttendanceRecordsAsExcelBySessions(
@RequestParam(name = "attendanceIds") List<Long> attendanceIds) {

byte[] excelFile = attendanceAdminService.createExcelForSessionAttendance(attendanceIds);
String finalFileName = attendanceAdminService.getEncodedFileName(attendanceIds);
byte[] excelFile = attendanceExcelService.createExcelForSessionAttendance(attendanceIds);
String finalFileName = attendanceExcelService.getEncodedFileName(attendanceIds);

HttpHeaders headers = AttendanceExcelHeaderUtil.createExcelDownloadHeaders(finalFileName);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package org.cotato.csquiz.api.attendance.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.cotato.csquiz.api.attendance.dto.AttendResponse;
import org.cotato.csquiz.api.attendance.dto.MemberAttendanceRecordsResponse;
import org.cotato.csquiz.api.attendance.dto.OfflineAttendanceRequest;
import org.cotato.csquiz.api.attendance.dto.OnlineAttendanceRequest;
import org.cotato.csquiz.domain.attendance.service.AttendanceRecordService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "출결 기록", description = "출결 기록 관련 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/v2/api/attendances/records")
public class AttendanceRecordController {

private final AttendanceRecordService attendanceRecordService;


@Operation(summary = "대면 출결 입력 API",
responses = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
@ApiResponse(
responseCode = "409",
description = "이미 출석을 완료함"
),
@ApiResponse(
responseCode = "400",
description = "출석 시간이 아님"
)
}
)
@PostMapping(value = "/offline")
public ResponseEntity<AttendResponse> submitOfflineAttendanceRecord(
@RequestBody @Valid OfflineAttendanceRequest request,
@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok().body(attendanceRecordService.submitRecord(request, memberId));
}

@Operation(summary = "비대면 출결 입력 API",
responses = {
@ApiResponse(
responseCode = "200",
description = "성공"
),
@ApiResponse(
responseCode = "409",
description = "이미 출석을 완료함"
),
@ApiResponse(
responseCode = "400",
description = "출석 시간이 아님"
)
})
@PostMapping(value = "/online")
public ResponseEntity<AttendResponse> submitOnlineAttendanceRecord(
@RequestBody @Valid OnlineAttendanceRequest request,
@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok().body(attendanceRecordService.submitRecord(request, memberId));
}

@Operation(summary = "부원의 기수별 출결 기록 반환 API")
@GetMapping("/members")
public ResponseEntity<MemberAttendanceRecordsResponse> findAllRecordsByGeneration(
@RequestParam("generationId") Long generationId,
@AuthenticationPrincipal Long memberId) {
return ResponseEntity.ok().body(attendanceRecordService.findAllRecordsBy(generationId, memberId));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import org.cotato.csquiz.domain.attendance.enums.AttendanceResult;

public record AttendResponse(
AttendanceResult status,
AttendanceResult result,
String message
) {
public static AttendResponse from(AttendanceResult status) {
public static AttendResponse from(AttendanceResult result) {
return new AttendResponse(
status,
status.getMessage()
result,
result.getMessage()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package org.cotato.csquiz.api.attendance.dto;

import java.time.LocalDateTime;
import org.cotato.csquiz.domain.attendance.enums.AttendanceResult;
import org.cotato.csquiz.domain.attendance.enums.AttendanceType;

public interface AttendanceParams {

AttendanceType attendanceType();

AttendanceResult attendanceResult();

Long attendanceId();

LocalDateTime requestTime();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.cotato.csquiz.domain.attendance.enums.AttendanceOpenStatus;
import org.cotato.csquiz.domain.attendance.util.AttendanceUtil;
import org.cotato.csquiz.domain.generation.entity.Session;
import org.cotato.csquiz.domain.generation.enums.SessionType;

public record AttendanceResponse(
@Schema(description = "출석 PK", requiredMode = RequiredMode.REQUIRED)
Expand All @@ -20,6 +21,8 @@ public record AttendanceResponse(
Location location,
@Schema(description = "세션 PK", requiredMode = RequiredMode.REQUIRED)
Long sessionId,
@Schema(description = "출결 옵션", requiredMode = RequiredMode.REQUIRED)
SessionType sessionType,
@Schema(description = "출석 오픈 상태", requiredMode = RequiredMode.REQUIRED)
AttendanceOpenStatus openStatus
) {
Expand All @@ -30,6 +33,7 @@ public static AttendanceResponse of(Attendance attendance, Session session) {
attendance.getLateDeadLine(),
attendance.getLocation(),
attendance.getSessionId(),
session.getSessionType(),
AttendanceUtil.getAttendanceOpenStatus(session.getSessionDateTime(), attendance, LocalDateTime.now())
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ public static AttendanceStatistic of(List<AttendanceRecord> attendanceRecords, I
attendanceRecordsByResult.getOrDefault(AttendanceResult.ONLINE, ZERO_VALUE),
attendanceRecordsByResult.getOrDefault(AttendanceResult.OFFLINE, ZERO_VALUE),
attendanceRecordsByResult.getOrDefault(AttendanceResult.LATE, ZERO_VALUE),
totalAttendanceCount - countNotAbsents(attendanceRecords).size()
totalAttendanceCount - getPresentCount(attendanceRecords)
);
}

private static List<AttendanceRecord> countNotAbsents(List<AttendanceRecord> attendanceRecords) {
private static long getPresentCount(List<AttendanceRecord> attendanceRecords) {
return attendanceRecords.stream()
.filter(AttendanceRecord::isAttendanceResultNotAbsent)
.toList();
.filter(AttendanceRecord::isPresent)
.count();
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package org.cotato.csquiz.api.attendance.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
import java.time.LocalDateTime;
import org.cotato.csquiz.domain.attendance.embedded.Location;
import org.cotato.csquiz.domain.attendance.entity.Attendance;

public record AttendanceTimeResponse(
@Schema(requiredMode = RequiredMode.REQUIRED)
Long sessionId,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(requiredMode = RequiredMode.REQUIRED)
LocalDateTime attendanceDeadLine,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(requiredMode = RequiredMode.REQUIRED)
LocalDateTime lateDeadLine,
Location location
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package org.cotato.csquiz.api.attendance.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode;
import java.time.LocalDateTime;
import lombok.Builder;
import org.cotato.csquiz.domain.attendance.enums.AttendanceOpenStatus;
import org.cotato.csquiz.domain.generation.enums.SessionType;

@Builder
public record AttendanceWithSessionResponse(
@Schema(requiredMode = RequiredMode.REQUIRED)
Long sessionId,
@Schema(requiredMode = RequiredMode.REQUIRED)
Long attendanceId,
@Schema(requiredMode = RequiredMode.NOT_REQUIRED)
String sessionTitle,
@Schema(requiredMode = RequiredMode.NOT_REQUIRED)
LocalDateTime sessionDateTime,
AttendanceOpenStatus openStatus
@Schema(requiredMode = RequiredMode.REQUIRED)
SessionType sessionType
) {
}
Loading

0 comments on commit c6e468a

Please sign in to comment.