Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b0e66e6
Merge branch 'main' of https://github.com/prgrms-web-devcourse-final-…
dbrkdgus00 Jul 14, 2025
707e1ed
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 14, 2025
0b1469f
[EA3-111] chore : 웹소켓 관련 의존성 추가
dbrkdgus00 Jul 14, 2025
c0d19ea
[EA3-111] refactor : swagger용 파일 주석처리 및 실제 기능 파일 리팩토링 중
dbrkdgus00 Jul 14, 2025
fe32313
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 14, 2025
3308ec6
Merge branch 'main' of https://github.com/prgrms-web-devcourse-final-…
dbrkdgus00 Jul 14, 2025
d22b654
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 14, 2025
776198a
[EA3-111] refactor : 엔티티 필드명 수정
dbrkdgus00 Jul 14, 2025
872a736
[EA3-111] refactor : 바뀐 엔티티 필드명 토대로 로직 리팩토링
dbrkdgus00 Jul 14, 2025
bec71fb
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 14, 2025
325ea1f
[EA3-111] feature : 스터디 채팅방 검증 로직 추가
dbrkdgus00 Jul 15, 2025
42b7f29
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 15, 2025
104fe64
[EA3-111] docs : 그룹채팅 Swagger 주석 해제 및 실제 컨트롤러 히든 처리
dbrkdgus00 Jul 15, 2025
b69e105
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 15, 2025
4101116
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 15, 2025
b5381d4
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 15, 2025
dbd4895
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 15, 2025
224d272
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 15, 2025
24c954f
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 15, 2025
92e863b
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 15, 2025
a33e139
Merge branch 'main' of https://github.com/prgrms-web-devcourse-final-…
dbrkdgus00 Jul 15, 2025
d225163
[EA3-111]
dbrkdgus00 Jul 15, 2025
4cac8e3
[EA3-111] refactor : sql문 수정
dbrkdgus00 Jul 15, 2025
1aa4764
[EA3-111] rename : 캘린더 도메인 및 테이블 명 변경
dbrkdgus00 Jul 16, 2025
5d96235
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 16, 2025
1187445
Merge branch 'main' of https://github.com/prgrms-web-devcourse-final-…
dbrkdgus00 Jul 16, 2025
0938bc4
[EA3-111] refactor : 데이터 필드명 변경
dbrkdgus00 Jul 16, 2025
2aa6d16
[EA3-111] refactor : sql파일 내 테이블 필드명 변경
dbrkdgus00 Jul 16, 2025
5a678a7
[EA3-111] feature : 과거 채팅 확인 및 무한 스크롤 페이징 구현
dbrkdgus00 Jul 16, 2025
c3cd899
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 18, 2025
6ac6837
[EA3-111] refactor : 컬럼 명 수정
dbrkdgus00 Jul 18, 2025
82e2bd4
[EA3-162] feature: 출석 기능 구현
pia01190 Jul 20, 2025
6382e80
[EA3-162] feature: 출석 조회 기능 구현
pia01190 Jul 20, 2025
f5ff6c4
[EA3-163] feature: 스터디 게시판 CUD 구현
Tokwasp Jul 21, 2025
30627c3
[EA3-163] chore: dto 패키지 이동
Tokwasp Jul 21, 2025
5598f8e
[EA3-163] refactor: 퀴즈 스터디 게시글 조회 변경
Tokwasp Jul 21, 2025
0908d5c
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
pia01190 Jul 21, 2025
fdd7fa0
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
pia01190 Jul 21, 2025
e86a7f4
[EA3-163] feature: 게시글 조회 기능 추가
Tokwasp Jul 21, 2025
0d5bf13
[EA3-162] feature: 출석 체크, 조회 테스트 작성
pia01190 Jul 21, 2025
7d54827
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 21, 2025
95f00c9
[EA3-161] feature: 스터디 생성 이미지 업로드
pia01190 Jul 21, 2025
963b9f7
[EA3-161] feature: 스터디 수정 이미지 업로드
pia01190 Jul 21, 2025
8def746
[EA3-163] feature: 스터디 게시글 페이징 조회 추가
Tokwasp Jul 21, 2025
313d58a
[EA3-163] chore: 스터디 게시글 스웨거 상세 추가
Tokwasp Jul 21, 2025
4e55484
[EA3-166] feature:PR 템플릿 조회 관련 수정 및 리팩터링
hyeunS-P Jul 21, 2025
7912fbd
[EA3-111] feature : 그룹채팅 과거 채팅 페이징 조회 API 구현 및 Swagger 문서 보완
dbrkdgus00 Jul 21, 2025
0b726bc
[EA3-166] fix:링크에 activated 컬럼 추가하여 조회가 정상적으로 동작하도록 수정
hyeunS-P Jul 21, 2025
d5d27d6
[EA3-161] refactor: 스터디 수정 이미지 업로드 오류 해결
pia01190 Jul 21, 2025
63780b6
[EA3-161] refactor: FileUploadResponse 생성자 오류 수정
pia01190 Jul 21, 2025
6484bae
[EA3-161] refactor: 스터디 테스트코드 오류 해결
pia01190 Jul 21, 2025
ecca1b8
[EA3-161] refactor: 스터디 테스트코드 수정
pia01190 Jul 21, 2025
2aff186
[EA3-138] feature: 모집글 수정시 모집마감 기간 수정 추가
Tokwasp Jul 21, 2025
6088c75
[EA3-138] feature: 모집글 댓글 CUD 추가
Tokwasp Jul 21, 2025
835f9ec
[EA3-111] docs : 중복된 의존성 삭제
dbrkdgus00 Jul 21, 2025
ce2c839
Merge pull request #157 from prgrms-web-devcourse-final-project/featu…
endorsement0912 Jul 21, 2025
4bbb9a8
Merge pull request #158 from prgrms-web-devcourse-final-project/featu…
endorsement0912 Jul 21, 2025
52ffbec
Merge pull request #159 from prgrms-web-devcourse-final-project/featu…
endorsement0912 Jul 21, 2025
4c2c5c6
Merge pull request #160 from prgrms-web-devcourse-final-project/featu…
endorsement0912 Jul 21, 2025
d93c48a
Merge pull request #161 from prgrms-web-devcourse-final-project/featu…
endorsement0912 Jul 21, 2025
71ef388
Merge branch 'develop' of https://github.com/prgrms-web-devcourse-fin…
dbrkdgus00 Jul 21, 2025
1acc0c3
Merge pull request #162 from prgrms-web-devcourse-final-project/featu…
dbrkdgus00 Jul 21, 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
4 changes: 3 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,11 @@ dependencies {
// 배포 관련 의존성
// runtimeOnly 'org.postgresql:postgresql'
implementation 'org.springframework.boot:spring-boot-devtools'

implementation 'com.google.cloud:spring-cloud-gcp-starter-secretmanager:4.9.1'
implementation 'com.google.cloud:google-cloud-storage:2.38.0'

// WebSocket + STOMP 통신용
implementation 'org.springframework.boot:spring-boot-starter-websocket'
}

