Skip to content

Commit

Permalink
Merge pull request #68 from ecolink-JOIN/feat/#56
Browse files Browse the repository at this point in the history
[Feat] 인증 기능 추가
  • Loading branch information
homebdy authored Jan 17, 2025
2 parents 89679aa + f444557 commit 29353bc
Show file tree
Hide file tree
Showing 23 changed files with 445 additions and 13 deletions.
24 changes: 17 additions & 7 deletions src/main/java/com/join/core/common/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,25 @@ public enum ErrorCode {
* 출석 관련 오류
*/
OUT_OF_ATTENDANCE_TIME(HttpStatus.BAD_REQUEST, "AT-001", "출석 시간이 아닙니다."),

NOT_MEMBER_OF_STUDY(HttpStatus.FORBIDDEN, "AT-002", "스터디 참여자가 아닙니다."),
ATTENDANCE_ALREADY_COMPLETED(HttpStatus.CONFLICT, "AT-003", "이미 출석이 완료되었습니다."),

/**
* 북마크 관련 오류
*/
BOOKMARK_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "B-001", "북마크가 이미 존재합니다.");

/**
* 인증 관련 오류
*/
PROOF_PHOTO_NOT_FOUND(HttpStatus.NOT_FOUND, "PR-001", "해당 파일을 찾을 수 없습니다."),
EMPTY_PROOF_PHOTO(HttpStatus.BAD_REQUEST, "PR-002", "사진 인증을 위해 이미지를 업로드해야 합니다."),
DUPLICATED_PROOF(HttpStatus.CONFLICT, "PR-003", "이미 진행 중인 인증이 존재합니다."),
OUT_OF_PROOF_TIME(HttpStatus.BAD_REQUEST, "PR-001", "인증 시간이 아닙니다."),

/**
* 스터디 참여자 관련 오류
*/
NOT_MEMBER_OF_STUDY(HttpStatus.FORBIDDEN, "EN-002", "스터디에 참여중인 사용자가 아닙니다."),

/**
* 북마크 관련 오류
*/
BOOKMARK_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "B-001", "북마크가 이미 존재합니다.");

private final HttpStatus httpStatus;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.join.core.common.exception.impl;

import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.GeneralException;

public class BadRequestException extends GeneralException {

public BadRequestException(ErrorCode errorCode) {
super(errorCode);
}
}
27 changes: 24 additions & 3 deletions src/main/java/com/join/core/meeting/domain/Meeting.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package com.join.core.meeting.domain;

import com.join.core.common.exception.impl.BadRequestException;
import com.join.core.common.exception.impl.InvalidParamException;
import com.join.core.meeting.constant.MeetingStatus;
import com.join.core.study.domain.Study;
import jakarta.persistence.*;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Getter;
Expand All @@ -13,8 +22,9 @@
import java.time.LocalDateTime;
import java.time.LocalTime;

import static com.join.core.common.exception.ErrorCode.*;
import static com.join.core.meeting.constant.MeetingStatus.*;
import static com.join.core.common.exception.ErrorCode.INVALID_PARAMETER;
import static com.join.core.common.exception.ErrorCode.OUT_OF_PROOF_TIME;
import static com.join.core.meeting.constant.MeetingStatus.WAITING;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand Down Expand Up @@ -80,4 +90,15 @@ public boolean isWithinMeetingTime(LocalDateTime now) {
LocalTime meetingStart = stTime.minusMinutes(10);
return currentTime.isAfter(meetingStart) && currentTime.isBefore(endTime);
}

public void checkProofTime(LocalDateTime provenDate) {
LocalTime currentTime = provenDate.toLocalTime();
LocalTime proofEndTime = LocalTime.MIDNIGHT.minusNanos(1);

if (!studyDate.isEqual(provenDate.toLocalDate()) ||
!currentTime.isAfter(stTime) ||
!currentTime.isBefore(proofEndTime)) {
throw new BadRequestException(OUT_OF_PROOF_TIME);
}
}
}
8 changes: 8 additions & 0 deletions src/main/java/com/join/core/proof/constant/ProofStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.join.core.proof.constant;

