-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 공유 링크 활성/비활성 토글 api 구현 #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 5 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
3e5d840
feat: 공유 링크 생성 api 구현 (#61)
Kohseoyoung 630f21c
feat: 공유 링크 진입 검증 api 및 게스트 경로 인증 예외 추가 (#61)
Kohseoyoung 1e8368c
feat: 게스트 등록 api 구현 (#61)
Kohseoyoung 8719ba6
feat: 공유 링크 소유자 조회 api 구현 (#61)
Kohseoyoung 283b7e9
feat: 공유 링크 활성/비활성 토글 api 구현 (#61)
Kohseoyoung d08644a
Merge branch 'develop' into feature/61-share-link
Kohseoyoung 5f583df
refactor: 공유 링크 생성 작성자 인증 방식 개선 및 검증 보강 (#61)
Kohseoyoung File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
91 changes: 91 additions & 0 deletions
91
src/main/java/com/slatto/domain/sharelink/controller/ShareLinkController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package com.slatto.domain.sharelink.controller; | ||
|
|
||
| import com.slatto.domain.sharelink.dto.request.ShareLinkRequest.ShareLinkCreateReqDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.ShareLinkCreateResDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.ShareLinkEntryResDTO; | ||
| import com.slatto.domain.sharelink.dto.request.ShareLinkRequest.GuestCreateReqDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.GuestCreateResDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.ShareLinkInfoResDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.ShareLinkToggleResDTO; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import com.slatto.domain.sharelink.service.ShareLinkService; | ||
| import com.slatto.global.response.ApiResponse; | ||
| import com.slatto.global.response.code.CommonSuccessCode; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/v1") | ||
| @RequiredArgsConstructor | ||
| @Tag(name = "ShareLink", description = "공유 링크 API") | ||
| public class ShareLinkController { | ||
|
|
||
| private final ShareLinkService shareLinkService; | ||
|
|
||
| @Operation(summary = "공유 링크 생성", description = "영상당 1개만 생성 가능하며, 이미 있으면 409를 반환합니다.") | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| @PostMapping("/videos/{videoId}/share-links") | ||
| public ApiResponse<ShareLinkCreateResDTO> createShareLink( | ||
| @PathVariable Long videoId, | ||
| @RequestBody @Valid ShareLinkCreateReqDTO req | ||
| ) { | ||
| return ApiResponse.success( | ||
| CommonSuccessCode.CREATED, | ||
| shareLinkService.createShareLink(videoId, req) | ||
| ); | ||
| } | ||
|
|
||
| @Operation(summary = "공유 링크 진입 검증", description = "게스트가 링크로 접근했을 때 유효성을 확인합니다. 인증이 필요 없습니다.") | ||
| @GetMapping("/share-links/{token}") | ||
| public ApiResponse<ShareLinkEntryResDTO> getShareLinkByToken( | ||
| @PathVariable String token | ||
| ) { | ||
| return ApiResponse.success( | ||
| CommonSuccessCode.OK, | ||
| shareLinkService.getShareLinkByToken(token) | ||
| ); | ||
| } | ||
|
|
||
| @Operation(summary = "게스트 등록", description = "링크로 진입한 게스트가 이름을 등록하고 guestId를 발급받습니다. 인증이 필요 없습니다.") | ||
| @ResponseStatus(HttpStatus.CREATED) | ||
| @PostMapping("/share-links/{token}/guests") | ||
| public ApiResponse<GuestCreateResDTO> registerGuest( | ||
| @PathVariable String token, | ||
| @RequestBody @Valid GuestCreateReqDTO req | ||
| ) { | ||
| return ApiResponse.success( | ||
| CommonSuccessCode.CREATED, | ||
| shareLinkService.registerGuest(token, req) | ||
| ); | ||
| } | ||
|
|
||
| @Operation(summary = "공유 링크 조회 (소유자용)", description = "영상의 공유 링크를 조회합니다. 프로젝트 멤버만 가능합니다.") | ||
| @GetMapping("/videos/{videoId}/share-links") | ||
| public ApiResponse< | ||
| ShareLinkInfoResDTO> getShareLinkByVideo( | ||
| @PathVariable Long videoId, | ||
| @AuthenticationPrincipal Long userId | ||
| ) { | ||
| return ApiResponse.success( | ||
| CommonSuccessCode.OK, | ||
| shareLinkService.getShareLinkByVideo(videoId, userId) | ||
| ); | ||
| } | ||
|
|
||
| @Operation(summary = "공유 링크 활성/비활성 토글", description = "공유 링크의 활성 상태를 뒤집습니다. 프로젝트 멤버만 가능합니다.") | ||
| @PatchMapping("/share-links/{shareLinkId}") | ||
| public ApiResponse<ShareLinkToggleResDTO> toggleShareLink( | ||
| @PathVariable Long shareLinkId, | ||
| @AuthenticationPrincipal Long userId | ||
| ) { | ||
| return ApiResponse.success( | ||
| CommonSuccessCode.OK, | ||
| shareLinkService.toggleShareLink(shareLinkId, userId) | ||
| ); | ||
| } | ||
|
|
||
| } |
71 changes: 71 additions & 0 deletions
71
src/main/java/com/slatto/domain/sharelink/converter/ShareLinkConverter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| package com.slatto.domain.sharelink.converter; | ||
|
|
||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.ShareLinkCreateResDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.ShareLinkEntryResDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.GuestCreateResDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.ShareLinkInfoResDTO; | ||
| import com.slatto.domain.sharelink.dto.response.ShareLinkResponse.ShareLinkToggleResDTO; | ||
| import com.slatto.domain.sharelink.entity.Guest; | ||
| import com.slatto.domain.sharelink.entity.ShareLink; | ||
| import com.slatto.domain.video.entity.Video; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| @Component | ||
| public class ShareLinkConverter { | ||
|
|
||
| public ShareLink toShareLink(Video video, LocalDateTime expiredAt) { | ||
| return ShareLink.create(video, expiredAt); | ||
| } | ||
|
|
||
| public ShareLinkCreateResDTO toCreateResponse(ShareLink shareLink) { | ||
| return new ShareLinkCreateResDTO( | ||
| shareLink.getId(), | ||
| shareLink.getVideo().getId(), | ||
| shareLink.getToken(), | ||
| shareLink.getIsActive(), | ||
| shareLink.getExpiredAt(), | ||
| shareLink.getCreatedAt() | ||
| ); | ||
| } | ||
|
|
||
| public ShareLinkEntryResDTO toEntryResponse(ShareLink shareLink) { | ||
| return new ShareLinkEntryResDTO( | ||
| shareLink.getVideo().getId(), | ||
| shareLink.getVideo().getTitle(), | ||
| true // 게스트는 항상 닉네임을 입력해야 함 | ||
| ); | ||
| } | ||
|
|
||
| public Guest toGuest(ShareLink shareLink, String name) { | ||
| return Guest.create(shareLink, name); | ||
| } | ||
|
|
||
| public GuestCreateResDTO toGuestCreateResponse(Guest guest) { | ||
| return new GuestCreateResDTO( | ||
| guest.getId(), | ||
| guest.getShareLink().getId(), | ||
| guest.getName(), | ||
| guest.getCreatedAt() | ||
| ); | ||
| } | ||
|
|
||
| public ShareLinkInfoResDTO toInfoResponse(ShareLink shareLink) { | ||
| return new ShareLinkInfoResDTO( | ||
| shareLink.getId(), | ||
| shareLink.getVideo().getId(), | ||
| shareLink.getToken(), | ||
| shareLink.getIsActive(), | ||
| shareLink.getExpiredAt(), | ||
| shareLink.getCreatedAt() | ||
| ); | ||
| } | ||
|
|
||
| public ShareLinkToggleResDTO toToggleResponse(ShareLink shareLink) { | ||
| return new ShareLinkToggleResDTO( | ||
| shareLink.getId(), | ||
| shareLink.getIsActive() | ||
| ); | ||
| } | ||
| } |
27 changes: 27 additions & 0 deletions
27
src/main/java/com/slatto/domain/sharelink/dto/request/ShareLinkRequest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package com.slatto.domain.sharelink.dto.request; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| public class ShareLinkRequest { | ||
|
|
||
| @Schema(description = "공유 링크 생성 요청") | ||
| public record ShareLinkCreateReqDTO( | ||
| @Schema(example = "1") | ||
| @NotNull(message = "사용자 ID는 필수입니다.") | ||
| Long userId, | ||
|
|
||
| @Schema(description = "만료 일시. 미지정 시 무기한", example = "2026-12-31T23:59:59", nullable = true) | ||
| LocalDateTime expiredAt | ||
| ) { } | ||
|
|
||
| @Schema(description = "게스트 등록 요청") | ||
| public record GuestCreateReqDTO( | ||
| @Schema(example = "홍길동") | ||
| @NotBlank(message = "이름은 필수입니다.") | ||
| String name | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ) { } | ||
| } | ||
86 changes: 86 additions & 0 deletions
86
src/main/java/com/slatto/domain/sharelink/dto/response/ShareLinkResponse.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| package com.slatto.domain.sharelink.dto.response; | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| public class ShareLinkResponse { | ||
|
|
||
| @Schema(description = "공유 링크 생성 응답") | ||
| public record ShareLinkCreateResDTO( | ||
| @Schema(example = "5") | ||
| Long shareLinkId, | ||
|
|
||
| @Schema(example = "10") | ||
| Long videoId, | ||
|
|
||
| @Schema(example = "550e8400-e29b-41d4-a716-446655440000") | ||
| String token, | ||
|
|
||
| @Schema(example = "true") | ||
| Boolean isActive, | ||
|
|
||
| @Schema(example = "2026-12-31T23:59:59", nullable = true) | ||
| LocalDateTime expiredAt, | ||
|
|
||
| @Schema(example = "2026-07-24T14:00:00") | ||
| LocalDateTime createdAt | ||
| ) { } | ||
|
|
||
| @Schema(description = "공유 링크 진입 검증 응답") | ||
| public record ShareLinkEntryResDTO( | ||
| @Schema(example = "10") | ||
| Long videoId, | ||
|
|
||
| @Schema(example = "1차 편집본") | ||
| String videoTitle, | ||
|
|
||
| @Schema(example = "true", description = "게스트 닉네임 입력이 필요한지 여부") | ||
| Boolean requiresNickname | ||
| ) { } | ||
|
|
||
| @Schema(description = "게스트 등록 응답") | ||
| public record GuestCreateResDTO( | ||
| @Schema(example = "20") | ||
| Long guestId, | ||
|
|
||
| @Schema(example = "5") | ||
| Long shareLinkId, | ||
|
|
||
| @Schema(example = "홍길동") | ||
| String name, | ||
|
|
||
| @Schema(example = "2026-07-24T14:00:00") | ||
| LocalDateTime createdAt | ||
| ) { } | ||
|
|
||
| @Schema(description = "공유 링크 조회 응답 (소유자용)") | ||
| public record ShareLinkInfoResDTO( | ||
| @Schema(example = "5") | ||
| Long shareLinkId, | ||
|
|
||
| @Schema(example = "10") | ||
| Long videoId, | ||
|
|
||
| @Schema(example = "550e8400-e29b-41d4-a716-446655440000") | ||
| String token, | ||
|
|
||
| @Schema(example = "true") | ||
| Boolean isActive, | ||
|
|
||
| @Schema(example = "2026-12-31T23:59:59", nullable = true) | ||
| LocalDateTime expiredAt, | ||
|
|
||
| @Schema(example = "2026-07-24T14:00:00") | ||
| LocalDateTime createdAt | ||
| ) { } | ||
|
|
||
| @Schema(description = "공유 링크 활성/비활성 토글 응답") | ||
| public record ShareLinkToggleResDTO( | ||
| @Schema(example = "5") | ||
| Long shareLinkId, | ||
|
|
||
| @Schema(example = "false", description = "토글 후의 활성 상태") | ||
| Boolean isActive | ||
| ) { } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
src/main/java/com/slatto/domain/sharelink/exception/ShareLinkErrorCode.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.slatto.domain.sharelink.exception; | ||
|
|
||
| import com.slatto.global.response.code.BaseCode; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum ShareLinkErrorCode implements BaseCode { | ||
|
|
||
| SHARE_LINK_NOT_FOUND(HttpStatus.NOT_FOUND, "SHARELINK404", "공유 링크를 찾을 수 없습니다."), | ||
| SHARE_LINK_ALREADY_EXISTS(HttpStatus.CONFLICT, "SHARELINK409", "이미 이 영상의 공유 링크가 존재합니다."), | ||
| INVALID_EXPIRED_AT(HttpStatus.BAD_REQUEST, "SHARELINK400", "만료 일시는 현재 시각보다 이후여야 합니다."), | ||
| SHARE_LINK_UNAVAILABLE(HttpStatus.GONE, "SHARELINK410", "비활성화되었거나 만료된 링크입니다."); | ||
|
|
||
| private final HttpStatus httpStatus; | ||
| private final String code; | ||
| private final String message; | ||
|
|
||
| @Override | ||
| public boolean isSuccess() { | ||
| return false; | ||
| } | ||
| } |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/slatto/domain/sharelink/repository/ShareLinkRepository.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.slatto.domain.sharelink.repository; | ||
|
|
||
| import com.slatto.domain.sharelink.entity.ShareLink; | ||
| import org.springframework.data.jpa.repository.JpaRepository; | ||
|
|
||
| import java.util.Optional; | ||
|
|
||
| public interface ShareLinkRepository extends JpaRepository<ShareLink, Long> { | ||
|
|
||
| // 진입 검증용 — 토큰으로 조회 | ||
| Optional<ShareLink> findByToken(String token); | ||
|
|
||
| // 소유자 조회용 — 영상 기준 | ||
| Optional<ShareLink> findByVideoId(Long videoId); | ||
|
|
||
| // 중복 생성 방지용 (409) | ||
| boolean existsByVideoId(Long videoId); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.