Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ public enum ErrorCode {

// 멀버 κ΄€λ ¨
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER-001", "ν•΄λ‹Ή 멀버λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),
MEMBER_ALREADY_EXISTS(HttpStatus.CONFLICT, "MEMBER-002", "이미 λ§ˆμΈλ“œλ§΅μ— λ“±λ‘λœ λ©€λ²„μž…λ‹ˆλ‹€."),

// μ΄ˆλŒ€ κ΄€λ ¨
INVITATION_NOT_FOUND(HttpStatus.NOT_FOUND, "INVITE-001", "μ΄ˆλŒ€ 정보λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),
INVITATION_EXPIRED(HttpStatus.BAD_REQUEST, "INVITE-002", "만료된 μ΄ˆλŒ€μž…λ‹ˆλ‹€."),
INVITATION_ALREADY_EXISTS(HttpStatus.CONFLICT, "INVITE-003", "이미 μ΄ˆλŒ€ λŒ€κΈ° 쀑인 μ‚¬μš©μžμž…λ‹ˆλ‹€."),
CANNOT_INVITE_SELF(HttpStatus.BAD_REQUEST, "INVITE-004", "자기 μžμ‹ μ„ μ΄ˆλŒ€ν•  수 μ—†μŠ΅λ‹ˆλ‹€."),
INVITATION_REJECTED_USER(HttpStatus.FORBIDDEN, "INVITE-005", "μ΄ˆλŒ€λ₯Ό κ±°μ ˆν•œ μ‚¬μš©μžμ΄λ―€λ‘œ μ΄ˆλŒ€ν•  수 μ—†μŠ΅λ‹ˆλ‹€."),

// 방문기둝 κ΄€λ ¨
HISTORY_NOT_FOUND(HttpStatus.NOT_FOUND, "VISITHISTORY-001", "λ°©λ¬Έ 기둝을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."),

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,102 @@
package com.teamEWSN.gitdeun.invitation.controller;

import com.teamEWSN.gitdeun.common.jwt.CustomUserDetails;
import com.teamEWSN.gitdeun.invitation.dto.InvitationActionResponseDto;
import com.teamEWSN.gitdeun.invitation.dto.InvitationResponseDto;
import com.teamEWSN.gitdeun.invitation.dto.InviteRequestDto;
import com.teamEWSN.gitdeun.invitation.dto.LinkResponseDto;
import com.teamEWSN.gitdeun.invitation.service.InvitationService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;


@RestController
@RequiredArgsConstructor
@RequestMapping("/api/invitations")
public class InvitationController {

private final InvitationService invitationService;

// μ΄λ©”μΌλ‘œ 멀버 μ΄ˆλŒ€
@PostMapping("/mindmaps/{mapId}")
public ResponseEntity<Void> inviteByEmail(
@PathVariable Long mapId,
@Valid @RequestBody InviteRequestDto requestDto,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
invitationService.inviteUserByEmail(mapId, requestDto, userDetails.getId());
return ResponseEntity.noContent().build();
}

// νŠΉμ • λ§ˆμΈλ“œλ§΅μ˜ 전체 μ΄ˆλŒ€ λͺ©λ‘ 쑰회 (νŽ˜μ΄μ§€λ„€μ΄μ…˜ 적용)
@GetMapping("/mindmaps/{mapId}")
public ResponseEntity<Page< InvitationResponseDto>> getInvitations(
@PathVariable Long mapId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@PageableDefault(size = 10, sort = "createdAt,desc") Pageable pageable
) {
return ResponseEntity.ok(invitationService.getInvitationsByMindmap(mapId, userDetails.getId(), pageable));
}

// μ΄ˆλŒ€ 수락
@PostMapping("/{invitationId}/accept")
public ResponseEntity<Void> acceptInvitation(
@PathVariable Long invitationId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
invitationService.acceptInvitation(invitationId, userDetails.getId());
return ResponseEntity.noContent().build();
}

// μ΄ˆλŒ€ 거절
@PostMapping("/{invitationId}/reject")
public ResponseEntity<Void> rejectInvitation(
@PathVariable Long invitationId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
invitationService.rejectInvitation(invitationId, userDetails.getId());
return ResponseEntity.noContent().build();
}


// μ΄ˆλŒ€ 링크 생성
@PostMapping("/mindmaps/{mapId}/link")
public ResponseEntity<LinkResponseDto> createInvitationLink(
@PathVariable Long mapId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
return ResponseEntity.ok(invitationService.createInvitationLink(mapId, userDetails.getId()));
}

// μ΄ˆλŒ€ 링크λ₯Ό 톡해 수락 (λ‘œκ·ΈμΈν•œ μ‚¬μš©μžκ°€ 링크 클릭 μ‹œ)
@PostMapping("/link/{token}/accept")
public ResponseEntity<Void> acceptInvitationByLink(
@PathVariable String token,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
invitationService.acceptInvitationByLink(token, userDetails.getId());
return ResponseEntity.noContent().build();
}

// Ownerκ°€ 링크 μ΄ˆλŒ€ 승인
@PostMapping("/{invitationId}/approve")
public ResponseEntity<InvitationActionResponseDto> approveLinkInvitation(
@PathVariable Long invitationId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
return ResponseEntity.ok(invitationService.approveLinkInvitation(invitationId, userDetails.getId()));
}
// Ownerκ°€ 링크 μ΄ˆλŒ€ κ±°λΆ€
@PostMapping("/{invitationId}/reject-link")
public ResponseEntity<InvitationActionResponseDto> rejectLinkApproval(
@PathVariable Long invitationId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
return ResponseEntity.ok(invitationService.rejectLinkApproval(invitationId, userDetails.getId()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.teamEWSN.gitdeun.invitation.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class InvitationActionResponseDto {
private String message;
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.teamEWSN.gitdeun.invitation.dto;

import com.teamEWSN.gitdeun.invitation.entity.InvitationStatus;
import com.teamEWSN.gitdeun.mindmapmember.entity.MindmapRole;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@AllArgsConstructor
public class InvitationResponseDto {

private Long invitationId;
private String mindmapName;
private String inviteeName;
private String inviteeEmail;
private MindmapRole role;
private InvitationStatus status;
private LocalDateTime createdAt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.teamEWSN.gitdeun.invitation.dto;

import com.teamEWSN.gitdeun.mindmapmember.entity.MindmapRole;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class InviteRequestDto {
@Email(message = "μ˜¬λ°”λ₯Έ 이메일 ν˜•μ‹μ΄ μ•„λ‹™λ‹ˆλ‹€.")
@NotNull(message = "이메일을 μž…λ ₯ν•΄μ£Όμ„Έμš”.")
private String email;

@NotNull(message = "κΆŒν•œμ„ μ„ νƒν•΄μ£Όμ„Έμš”.")
private MindmapRole role;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.teamEWSN.gitdeun.invitation.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class LinkResponseDto {
private String invitationLink;
}
Original file line number Diff line number Diff line change
@@ -1,47 +1,61 @@
package com.teamEWSN.gitdeun.invitation.entity;

import com.teamEWSN.gitdeun.common.util.CreatedEntity;
import com.teamEWSN.gitdeun.mindmap.entity.Mindmap;
import com.teamEWSN.gitdeun.mindmapmember.entity.MindmapRole;
import com.teamEWSN.gitdeun.user.entity.User;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.CreationTimestamp;
import lombok.*;

import java.time.LocalDateTime;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE) // λΉŒλ”λ§Œ μ‚¬μš©ν•˜λ„λ‘ κ°•μ œ
@Builder(toBuilder = true)
@Table(name = "invitation")
public class Invitation {
public class Invitation extends CreatedEntity {

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

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "mindmap_id", nullable = false)
private Mindmap mindmap;

// μ΄ˆλŒ€ν•œ μ‚¬λžŒ
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "inviter_id", nullable = false)
private User inviter;

// μ΄ˆλŒ€λ°›μ€ μ‚¬λžŒ (이메일 μ΄ˆλŒ€μ˜ 경우)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "invitee_id")
private User invitee;

@Column(length = 36, nullable = false, unique = true)
private String token;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
@ColumnDefault("'READ_ONLY'")
private MindmapRole role; // μ΄ˆλŒ€ μ‹œ λΆ€μ—¬ν•  κΆŒν•œ

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private InvitationStatus status;

@CreationTimestamp
@Column(name = "invited_at", updatable = false)
private LocalDateTime invitedAt;
@Column(name = "expires_at")
private LocalDateTime expiresAt; // μ΄ˆλŒ€ 만료 μ‹œκ°„


public Invitation accept() {
this.status = InvitationStatus.ACCEPTED;
return this;
}

@Column(name = "is_accept", nullable = false)
@ColumnDefault("false")
private boolean isAccept;
public void reject() {
this.status = InvitationStatus.REJECTED;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.teamEWSN.gitdeun.invitation.entity;

public enum InvitationStatus {
READ_ONLY,
EDIT_ALLOWED
PENDING, // μ΄ˆλŒ€ 승인 λŒ€κΈ°μ€‘
ACCEPTED, // 수락
REJECTED // 거절
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.teamEWSN.gitdeun.invitation.mapper;

import com.teamEWSN.gitdeun.invitation.dto.InvitationResponseDto;
import com.teamEWSN.gitdeun.invitation.entity.Invitation;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;

@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface InvitationMapper {

@Mapping(source = "id", target = "invitationId")
@Mapping(source = "mindmap.field", target = "mindmapName")
@Mapping(source = "invitee.name", target = "inviteeName")
@Mapping(source = "invitee.email", target = "inviteeEmail", defaultExpression = "java(\"링크 μ΄ˆλŒ€\")")
InvitationResponseDto toResponseDto(Invitation invitation);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
package com.teamEWSN.gitdeun.invitation.repository;

public class InvitationRepository {

import com.teamEWSN.gitdeun.invitation.entity.Invitation;
import com.teamEWSN.gitdeun.invitation.entity.InvitationStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

@Repository
public interface InvitationRepository extends JpaRepository<Invitation, Long> {

// νŠΉμ • λ§ˆμΈλ“œλ§΅μ˜ λͺ¨λ“  μ΄ˆλŒ€ λͺ©λ‘μ„ νŽ˜μ΄μ§•ν•˜μ—¬ 쑰회
Page<Invitation> findByMindmapId(Long mindmapId, Pageable pageable);

// νŠΉμ • λ§ˆμΈλ“œλ§΅μ— νŠΉμ • μœ μ €κ°€ 이미 μ΄ˆλŒ€ λŒ€κΈ°μ€‘μΈμ§€ 확인
boolean existsByMindmapIdAndInviteeIdAndStatusAndExpiresAtAfter(Long mindmapId, Long inviteeId, InvitationStatus status, LocalDateTime now);

boolean existsByMindmapIdAndInviteeIdAndStatus(Long mindmapId, Long inviteeId, InvitationStatus status);

// μ‚¬μš©μžκ°€ 받은 λͺ¨λ“  μ΄ˆλŒ€ λͺ©λ‘ 쑰회
List<Invitation> findByInviteeIdAndStatus(Long inviteeId, InvitationStatus status);

// 고유 ν† ν°μœΌλ‘œ μ΄ˆλŒ€ 정보 쑰회
Optional<Invitation> findByToken(String token);

}
Loading