public enum ProofStatus {

PENDING,
APPROVED,
REJECTED
}
5 changes: 4 additions & 1 deletion src/main/java/com/join/core/proof/constant/ProofType.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package com.join.core.proof.constant;

public enum ProofType {
PHOTO, TIMER
PHOTO, TIMER;

public boolean isPhotoType() {
return this == PHOTO;
}
}
46 changes: 46 additions & 0 deletions src/main/java/com/join/core/proof/controller/ProofController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.join.core.proof.controller;

import com.join.core.auth.domain.UserPrincipal;
import com.join.core.common.response.ApiResponse;
import com.join.core.proof.controller.specification.ProofControllerSpecification;
import com.join.core.proof.dto.request.CreateProofRequest;
import com.join.core.proof.dto.response.CreateProofResponse;
import com.join.core.proof.service.ProofService;
import com.join.core.proof.service.dto.CreateProofCommand;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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.RestController;

@RequiredArgsConstructor
@RestController
@RequestMapping("${api.prefix}/study/{studyToken}/meetings/{meetingNo}/proof")
public class ProofController implements ProofControllerSpecification {

private final ProofService proofService;

@PreAuthorize("isAuthenticated()")
@PostMapping
public ApiResponse<CreateProofResponse> createProof(
@AuthenticationPrincipal UserPrincipal userPrincipal,
@PathVariable String studyToken,
@PathVariable Integer meetingNo,
@Valid @RequestBody CreateProofRequest createProofRequest
) {
return ApiResponse.created(
proofService.createProof(new CreateProofCommand(
createProofRequest.proofType(),
createProofRequest.proofPhotoUrl(),
userPrincipal.getAvatarId(),
studyToken,
meetingNo,
createProofRequest.provenDate()
))
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.join.core.proof.controller.specification;

import com.join.core.auth.domain.UserPrincipal;
import com.join.core.common.response.ApiResponse;
import com.join.core.proof.dto.request.CreateProofRequest;
import com.join.core.proof.dto.response.CreateProofResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

public interface ProofControllerSpecification {

@Tag(name = "${swagger.tag.proof}")
@Operation(summary = "회차 인증 - 인증 필수",
description = "회차 인증 - 인증 필수",
security = {@SecurityRequirement(name = "session-token")})
ApiResponse<CreateProofResponse> createProof(
@AuthenticationPrincipal UserPrincipal userPrincipal,
@PathVariable String studyToken,
@PathVariable Integer meetingNo,
@Valid @RequestBody CreateProofRequest createProofRequest
);
}
31 changes: 29 additions & 2 deletions src/main/java/com/join/core/proof/domain/Proof.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,22 @@
import com.join.core.avatar.domain.Avatar;
import com.join.core.common.domain.BaseTimeEntity;
import com.join.core.meeting.domain.Meeting;
import com.join.core.proof.constant.ProofStatus;
import com.join.core.proof.constant.ProofType;
import jakarta.persistence.*;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.OnDelete;
Expand All @@ -24,7 +36,8 @@ public class Proof extends BaseTimeEntity {
private Long id;

@NotNull
private boolean proofStatus;
@Enumerated(EnumType.STRING)
private ProofStatus proofStatus;

@NotNull
@Enumerated(EnumType.STRING)
Expand All @@ -33,6 +46,10 @@ public class Proof extends BaseTimeEntity {
@NotNull
private LocalDateTime provenDate;

@NotNull
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private ProofPhoto photo;

@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@OnDelete(action = OnDeleteAction.CASCADE)
Expand All @@ -44,4 +61,14 @@ public class Proof extends BaseTimeEntity {
@JoinColumn(name = "avatar_id", nullable = false)
private Avatar avatar;

@Builder
public Proof(Long id, ProofStatus proofStatus, ProofType type, LocalDateTime provenDate, ProofPhoto photo, Meeting meeting, Avatar avatar) {
this.id = id;
this.proofStatus = proofStatus;
this.type = type;
this.provenDate = provenDate;
this.photo = photo;
this.meeting = meeting;
this.avatar = avatar;
}
}
33 changes: 33 additions & 0 deletions src/main/java/com/join/core/proof/domain/ProofPhoto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.join.core.proof.domain;

import com.join.core.file.domain.ImageFile;
import com.join.core.file.domain.SinglePhoto;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class ProofPhoto implements SinglePhoto {

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

@Valid
@NotNull
@Embedded
private ImageFile file;

public ProofPhoto(ImageFile file) {
this.file = file;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.join.core.proof.dto.request;

import com.join.core.proof.constant.ProofType;
import jakarta.validation.constraints.NotNull;

import java.time.LocalDateTime;

public record CreateProofRequest(
@NotNull ProofType proofType,
String proofPhotoUrl,
@NotNull LocalDateTime provenDate) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.join.core.proof.dto.response;

import com.join.core.proof.constant.ProofType;
import lombok.Builder;

import java.time.LocalDateTime;

@Builder
public record CreateProofResponse(
Long id,
ProofType proofType,
String proofPhotoUrl,
LocalDateTime provenDate
) {
}
34 changes: 34 additions & 0 deletions src/main/java/com/join/core/proof/mapper/ProofMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.join.core.proof.mapper;

import com.join.core.avatar.domain.Avatar;
import com.join.core.meeting.domain.Meeting;
import com.join.core.proof.constant.ProofStatus;
import com.join.core.proof.domain.Proof;
import com.join.core.proof.domain.ProofPhoto;
import com.join.core.proof.dto.response.CreateProofResponse;
import com.join.core.proof.service.dto.CreateProofCommand;
import org.springframework.stereotype.Component;

@Component
public class ProofMapper {

public Proof toEntity(CreateProofCommand command, Avatar avatar, Meeting meeting, ProofPhoto photo) {
return Proof.builder()
.type(command.proofType())
.provenDate(command.provenDate())
.avatar(avatar)
.meeting(meeting)
.photo(photo)
.proofStatus(ProofStatus.PENDING)
.build();
}

public CreateProofResponse toResponse(Proof proof) {
return CreateProofResponse.builder()
.id(proof.getId())
.proofType(proof.getType())
.proofPhotoUrl(proof.getPhoto().getFile().getUrl())
.provenDate(proof.getProvenDate())
.build();
}
}
19 changes: 19 additions & 0 deletions src/main/java/com/join/core/proof/repository/ProofReaderImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.join.core.proof.repository;

import com.join.core.proof.constant.ProofStatus;
import com.join.core.proof.service.ProofReader;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class ProofReaderImpl implements ProofReader {

private final ProofRepository proofRepository;

@Override
public boolean hasOngoingProof(Long avatarId, Long meetingId) {
return proofRepository.existsByAvatarIdAndMeetingIdAndProofStatus(avatarId, meetingId, ProofStatus.PENDING) ||
proofRepository.existsByAvatarIdAndMeetingIdAndProofStatus(avatarId, meetingId, ProofStatus.APPROVED);
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/join/core/proof/repository/ProofRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.join.core.proof.repository;

import com.join.core.proof.constant.ProofStatus;
import com.join.core.proof.domain.Proof;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProofRepository extends JpaRepository<Proof, Long> {

boolean existsByAvatarIdAndMeetingIdAndProofStatus(Long avatarId, Long meetingId, ProofStatus proofStatus);
}
18 changes: 18 additions & 0 deletions src/main/java/com/join/core/proof/repository/ProofStoreImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.join.core.proof.repository;

import com.join.core.proof.domain.Proof;
import com.join.core.proof.service.ProofStore;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class ProofStoreImpl implements ProofStore {

private final ProofRepository proofRepository;

@Override
public Proof save(Proof proof) {
return proofRepository.save(proof);
}
}
Loading

0 comments on commit 29353bc

Please sign in to comment.