tasks.named('test') {
Expand Down
26 changes: 23 additions & 3 deletions src/main/java/grep/neogul_coder/domain/attendance/Attendance.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

import grep.neogul_coder.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Getter
@Entity
public class Attendance extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long attendanceId;
private Long id;

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

@Column(nullable = false)
private LocalDate attendanceDate;
private LocalDateTime attendanceDate;

protected Attendance() {}

@Builder
private Attendance(Long studyId, Long userId, LocalDateTime attendanceDate) {
this.studyId = studyId;
this.userId = userId;
this.attendanceDate = attendanceDate;
}

public static Attendance create(Long studyId, Long userId) {
return Attendance.builder()
.studyId(studyId)
.userId(userId)
.attendanceDate(LocalDateTime.now())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
package grep.neogul_coder.domain.attendance.controller;

import grep.neogul_coder.domain.attendance.controller.dto.response.AttendanceResponse;
import grep.neogul_coder.domain.attendance.controller.dto.response.AttendanceInfoResponse;
import grep.neogul_coder.domain.attendance.service.AttendanceService;
import grep.neogul_coder.global.auth.Principal;
import grep.neogul_coder.global.response.ApiResponse;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping("/api/attendances")
@RequestMapping("/api/studies/{studyId}/attendances")
@RequiredArgsConstructor
@RestController
public class AttendanceController implements AttendanceSpecification {

private final AttendanceService attendanceService;

@GetMapping
public ApiResponse<List<AttendanceResponse>> getAttendances() {
return ApiResponse.success(List.of(new AttendanceResponse()));
public ApiResponse<AttendanceInfoResponse> getAttendances(@PathVariable("studyId") Long studyId,
@AuthenticationPrincipal Principal userDetails) {
AttendanceInfoResponse attendances = attendanceService.getAttendances(studyId, userDetails.getUserId());
return ApiResponse.success(attendances);
}

@PostMapping
public ApiResponse<Void> createAttendance() {
return ApiResponse.noContent();
public ApiResponse<Long> createAttendance(@PathVariable("studyId") Long studyId,
@AuthenticationPrincipal Principal userDetails) {
Long userId = userDetails.getUserId();
Long id = attendanceService.createAttendance(studyId, userId);
return ApiResponse.success(id);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package grep.neogul_coder.domain.attendance.controller;

import grep.neogul_coder.domain.attendance.controller.dto.response.AttendanceInfoResponse;
import grep.neogul_coder.domain.attendance.controller.dto.response.AttendanceResponse;
import grep.neogul_coder.global.auth.Principal;
import grep.neogul_coder.global.response.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -11,8 +13,8 @@
public interface AttendanceSpecification {

@Operation(summary = "출석 조회", description = "일주일 단위로 출석을 조회합니다.")
ApiResponse<List<AttendanceResponse>> getAttendances();
ApiResponse<AttendanceInfoResponse> getAttendances(Long studyId, Principal userDetails);

@Operation(summary = "출석 체크", description = "스터디에 출석을 합니다.")
ApiResponse<Void> createAttendance();
ApiResponse<Long> createAttendance(Long studyId, Principal userDetails);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package grep.neogul_coder.domain.attendance.controller.dto.response;

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

import java.util.List;

@Getter
public class AttendanceInfoResponse {

@Schema(description = "출석일 목록")
private List<AttendanceResponse> attendances;

@Schema(description = "출석률", example = "50")
private int attendanceRate;

@Builder
private AttendanceInfoResponse(List<AttendanceResponse> attendances, int attendanceRate) {
this.attendances = attendances;
this.attendanceRate = attendanceRate;
}

public static AttendanceInfoResponse of(List<AttendanceResponse> responses, int attendanceRate) {
return AttendanceInfoResponse.builder()
.attendances(responses)
.attendanceRate(attendanceRate)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package grep.neogul_coder.domain.attendance.controller.dto.response;

import grep.neogul_coder.domain.attendance.Attendance;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDate;
Expand All @@ -16,4 +18,19 @@ public class AttendanceResponse {

@Schema(description = "출석일", example = "2025-07-10")
private LocalDate attendanceDate;

@Builder
private AttendanceResponse(Long studyId, Long userId, LocalDate attendanceDate) {
this.studyId = studyId;
this.userId = userId;
this.attendanceDate = attendanceDate;
}

public static AttendanceResponse from(Attendance attendance) {
return AttendanceResponse.builder()
.studyId(attendance.getStudyId())
.userId(attendance.getUserId())
.attendanceDate(attendance.getAttendanceDate().toLocalDate())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package grep.neogul_coder.domain.attendance.exception.code;

import grep.neogul_coder.global.response.code.ErrorCode;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum AttendanceErrorCode implements ErrorCode {

ATTENDANCE_ALREADY_CHECKED("A001", HttpStatus.BAD_REQUEST, "출석은 하루에 한 번만 가능합니다.");

private final String code;
private final HttpStatus status;
private final String message;

AttendanceErrorCode(String code, HttpStatus status, String message) {
this.code = code;
this.status = status;
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package grep.neogul_coder.domain.attendance.repository;

import grep.neogul_coder.domain.attendance.Attendance;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.time.LocalDateTime;
import java.util.List;

public interface AttendanceRepository extends JpaRepository<Attendance, Long> {
@Query("select count(a) > 0 from Attendance a where a.studyId = :studyId and a.userId = :userId and a.attendanceDate between :startOfDay and :endOfDay")
boolean existsTodayAttendance(@Param("studyId") Long studyId,
@Param("userId") Long userId,
@Param("startOfDay") LocalDateTime startOfDay,
@Param("endOfDay") LocalDateTime endOfDay);

List<Attendance> findByStudyIdAndUserId(Long studyId, Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package grep.neogul_coder.domain.attendance.service;

import grep.neogul_coder.domain.attendance.Attendance;
import grep.neogul_coder.domain.attendance.controller.dto.response.AttendanceInfoResponse;
import grep.neogul_coder.domain.attendance.controller.dto.response.AttendanceResponse;
import grep.neogul_coder.domain.attendance.repository.AttendanceRepository;
import grep.neogul_coder.domain.study.Study;
import grep.neogul_coder.domain.study.repository.StudyMemberRepository;
import grep.neogul_coder.domain.study.repository.StudyRepository;
import grep.neogul_coder.global.exception.business.BusinessException;
import grep.neogul_coder.global.exception.business.NotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.List;

import static grep.neogul_coder.domain.attendance.exception.code.AttendanceErrorCode.ATTENDANCE_ALREADY_CHECKED;
import static grep.neogul_coder.domain.study.exception.code.StudyErrorCode.STUDY_NOT_FOUND;

@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class AttendanceService {

private final AttendanceRepository attendanceRepository;
private final StudyRepository studyRepository;
private final StudyMemberRepository studyMemberRepository;

public AttendanceInfoResponse getAttendances(Long studyId, Long userId) {
Study study = studyRepository.findByIdAndActivatedTrue(studyId)
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));

List<Attendance> attendances = attendanceRepository.findByStudyIdAndUserId(studyId, userId);
List<AttendanceResponse> responses = attendances.stream()
.map(AttendanceResponse::from)
.toList();

int attendanceRate = getAttendanceRate(studyId, userId, study, responses);

return AttendanceInfoResponse.of(responses, attendanceRate);
}

@Transactional
public Long createAttendance(Long studyId, Long userId) {
Study study = studyRepository.findByIdAndActivatedTrue(studyId)
.orElseThrow(() -> new NotFoundException(STUDY_NOT_FOUND));

validateNotAlreadyChecked(studyId, userId);

Attendance attendance = Attendance.create(studyId, userId);
attendanceRepository.save(attendance);
return attendance.getId();
}

private int getAttendanceRate(Long studyId, Long userId, Study study, List<AttendanceResponse> responses) {
LocalDate start = study.getStartDate().toLocalDate();
LocalDate participated = studyMemberRepository.findCreatedDateByStudyIdAndUserId(studyId, userId).toLocalDate();
LocalDate attendanceStart = start.isAfter(participated) ? start : participated;
LocalDate end = study.getEndDate().toLocalDate();

int totalDays = (int) ChronoUnit.DAYS.between(attendanceStart, end) + 1;
int attendDays = responses.size();
int attendanceRate = totalDays == 0 ? 0 : Math.round(((float) attendDays / totalDays) * 100);

return attendanceRate;
}

private void validateNotAlreadyChecked(Long studyId, Long userId) {
LocalDateTime startOfDay = LocalDate.now().atStartOfDay();
LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX);

if (attendanceRepository.existsTodayAttendance(studyId, userId, startOfDay, endOfDay)) {
throw new BusinessException(ATTENDANCE_ALREADY_CHECKED);
}
}
}
26 changes: 0 additions & 26 deletions src/main/java/grep/neogul_coder/domain/comment/Comment.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package grep.neogul_coder.domain.groupchat.controller;

import grep.neogul_coder.domain.groupchat.controller.dto.response.GroupChatMessageResponseDto;
import grep.neogul_coder.domain.groupchat.service.GroupChatService;
import grep.neogul_coder.global.response.ApiResponse;
import grep.neogul_coder.global.response.PageResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/chat")
public class GroupChatRestController implements GroupChatRestSpecification {

private final GroupChatService groupChatService;

// 과거 채팅 메시지 페이징 조회 (무한 스크롤용)
@Override
@GetMapping("/room/{roomId}/messages")
public ApiResponse<PageResponse<GroupChatMessageResponseDto>> getMessages(
@PathVariable("roomId") Long roomId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size
) {
// 서비스에서 페이징된 메시지 조회
PageResponse<GroupChatMessageResponseDto> pageResponse =
groupChatService.getMessages(roomId, page, size);

return ApiResponse.success(pageResponse);
}
}
Loading