diff --git a/src/main/java/com/opensource/docgrid/domain/collection/service/command/CollectionCommandService.java b/src/main/java/com/opensource/docgrid/domain/collection/service/command/CollectionCommandService.java index 4d6cf41..45caf44 100644 --- a/src/main/java/com/opensource/docgrid/domain/collection/service/command/CollectionCommandService.java +++ b/src/main/java/com/opensource/docgrid/domain/collection/service/command/CollectionCommandService.java @@ -6,6 +6,7 @@ import org.springframework.transaction.annotation.Transactional; import com.opensource.docgrid.domain.collection.converter.CollectionConverter; +import com.opensource.docgrid.domain.permission.service.query.PermissionQueryService; import com.opensource.docgrid.domain.collection.dto.request.AddDocumentRequest; import com.opensource.docgrid.domain.collection.dto.request.CreateCollectionRequest; import com.opensource.docgrid.domain.collection.dto.response.CollectionDocumentResponse; @@ -35,6 +36,7 @@ public class CollectionCommandService { private final DocumentRepository documentRepository; private final UserRepository userRepository; private final CollectionConverter collectionConverter; + private final PermissionQueryService permissionQueryService; // 폴더 생성 public CollectionResponse createCollection(Long userId, CreateCollectionRequest request) { @@ -66,8 +68,7 @@ public CollectionDocumentResponse addDocument(Long collectionId, Long userId, Ad DocumentCollection collection = collectionRepository.findById(collectionId) .orElseThrow(() -> new DocGridException(ErrorCode.COLLECTION_NOT_FOUND)); - // TODO: Issue 3 PermissionService 완성 후 canWriteDocument() 로 대체 - if (!collection.getOwner().getId().equals(userId)) { + if (!permissionQueryService.canWriteCollection(userId, collectionId)) { throw new DocGridException(ErrorCode.PERMISSION_DENIED); } diff --git a/src/main/java/com/opensource/docgrid/domain/permission/repository/CollectionPermissionRepository.java b/src/main/java/com/opensource/docgrid/domain/permission/repository/CollectionPermissionRepository.java index 23e7076..e8d75f5 100644 --- a/src/main/java/com/opensource/docgrid/domain/permission/repository/CollectionPermissionRepository.java +++ b/src/main/java/com/opensource/docgrid/domain/permission/repository/CollectionPermissionRepository.java @@ -1,8 +1,158 @@ package com.opensource.docgrid.domain.permission.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import com.opensource.docgrid.domain.permission.entity.CollectionPermission; public interface CollectionPermissionRepository extends JpaRepository { + + // ROLE live — 사용자 역할 기반 컬렉션→문서 읽기 권한 존재 여부 + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN CollectionDocument cd ON cd.collection = cp.collection + JOIN UserRole ur ON ur.role = cp.role + WHERE cd.document.id = :documentId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.ROLE + AND ur.user.id = :userId + AND cp.canRead = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsRoleReadPermissionForDocument(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // ROLE live — 사용자 역할 기반 컬렉션→문서 쓰기 권한 존재 여부 + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN CollectionDocument cd ON cd.collection = cp.collection + JOIN UserRole ur ON ur.role = cp.role + WHERE cd.document.id = :documentId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.ROLE + AND ur.user.id = :userId + AND cp.canWrite = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsRoleWritePermissionForDocument(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // ROLE live — 사용자 역할 기반 컬렉션→문서 관리 권한 존재 여부 + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN CollectionDocument cd ON cd.collection = cp.collection + JOIN UserRole ur ON ur.role = cp.role + WHERE cd.document.id = :documentId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.ROLE + AND ur.user.id = :userId + AND cp.canAdmin = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsRoleAdminPermissionForDocument(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // DEPARTMENT live — 사용자 부서 기반 컬렉션→문서 읽기 권한 존재 여부 + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN CollectionDocument cd ON cd.collection = cp.collection + JOIN User u ON u.department = cp.department + WHERE cd.document.id = :documentId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.DEPARTMENT + AND u.id = :userId + AND cp.canRead = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsDeptReadPermissionForDocument(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // DEPARTMENT live — 사용자 부서 기반 컬렉션→문서 쓰기 권한 존재 여부 + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN CollectionDocument cd ON cd.collection = cp.collection + JOIN User u ON u.department = cp.department + WHERE cd.document.id = :documentId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.DEPARTMENT + AND u.id = :userId + AND cp.canWrite = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsDeptWritePermissionForDocument(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // DEPARTMENT live — 사용자 부서 기반 컬렉션→문서 관리 권한 존재 여부 + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN CollectionDocument cd ON cd.collection = cp.collection + JOIN User u ON u.department = cp.department + WHERE cd.document.id = :documentId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.DEPARTMENT + AND u.id = :userId + AND cp.canAdmin = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsDeptAdminPermissionForDocument(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // USER 직접 권한 — 컬렉션에 쓰기 권한이 있는지 (canWriteCollection 판단용) + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + WHERE cp.collection.id = :collectionId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.USER + AND cp.user.id = :userId + AND cp.canWrite = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsUserWritePermission(@Param("userId") Long userId, @Param("collectionId") Long collectionId); + + // USER 직접 권한 — 컬렉션에 관리 권한이 있는지 (canAdminCollection 판단용) + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + WHERE cp.collection.id = :collectionId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.USER + AND cp.user.id = :userId + AND cp.canAdmin = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsUserAdminPermission(@Param("userId") Long userId, @Param("collectionId") Long collectionId); + + // ROLE live — 사용자 역할 기반 컬렉션 쓰기 권한 존재 여부 (canWriteCollection 판단용) + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN UserRole ur ON ur.role = cp.role + WHERE cp.collection.id = :collectionId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.ROLE + AND ur.user.id = :userId + AND cp.canWrite = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsRoleWritePermissionForCollection(@Param("userId") Long userId, @Param("collectionId") Long collectionId); + + // ROLE live — 사용자 역할 기반 컬렉션 관리 권한 존재 여부 (canAdminCollection 판단용) + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN UserRole ur ON ur.role = cp.role + WHERE cp.collection.id = :collectionId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.ROLE + AND ur.user.id = :userId + AND cp.canAdmin = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsRoleAdminPermissionForCollection(@Param("userId") Long userId, @Param("collectionId") Long collectionId); + + // DEPARTMENT live — 사용자 부서 기반 컬렉션 쓰기 권한 존재 여부 (canWriteCollection 판단용) + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN User u ON u.department = cp.department + WHERE cp.collection.id = :collectionId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.DEPARTMENT + AND u.id = :userId + AND cp.canWrite = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsDeptWritePermissionForCollection(@Param("userId") Long userId, @Param("collectionId") Long collectionId); + + // DEPARTMENT live — 사용자 부서 기반 컬렉션 관리 권한 존재 여부 (canAdminCollection 판단용) + @Query(""" + SELECT COUNT(cp) > 0 FROM CollectionPermission cp + JOIN User u ON u.department = cp.department + WHERE cp.collection.id = :collectionId + AND cp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.DEPARTMENT + AND u.id = :userId + AND cp.canAdmin = true + AND (cp.expiresAt IS NULL OR cp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsDeptAdminPermissionForCollection(@Param("userId") Long userId, @Param("collectionId") Long collectionId); } diff --git a/src/main/java/com/opensource/docgrid/domain/permission/repository/DocumentPermissionRepository.java b/src/main/java/com/opensource/docgrid/domain/permission/repository/DocumentPermissionRepository.java index 6411d10..99716d6 100644 --- a/src/main/java/com/opensource/docgrid/domain/permission/repository/DocumentPermissionRepository.java +++ b/src/main/java/com/opensource/docgrid/domain/permission/repository/DocumentPermissionRepository.java @@ -1,8 +1,82 @@ package com.opensource.docgrid.domain.permission.repository; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import com.opensource.docgrid.domain.permission.entity.DocumentPermission; public interface DocumentPermissionRepository extends JpaRepository { + + // ROLE live — 사용자 역할 기반 문서 읽기 권한 존재 여부 + @Query(""" + SELECT COUNT(dp) > 0 FROM DocumentPermission dp + JOIN UserRole ur ON ur.role = dp.role + WHERE dp.document.id = :documentId + AND dp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.ROLE + AND ur.user.id = :userId + AND dp.canRead = true + AND (dp.expiresAt IS NULL OR dp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsRoleReadPermission(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // ROLE live — 사용자 역할 기반 문서 쓰기 권한 존재 여부 + @Query(""" + SELECT COUNT(dp) > 0 FROM DocumentPermission dp + JOIN UserRole ur ON ur.role = dp.role + WHERE dp.document.id = :documentId + AND dp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.ROLE + AND ur.user.id = :userId + AND dp.canWrite = true + AND (dp.expiresAt IS NULL OR dp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsRoleWritePermission(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // ROLE live — 사용자 역할 기반 문서 관리 권한 존재 여부 + @Query(""" + SELECT COUNT(dp) > 0 FROM DocumentPermission dp + JOIN UserRole ur ON ur.role = dp.role + WHERE dp.document.id = :documentId + AND dp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.ROLE + AND ur.user.id = :userId + AND dp.canAdmin = true + AND (dp.expiresAt IS NULL OR dp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsRoleAdminPermission(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // DEPARTMENT live — 사용자 부서 기반 문서 읽기 권한 존재 여부 + @Query(""" + SELECT COUNT(dp) > 0 FROM DocumentPermission dp + JOIN User u ON u.department = dp.department + WHERE dp.document.id = :documentId + AND dp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.DEPARTMENT + AND u.id = :userId + AND dp.canRead = true + AND (dp.expiresAt IS NULL OR dp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsDeptReadPermission(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // DEPARTMENT live — 사용자 부서 기반 문서 쓰기 권한 존재 여부 + @Query(""" + SELECT COUNT(dp) > 0 FROM DocumentPermission dp + JOIN User u ON u.department = dp.department + WHERE dp.document.id = :documentId + AND dp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.DEPARTMENT + AND u.id = :userId + AND dp.canWrite = true + AND (dp.expiresAt IS NULL OR dp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsDeptWritePermission(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // DEPARTMENT live — 사용자 부서 기반 문서 관리 권한 존재 여부 + @Query(""" + SELECT COUNT(dp) > 0 FROM DocumentPermission dp + JOIN User u ON u.department = dp.department + WHERE dp.document.id = :documentId + AND dp.targetType = com.opensource.docgrid.domain.permission.enums.PermissionTargetType.DEPARTMENT + AND u.id = :userId + AND dp.canAdmin = true + AND (dp.expiresAt IS NULL OR dp.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsDeptAdminPermission(@Param("userId") Long userId, @Param("documentId") Long documentId); } diff --git a/src/main/java/com/opensource/docgrid/domain/permission/repository/UserDocumentAccessCacheRepository.java b/src/main/java/com/opensource/docgrid/domain/permission/repository/UserDocumentAccessCacheRepository.java index 5e14827..b8e6708 100644 --- a/src/main/java/com/opensource/docgrid/domain/permission/repository/UserDocumentAccessCacheRepository.java +++ b/src/main/java/com/opensource/docgrid/domain/permission/repository/UserDocumentAccessCacheRepository.java @@ -14,9 +14,11 @@ public interface UserDocumentAccessCacheRepository extends JpaRepository { + // 특정 권한 출처(sourceType + sourceId)의 캐시 단건 조회 Optional findByUserIdAndDocumentIdAndSourceTypeAndSourceId( Long userId, Long documentId, AccessSourceType sourceType, Long sourceId); + // 특정 권한 출처에서 파생된 캐시 전체 무효화 (invalidated_at 일괄 설정) @Modifying(clearAutomatically = true) @Query(""" UPDATE UserDocumentAccessCache c @@ -26,6 +28,7 @@ Optional findByUserIdAndDocumentIdAndSourceTypeAndSourc int bulkInvalidateBySource(@Param("sourceType") AccessSourceType sourceType, @Param("sourceId") Long sourceId); + // 특정 권한 출처에서 파생된 캐시 전체 권한 갱신 @Modifying(clearAutomatically = true) @Query(""" UPDATE UserDocumentAccessCache c @@ -41,6 +44,7 @@ int bulkUpdateBySource(@Param("userId") Long userId, @Param("canAdmin") boolean canAdmin, @Param("expiresAt") LocalDateTime expiresAt); + // 특정 권한 출처에서 이미 캐시된 문서 ID 목록 조회 (배치 INSERT 시 중복 방지용) @Query(""" SELECT c.document.id FROM UserDocumentAccessCache c WHERE c.user.id = :userId AND c.sourceType = :sourceType AND c.sourceId = :sourceId @@ -49,4 +53,34 @@ List findDocumentIdsByUserIdAndSourceTypeAndSourceId( @Param("userId") Long userId, @Param("sourceType") AccessSourceType sourceType, @Param("sourceId") Long sourceId); + + // 유효한 읽기 캐시 존재 여부 (invalidated_at IS NULL, 만료 미포함) + @Query(""" + SELECT COUNT(c) > 0 FROM UserDocumentAccessCache c + WHERE c.user.id = :userId AND c.document.id = :documentId + AND c.canRead = true + AND c.invalidatedAt IS NULL + AND (c.expiresAt IS NULL OR c.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsValidReadCache(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // 유효한 쓰기 캐시 존재 여부 + @Query(""" + SELECT COUNT(c) > 0 FROM UserDocumentAccessCache c + WHERE c.user.id = :userId AND c.document.id = :documentId + AND c.canWrite = true + AND c.invalidatedAt IS NULL + AND (c.expiresAt IS NULL OR c.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsValidWriteCache(@Param("userId") Long userId, @Param("documentId") Long documentId); + + // 유효한 관리 캐시 존재 여부 + @Query(""" + SELECT COUNT(c) > 0 FROM UserDocumentAccessCache c + WHERE c.user.id = :userId AND c.document.id = :documentId + AND c.canAdmin = true + AND c.invalidatedAt IS NULL + AND (c.expiresAt IS NULL OR c.expiresAt > CURRENT_TIMESTAMP) + """) + boolean existsValidAdminCache(@Param("userId") Long userId, @Param("documentId") Long documentId); } diff --git a/src/main/java/com/opensource/docgrid/domain/permission/service/command/CollectionPermissionCommandService.java b/src/main/java/com/opensource/docgrid/domain/permission/service/command/CollectionPermissionCommandService.java index 417d860..be5503b 100644 --- a/src/main/java/com/opensource/docgrid/domain/permission/service/command/CollectionPermissionCommandService.java +++ b/src/main/java/com/opensource/docgrid/domain/permission/service/command/CollectionPermissionCommandService.java @@ -12,6 +12,7 @@ import com.opensource.docgrid.domain.collection.repository.CollectionRepository; import com.opensource.docgrid.domain.document.entity.Document; import com.opensource.docgrid.domain.permission.converter.PermissionConverter; +import com.opensource.docgrid.domain.permission.service.query.PermissionQueryService; import com.opensource.docgrid.domain.permission.dto.request.GrantPermissionRequest; import com.opensource.docgrid.domain.permission.dto.response.CollectionPermissionResponse; import com.opensource.docgrid.domain.permission.entity.CollectionPermission; @@ -43,6 +44,7 @@ public class CollectionPermissionCommandService { private final RoleRepository roleRepository; private final DepartmentRepository departmentRepository; private final PermissionConverter permissionConverter; + private final PermissionQueryService permissionQueryService; // 컬렉션 권한 부여 public CollectionPermissionResponse grantPermission(Long collectionId, Long grantorId, @@ -50,8 +52,7 @@ public CollectionPermissionResponse grantPermission(Long collectionId, Long gran DocumentCollection collection = collectionRepository.findById(collectionId) .orElseThrow(() -> new DocGridException(ErrorCode.COLLECTION_NOT_FOUND)); - // TODO: Issue 3 PermissionService 완성 후 canAdminDocument() 로 대체 - if (!collection.getOwner().getId().equals(grantorId)) { + if (!permissionQueryService.canAdminCollection(grantorId, collectionId)) { throw new DocGridException(ErrorCode.PERMISSION_DENIED); } @@ -109,8 +110,7 @@ public void revokePermission(Long collectionId, Long permissionId, Long revokerI throw new DocGridException(ErrorCode.COLLECTION_PERMISSION_NOT_FOUND); } - // TODO: Issue 3 PermissionService 완성 후 canAdminDocument() 로 대체 - if (!collection.getOwner().getId().equals(revokerId)) { + if (!permissionQueryService.canAdminCollection(revokerId, collectionId)) { throw new DocGridException(ErrorCode.PERMISSION_DENIED); } diff --git a/src/main/java/com/opensource/docgrid/domain/permission/service/command/DocumentPermissionCommandService.java b/src/main/java/com/opensource/docgrid/domain/permission/service/command/DocumentPermissionCommandService.java index 57c8b37..efc3f77 100644 --- a/src/main/java/com/opensource/docgrid/domain/permission/service/command/DocumentPermissionCommandService.java +++ b/src/main/java/com/opensource/docgrid/domain/permission/service/command/DocumentPermissionCommandService.java @@ -8,6 +8,7 @@ import com.opensource.docgrid.domain.document.entity.Document; import com.opensource.docgrid.domain.document.repository.DocumentRepository; import com.opensource.docgrid.domain.permission.converter.PermissionConverter; +import com.opensource.docgrid.domain.permission.service.query.PermissionQueryService; import com.opensource.docgrid.domain.permission.dto.request.GrantPermissionRequest; import com.opensource.docgrid.domain.permission.dto.response.DocumentPermissionResponse; import com.opensource.docgrid.domain.permission.entity.DocumentPermission; @@ -38,6 +39,7 @@ public class DocumentPermissionCommandService { private final RoleRepository roleRepository; private final DepartmentRepository departmentRepository; private final PermissionConverter permissionConverter; + private final PermissionQueryService permissionQueryService; // 문서 단건 예외 권한 부여 public DocumentPermissionResponse grantPermission(Long documentId, Long grantorId, @@ -45,8 +47,7 @@ public DocumentPermissionResponse grantPermission(Long documentId, Long grantorI Document document = documentRepository.findById(documentId) .orElseThrow(() -> new DocGridException(ErrorCode.DOCUMENT_NOT_FOUND)); - // TODO: Issue 3 PermissionService 완성 후 canAdminDocument() 로 대체 - if (!document.getOwner().getId().equals(grantorId)) { + if (!permissionQueryService.canAdminDocument(grantorId, documentId)) { throw new DocGridException(ErrorCode.PERMISSION_DENIED); } @@ -105,8 +106,7 @@ public void revokePermission(Long documentId, Long permissionId, Long revokerId) throw new DocGridException(ErrorCode.DOCUMENT_PERMISSION_NOT_FOUND); } - // TODO: Issue 3 PermissionService 완성 후 canAdminDocument() 로 대체 - if (!permission.getDocument().getOwner().getId().equals(revokerId)) { + if (!permissionQueryService.canAdminDocument(revokerId, documentId)) { throw new DocGridException(ErrorCode.PERMISSION_DENIED); } diff --git a/src/main/java/com/opensource/docgrid/domain/permission/service/query/PermissionQueryService.java b/src/main/java/com/opensource/docgrid/domain/permission/service/query/PermissionQueryService.java new file mode 100644 index 0000000..74941f0 --- /dev/null +++ b/src/main/java/com/opensource/docgrid/domain/permission/service/query/PermissionQueryService.java @@ -0,0 +1,200 @@ +package com.opensource.docgrid.domain.permission.service.query; + +import org.springframework.transaction.annotation.Transactional; +import org.springframework.stereotype.Service; + +import com.opensource.docgrid.domain.collection.entity.DocumentCollection; +import com.opensource.docgrid.domain.collection.repository.CollectionRepository; +import com.opensource.docgrid.domain.document.entity.Document; +import com.opensource.docgrid.domain.document.enums.VisibilityType; +import com.opensource.docgrid.domain.document.repository.DocumentRepository; +import com.opensource.docgrid.domain.permission.repository.CollectionPermissionRepository; +import com.opensource.docgrid.domain.permission.repository.DocumentPermissionRepository; +import com.opensource.docgrid.domain.permission.repository.UserDocumentAccessCacheRepository; +import com.opensource.docgrid.global.exception.DocGridException; +import com.opensource.docgrid.global.exception.ErrorCode; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Transactional(readOnly = true) +@Service +@RequiredArgsConstructor +public class PermissionQueryService { + + private final CollectionRepository collectionRepository; + private final DocumentRepository documentRepository; + private final UserDocumentAccessCacheRepository cacheRepository; + private final DocumentPermissionRepository documentPermissionRepository; + private final CollectionPermissionRepository collectionPermissionRepository; + + // 문서 읽기 권한 판단 (5단계) + public boolean canReadDocument(Long userId, Long documentId) { + long start = System.nanoTime(); + Document document = documentRepository.findById(documentId) + .orElseThrow(() -> new DocGridException(ErrorCode.DOCUMENT_NOT_FOUND)); + + // 1단계: 소유자 + long t1 = System.nanoTime(); + if (document.getOwner().getId().equals(userId)) { + log.info("[PERM] canRead owner=true doc={} user={} elapsed={}ms", documentId, userId, ms(start)); + return true; + } + + // 2단계: PUBLIC + long t2 = System.nanoTime(); + double step1Ms = (t2 - t1) / 1_000_000.0; + if (document.getVisibility() == VisibilityType.PUBLIC) { + log.info("[PERM] canRead public=true doc={} user={} step1={}ms elapsed={}ms", + documentId, userId, step1Ms, ms(start)); + return true; + } + + // 3단계: USER 캐시 + long t3 = System.nanoTime(); + double step2Ms = (t3 - t2) / 1_000_000.0; + if (cacheRepository.existsValidReadCache(userId, documentId)) { + log.info("[PERM] canRead cache=true doc={} user={} step2={}ms elapsed={}ms", + documentId, userId, step2Ms, ms(start)); + return true; + } + + // 4단계: ROLE live + long t4 = System.nanoTime(); + double step3Ms = (t4 - t3) / 1_000_000.0; + if (documentPermissionRepository.existsRoleReadPermission(userId, documentId) + || collectionPermissionRepository.existsRoleReadPermissionForDocument(userId, documentId)) { + log.info("[PERM] canRead role=true doc={} user={} step3={}ms elapsed={}ms", + documentId, userId, step3Ms, ms(start)); + return true; + } + + // 5단계: DEPARTMENT live + long t5 = System.nanoTime(); + double step4Ms = (t5 - t4) / 1_000_000.0; + if (documentPermissionRepository.existsDeptReadPermission(userId, documentId) + || collectionPermissionRepository.existsDeptReadPermissionForDocument(userId, documentId)) { + log.info("[PERM] canRead dept=true doc={} user={} step4={}ms elapsed={}ms", + documentId, userId, step4Ms, ms(start)); + return true; + } + + double step5Ms = (System.nanoTime() - t5) / 1_000_000.0; + log.info("[PERM] canRead denied doc={} user={} step5={}ms elapsed={}ms", + documentId, userId, step5Ms, ms(start)); + return false; + } + + // 문서 쓰기 권한 판단 (4단계) + public boolean canWriteDocument(Long userId, Long documentId) { + long start = System.nanoTime(); + Document document = documentRepository.findById(documentId) + .orElseThrow(() -> new DocGridException(ErrorCode.DOCUMENT_NOT_FOUND)); + + long t1 = System.nanoTime(); + if (document.getOwner().getId().equals(userId)) { + log.info("[PERM] canWrite owner=true doc={} user={} elapsed={}ms", documentId, userId, ms(start)); + return true; + } + + long t2 = System.nanoTime(); + double step1Ms = (t2 - t1) / 1_000_000.0; + if (cacheRepository.existsValidWriteCache(userId, documentId)) { + log.info("[PERM] canWrite cache=true doc={} user={} step1={}ms elapsed={}ms", + documentId, userId, step1Ms, ms(start)); + return true; + } + + long t3 = System.nanoTime(); + double step2Ms = (t3 - t2) / 1_000_000.0; + if (documentPermissionRepository.existsRoleWritePermission(userId, documentId) + || collectionPermissionRepository.existsRoleWritePermissionForDocument(userId, documentId)) { + log.info("[PERM] canWrite role=true doc={} user={} step2={}ms elapsed={}ms", + documentId, userId, step2Ms, ms(start)); + return true; + } + + long t4 = System.nanoTime(); + double step3Ms = (t4 - t3) / 1_000_000.0; + if (documentPermissionRepository.existsDeptWritePermission(userId, documentId) + || collectionPermissionRepository.existsDeptWritePermissionForDocument(userId, documentId)) { + log.info("[PERM] canWrite dept=true doc={} user={} step3={}ms elapsed={}ms", + documentId, userId, step3Ms, ms(start)); + return true; + } + + double step4Ms = (System.nanoTime() - t4) / 1_000_000.0; + log.info("[PERM] canWrite denied doc={} user={} step4={}ms elapsed={}ms", + documentId, userId, step4Ms, ms(start)); + return false; + } + + // 문서 관리 권한 판단 (4단계) + public boolean canAdminDocument(Long userId, Long documentId) { + long start = System.nanoTime(); + Document document = documentRepository.findById(documentId) + .orElseThrow(() -> new DocGridException(ErrorCode.DOCUMENT_NOT_FOUND)); + + long t1 = System.nanoTime(); + if (document.getOwner().getId().equals(userId)) { + log.info("[PERM] canAdmin owner=true doc={} user={} elapsed={}ms", documentId, userId, ms(start)); + return true; + } + + long t2 = System.nanoTime(); + double step1Ms = (t2 - t1) / 1_000_000.0; + if (cacheRepository.existsValidAdminCache(userId, documentId)) { + log.info("[PERM] canAdmin cache=true doc={} user={} step1={}ms elapsed={}ms", + documentId, userId, step1Ms, ms(start)); + return true; + } + + long t3 = System.nanoTime(); + double step2Ms = (t3 - t2) / 1_000_000.0; + if (documentPermissionRepository.existsRoleAdminPermission(userId, documentId) + || collectionPermissionRepository.existsRoleAdminPermissionForDocument(userId, documentId)) { + log.info("[PERM] canAdmin role=true doc={} user={} step2={}ms elapsed={}ms", + documentId, userId, step2Ms, ms(start)); + return true; + } + + long t4 = System.nanoTime(); + double step3Ms = (t4 - t3) / 1_000_000.0; + if (documentPermissionRepository.existsDeptAdminPermission(userId, documentId) + || collectionPermissionRepository.existsDeptAdminPermissionForDocument(userId, documentId)) { + log.info("[PERM] canAdmin dept=true doc={} user={} step3={}ms elapsed={}ms", + documentId, userId, step3Ms, ms(start)); + return true; + } + + double step4Ms = (System.nanoTime() - t4) / 1_000_000.0; + log.info("[PERM] canAdmin denied doc={} user={} step4={}ms elapsed={}ms", + documentId, userId, step4Ms, ms(start)); + return false; + } + + // 컬렉션 쓰기 권한 판단 (소유자, USER/ROLE/DEPT 직접 권한) + public boolean canWriteCollection(Long userId, Long collectionId) { + DocumentCollection collection = collectionRepository.findById(collectionId) + .orElseThrow(() -> new DocGridException(ErrorCode.COLLECTION_NOT_FOUND)); + if (collection.getOwner().getId().equals(userId)) return true; + if (collectionPermissionRepository.existsUserWritePermission(userId, collectionId)) return true; + if (collectionPermissionRepository.existsRoleWritePermissionForCollection(userId, collectionId)) return true; + return collectionPermissionRepository.existsDeptWritePermissionForCollection(userId, collectionId); + } + + // 컬렉션 관리 권한 판단 (소유자, USER/ROLE/DEPT 직접 권한) + public boolean canAdminCollection(Long userId, Long collectionId) { + DocumentCollection collection = collectionRepository.findById(collectionId) + .orElseThrow(() -> new DocGridException(ErrorCode.COLLECTION_NOT_FOUND)); + if (collection.getOwner().getId().equals(userId)) return true; + if (collectionPermissionRepository.existsUserAdminPermission(userId, collectionId)) return true; + if (collectionPermissionRepository.existsRoleAdminPermissionForCollection(userId, collectionId)) return true; + return collectionPermissionRepository.existsDeptAdminPermissionForCollection(userId, collectionId); + } + + private double ms(long fromNano) { + return (System.nanoTime() - fromNano) / 1_000_000.0; + } +} diff --git a/src/test/java/com/opensource/docgrid/domain/collection/service/command/CollectionCommandServiceTest.java b/src/test/java/com/opensource/docgrid/domain/collection/service/command/CollectionCommandServiceTest.java index e93a388..500a5a3 100644 --- a/src/test/java/com/opensource/docgrid/domain/collection/service/command/CollectionCommandServiceTest.java +++ b/src/test/java/com/opensource/docgrid/domain/collection/service/command/CollectionCommandServiceTest.java @@ -29,6 +29,7 @@ import com.opensource.docgrid.domain.document.entity.Document; import com.opensource.docgrid.domain.document.enums.VisibilityType; import com.opensource.docgrid.domain.document.repository.DocumentRepository; +import com.opensource.docgrid.domain.permission.service.query.PermissionQueryService; import com.opensource.docgrid.domain.user.entity.User; import com.opensource.docgrid.domain.user.repository.UserRepository; import com.opensource.docgrid.global.exception.DocGridException; @@ -56,6 +57,9 @@ class CollectionCommandServiceTest { @Mock private CollectionConverter collectionConverter; + @Mock + private PermissionQueryService permissionQueryService; + // ==================== createCollection ==================== @Test @@ -142,6 +146,7 @@ void addDocument_succeeds_when_validRequest() { AddDocumentRequest request = new AddDocumentRequest(CollectionFixture.DOCUMENT_ID); given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)).willReturn(Optional.of(collection)); + given(permissionQueryService.canWriteCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID)).willReturn(true); given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); given(collectionDocumentRepository.existsByCollectionIdAndDocumentId( CollectionFixture.COLLECTION_ID, CollectionFixture.DOCUMENT_ID)).willReturn(false); @@ -186,6 +191,7 @@ void addDocument_throws_when_documentNotFound() { User owner = CollectionFixture.createOwner(); DocumentCollection collection = CollectionFixture.createCollection(owner); given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)).willReturn(Optional.of(collection)); + given(permissionQueryService.canWriteCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID)).willReturn(true); given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.empty()); assertThatThrownBy(() -> collectionCommandService.addDocument( @@ -201,6 +207,7 @@ void addDocument_throws_when_alreadyExists() { DocumentCollection collection = CollectionFixture.createCollection(owner); Document document = CollectionFixture.createDocument(owner); given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)).willReturn(Optional.of(collection)); + given(permissionQueryService.canWriteCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID)).willReturn(true); given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); given(collectionDocumentRepository.existsByCollectionIdAndDocumentId( CollectionFixture.COLLECTION_ID, CollectionFixture.DOCUMENT_ID)).willReturn(true); diff --git a/src/test/java/com/opensource/docgrid/domain/permission/service/command/CollectionPermissionCommandServiceTest.java b/src/test/java/com/opensource/docgrid/domain/permission/service/command/CollectionPermissionCommandServiceTest.java index 5305e2b..351730f 100644 --- a/src/test/java/com/opensource/docgrid/domain/permission/service/command/CollectionPermissionCommandServiceTest.java +++ b/src/test/java/com/opensource/docgrid/domain/permission/service/command/CollectionPermissionCommandServiceTest.java @@ -31,6 +31,7 @@ import com.opensource.docgrid.domain.permission.enums.PermissionType; import com.opensource.docgrid.domain.permission.fixture.PermissionFixture; import com.opensource.docgrid.domain.permission.repository.CollectionPermissionRepository; +import com.opensource.docgrid.domain.permission.service.query.PermissionQueryService; import com.opensource.docgrid.domain.user.entity.Role; import com.opensource.docgrid.domain.user.entity.User; import com.opensource.docgrid.domain.user.repository.DepartmentRepository; @@ -54,6 +55,7 @@ class CollectionPermissionCommandServiceTest { @Mock private RoleRepository roleRepository; @Mock private DepartmentRepository departmentRepository; @Mock private PermissionConverter permissionConverter; + @Mock private PermissionQueryService permissionQueryService; // ==================== grantPermission ==================== @@ -68,6 +70,7 @@ void grantPermission_user_updatesCache() { PermissionTargetType.USER, CollectionFixture.USER_ID, null, null, PermissionType.READ, null); given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)).willReturn(Optional.of(collection)); + given(permissionQueryService.canAdminCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID)).willReturn(true); given(userRepository.findById(CollectionFixture.USER_ID)).willReturn(Optional.of(owner)); given(userRepository.getReferenceById(CollectionFixture.USER_ID)).willReturn(owner); given(collectionDocumentRepository.findAllByCollectionId(CollectionFixture.COLLECTION_ID)) @@ -77,7 +80,7 @@ void grantPermission_user_updatesCache() { service.grantPermission(CollectionFixture.COLLECTION_ID, CollectionFixture.USER_ID, request); then(collectionPermissionRepository).should().save(any(CollectionPermission.class)); - then(cacheService).should().grantUserPermission(any(), any(), any(boolean.class), + then(cacheService).should().bulkGrantUserPermission(any(), any(), any(boolean.class), any(boolean.class), any(boolean.class), any(), any(), any()); } @@ -91,6 +94,7 @@ void grantPermission_role_noCacheUpdate() { PermissionTargetType.ROLE, null, PermissionFixture.ROLE_ID, null, PermissionType.READ, null); given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)).willReturn(Optional.of(collection)); + given(permissionQueryService.canAdminCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID)).willReturn(true); given(roleRepository.findById(PermissionFixture.ROLE_ID)).willReturn(Optional.of(role)); given(userRepository.getReferenceById(CollectionFixture.USER_ID)).willReturn(owner); given(permissionConverter.toCollectionPermissionResponse(any())).willReturn(null); @@ -98,7 +102,7 @@ void grantPermission_role_noCacheUpdate() { service.grantPermission(CollectionFixture.COLLECTION_ID, CollectionFixture.USER_ID, request); then(collectionPermissionRepository).should().save(any(CollectionPermission.class)); - then(cacheService).should(never()).grantUserPermission(any(), any(), any(boolean.class), + then(cacheService).should(never()).bulkGrantUserPermission(any(), any(), any(boolean.class), any(boolean.class), any(boolean.class), any(), any(), any()); } @@ -141,6 +145,7 @@ void grantPermission_throws_when_invalidTargetType() { PermissionTargetType.USER, null, null, null, PermissionType.READ, null); given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)).willReturn(Optional.of(collection)); + given(permissionQueryService.canAdminCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID)).willReturn(true); assertThatThrownBy(() -> service.grantPermission( CollectionFixture.COLLECTION_ID, CollectionFixture.USER_ID, request)) @@ -151,23 +156,20 @@ void grantPermission_throws_when_invalidTargetType() { // ==================== revokePermission ==================== @Test - @DisplayName("USER 권한을 회수하면 캐시도 무효화된다") + @DisplayName("USER 권한을 회수하면 캐시도 일괄 무효화된다") void revokePermission_user_invalidatesCache() { User owner = CollectionFixture.createOwner(); DocumentCollection collection = CollectionFixture.createCollection(owner); - Document document = CollectionFixture.createDocument(owner); - CollectionDocument cd = buildCollectionDocument(collection, document); CollectionPermission permission = PermissionFixture.createCollectionPermission(collection, owner); given(collectionPermissionRepository.findById(PermissionFixture.PERMISSION_ID)) .willReturn(Optional.of(permission)); - given(collectionDocumentRepository.findAllByCollectionId(CollectionFixture.COLLECTION_ID)) - .willReturn(List.of(cd)); + given(permissionQueryService.canAdminCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID)).willReturn(true); service.revokePermission(CollectionFixture.COLLECTION_ID, PermissionFixture.PERMISSION_ID, CollectionFixture.USER_ID); - then(cacheService).should().revokeUserPermission(any(), any(), any(), any()); + then(cacheService).should().bulkRevokeBySource(any(), any()); then(collectionPermissionRepository).should().delete(permission); } diff --git a/src/test/java/com/opensource/docgrid/domain/permission/service/command/DocumentPermissionCommandServiceTest.java b/src/test/java/com/opensource/docgrid/domain/permission/service/command/DocumentPermissionCommandServiceTest.java index 3c9791f..3dd10de 100644 --- a/src/test/java/com/opensource/docgrid/domain/permission/service/command/DocumentPermissionCommandServiceTest.java +++ b/src/test/java/com/opensource/docgrid/domain/permission/service/command/DocumentPermissionCommandServiceTest.java @@ -25,6 +25,7 @@ import com.opensource.docgrid.domain.permission.enums.PermissionType; import com.opensource.docgrid.domain.permission.fixture.PermissionFixture; import com.opensource.docgrid.domain.permission.repository.DocumentPermissionRepository; +import com.opensource.docgrid.domain.permission.service.query.PermissionQueryService; import com.opensource.docgrid.domain.user.entity.User; import com.opensource.docgrid.domain.user.repository.DepartmentRepository; import com.opensource.docgrid.domain.user.repository.RoleRepository; @@ -46,6 +47,7 @@ class DocumentPermissionCommandServiceTest { @Mock private RoleRepository roleRepository; @Mock private DepartmentRepository departmentRepository; @Mock private PermissionConverter permissionConverter; + @Mock private PermissionQueryService permissionQueryService; @Test @DisplayName("USER 대상 문서 권한을 부여하면 캐시도 함께 갱신된다") @@ -56,6 +58,7 @@ void grantPermission_user_updatesCache() { PermissionTargetType.USER, CollectionFixture.USER_ID, null, null, PermissionType.READ, null); given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(permissionQueryService.canAdminDocument(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID)).willReturn(true); given(userRepository.findById(CollectionFixture.USER_ID)).willReturn(Optional.of(owner)); given(userRepository.getReferenceById(CollectionFixture.USER_ID)).willReturn(owner); given(permissionConverter.toDocumentPermissionResponse(any())).willReturn(null); @@ -76,6 +79,7 @@ void grantPermission_role_noCacheUpdate() { PermissionTargetType.ROLE, null, PermissionFixture.ROLE_ID, null, PermissionType.READ, null); given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(permissionQueryService.canAdminDocument(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID)).willReturn(true); given(roleRepository.findById(PermissionFixture.ROLE_ID)).willReturn(Optional.of(PermissionFixture.createRole())); given(userRepository.getReferenceById(CollectionFixture.USER_ID)).willReturn(owner); given(permissionConverter.toDocumentPermissionResponse(any())).willReturn(null); @@ -128,6 +132,7 @@ void revokePermission_user_invalidatesCache() { given(documentPermissionRepository.findById(PermissionFixture.PERMISSION_ID)) .willReturn(Optional.of(permission)); + given(permissionQueryService.canAdminDocument(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID)).willReturn(true); service.revokePermission(CollectionFixture.DOCUMENT_ID, PermissionFixture.PERMISSION_ID, CollectionFixture.USER_ID); @@ -174,6 +179,7 @@ void grantPermission_throws_when_invalidTargetType() { PermissionTargetType.ROLE, null, null, null, PermissionType.READ, null); given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(permissionQueryService.canAdminDocument(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID)).willReturn(true); assertThatThrownBy(() -> service.grantPermission( CollectionFixture.DOCUMENT_ID, CollectionFixture.USER_ID, request)) diff --git a/src/test/java/com/opensource/docgrid/domain/permission/service/query/PermissionQueryServiceTest.java b/src/test/java/com/opensource/docgrid/domain/permission/service/query/PermissionQueryServiceTest.java new file mode 100644 index 0000000..f81fc37 --- /dev/null +++ b/src/test/java/com/opensource/docgrid/domain/permission/service/query/PermissionQueryServiceTest.java @@ -0,0 +1,480 @@ +package com.opensource.docgrid.domain.permission.service.query; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.never; + +import java.util.Optional; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.opensource.docgrid.domain.collection.fixture.CollectionFixture; +import com.opensource.docgrid.domain.collection.repository.CollectionRepository; +import com.opensource.docgrid.domain.document.entity.Document; +import com.opensource.docgrid.domain.document.enums.VisibilityType; +import com.opensource.docgrid.domain.document.repository.DocumentRepository; +import com.opensource.docgrid.domain.permission.repository.CollectionPermissionRepository; +import com.opensource.docgrid.domain.permission.repository.DocumentPermissionRepository; +import com.opensource.docgrid.domain.permission.repository.UserDocumentAccessCacheRepository; +import com.opensource.docgrid.domain.user.entity.User; +import com.opensource.docgrid.global.exception.DocGridException; +import com.opensource.docgrid.global.exception.ErrorCode; + +@ExtendWith(MockitoExtension.class) +@DisplayName("PermissionQueryService 단위 테스트") +class PermissionQueryServiceTest { + + @InjectMocks + private PermissionQueryService service; + + @Mock private CollectionRepository collectionRepository; + @Mock private DocumentRepository documentRepository; + @Mock private UserDocumentAccessCacheRepository cacheRepository; + @Mock private DocumentPermissionRepository documentPermissionRepository; + @Mock private CollectionPermissionRepository collectionPermissionRepository; + + // ==================== canReadDocument ==================== + + @Test + @DisplayName("소유자는 canReadDocument가 true다") + void canReadDocument_owner_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + + boolean result = service.canReadDocument(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(cacheRepository).should(never()).existsValidReadCache(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("PUBLIC 문서는 누구나 canReadDocument가 true다") + void canReadDocument_public_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + org.springframework.test.util.ReflectionTestUtils.setField(document, "visibility", VisibilityType.PUBLIC); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + + boolean result = service.canReadDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(cacheRepository).should(never()).existsValidReadCache(otherUserId, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("유효한 USER 캐시가 있으면 canReadDocument가 true다") + void canReadDocument_validCache_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidReadCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canReadDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(documentPermissionRepository).should(never()).existsRoleReadPermission(otherUserId, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("ROLE live 권한이 있으면 canReadDocument가 true다") + void canReadDocument_roleLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidReadCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleReadPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canReadDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(documentPermissionRepository).should(never()).existsDeptReadPermission(otherUserId, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("DEPARTMENT live 권한이 있으면 canReadDocument가 true다") + void canReadDocument_deptLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidReadCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleReadPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsRoleReadPermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsDeptReadPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canReadDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("모든 단계를 통과하지 못하면 canReadDocument가 false다") + void canReadDocument_noPermission_returnsFalse() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidReadCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleReadPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsRoleReadPermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsDeptReadPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsDeptReadPermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + + boolean result = service.canReadDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isFalse(); + } + + @Test + @DisplayName("문서가 없으면 DOCUMENT_NOT_FOUND 예외가 발생한다") + void canReadDocument_documentNotFound_throwsException() { + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.empty()); + + assertThatThrownBy(() -> service.canReadDocument(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID)) + .isInstanceOf(DocGridException.class) + .hasFieldOrPropertyWithValue("errorCode", ErrorCode.DOCUMENT_NOT_FOUND); + } + + // ==================== canWriteDocument ==================== + + @Test + @DisplayName("소유자는 canWriteDocument가 true다") + void canWriteDocument_owner_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + + boolean result = service.canWriteDocument(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(cacheRepository).should(never()).existsValidWriteCache(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("유효한 USER 캐시가 있으면 canWriteDocument가 true다") + void canWriteDocument_validCache_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidWriteCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canWriteDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(documentPermissionRepository).should(never()).existsRoleWritePermission(otherUserId, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("ROLE live 권한이 있으면 canWriteDocument가 true다") + void canWriteDocument_roleLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidWriteCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleWritePermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canWriteDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(documentPermissionRepository).should(never()).existsDeptWritePermission(otherUserId, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("DEPARTMENT live 권한이 있으면 canWriteDocument가 true다") + void canWriteDocument_deptLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidWriteCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleWritePermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsRoleWritePermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsDeptWritePermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canWriteDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("모든 단계를 통과하지 못하면 canWriteDocument가 false다") + void canWriteDocument_noPermission_returnsFalse() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidWriteCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleWritePermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsRoleWritePermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsDeptWritePermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsDeptWritePermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + + boolean result = service.canWriteDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isFalse(); + } + + // ==================== canAdminDocument ==================== + + @Test + @DisplayName("소유자는 canAdminDocument가 true다") + void canAdminDocument_owner_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + + boolean result = service.canAdminDocument(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(cacheRepository).should(never()).existsValidAdminCache(CollectionFixture.USER_ID, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("유효한 USER 캐시가 있으면 canAdminDocument가 true다") + void canAdminDocument_validCache_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidAdminCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canAdminDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(documentPermissionRepository).should(never()).existsRoleAdminPermission(otherUserId, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("ROLE live 권한이 있으면 canAdminDocument가 true다") + void canAdminDocument_roleLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidAdminCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleAdminPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canAdminDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + then(documentPermissionRepository).should(never()).existsDeptAdminPermission(otherUserId, CollectionFixture.DOCUMENT_ID); + } + + @Test + @DisplayName("DEPARTMENT live 권한이 있으면 canAdminDocument가 true다") + void canAdminDocument_deptLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidAdminCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleAdminPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsRoleAdminPermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsDeptAdminPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(true); + + boolean result = service.canAdminDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("모든 단계를 통과하지 못하면 canAdminDocument가 false다") + void canAdminDocument_noPermission_returnsFalse() { + User owner = CollectionFixture.createOwner(); + Document document = CollectionFixture.createDocument(owner); + Long otherUserId = 99L; + given(documentRepository.findById(CollectionFixture.DOCUMENT_ID)).willReturn(Optional.of(document)); + given(cacheRepository.existsValidAdminCache(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsRoleAdminPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsRoleAdminPermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(documentPermissionRepository.existsDeptAdminPermission(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + given(collectionPermissionRepository.existsDeptAdminPermissionForDocument(otherUserId, CollectionFixture.DOCUMENT_ID)).willReturn(false); + + boolean result = service.canAdminDocument(otherUserId, CollectionFixture.DOCUMENT_ID); + + assertThat(result).isFalse(); + } + + // ==================== canWriteCollection ==================== + + @Test + @DisplayName("컬렉션 소유자는 canWriteCollection이 true다") + void canWriteCollection_owner_returnsTrue() { + User owner = CollectionFixture.createOwner(); + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + + boolean result = service.canWriteCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID); + + assertThat(result).isTrue(); + then(collectionPermissionRepository).should(never()).existsUserWritePermission(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID); + } + + @Test + @DisplayName("USER 권한으로 write가 부여된 경우 canWriteCollection이 true다") + void canWriteCollection_userPermission_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Long otherUserId = 99L; + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + given(collectionPermissionRepository.existsUserWritePermission(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(true); + + boolean result = service.canWriteCollection(otherUserId, CollectionFixture.COLLECTION_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("ROLE live 권한이 있으면 canWriteCollection이 true다") + void canWriteCollection_roleLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Long otherUserId = 99L; + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + given(collectionPermissionRepository.existsUserWritePermission(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsRoleWritePermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(true); + + boolean result = service.canWriteCollection(otherUserId, CollectionFixture.COLLECTION_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("DEPARTMENT live 권한이 있으면 canWriteCollection이 true다") + void canWriteCollection_deptLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Long otherUserId = 99L; + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + given(collectionPermissionRepository.existsUserWritePermission(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsRoleWritePermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsDeptWritePermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(true); + + boolean result = service.canWriteCollection(otherUserId, CollectionFixture.COLLECTION_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("권한이 없으면 canWriteCollection이 false다") + void canWriteCollection_noPermission_returnsFalse() { + User owner = CollectionFixture.createOwner(); + Long otherUserId = 99L; + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + given(collectionPermissionRepository.existsUserWritePermission(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsRoleWritePermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsDeptWritePermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + + boolean result = service.canWriteCollection(otherUserId, CollectionFixture.COLLECTION_ID); + + assertThat(result).isFalse(); + } + + // ==================== canAdminCollection ==================== + + @Test + @DisplayName("컬렉션 소유자는 canAdminCollection이 true다") + void canAdminCollection_owner_returnsTrue() { + User owner = CollectionFixture.createOwner(); + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + + boolean result = service.canAdminCollection(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID); + + assertThat(result).isTrue(); + then(collectionPermissionRepository).should(never()).existsUserAdminPermission(CollectionFixture.USER_ID, CollectionFixture.COLLECTION_ID); + } + + @Test + @DisplayName("USER 권한으로 admin이 부여된 경우 canAdminCollection이 true다") + void canAdminCollection_userPermission_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Long otherUserId = 99L; + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + given(collectionPermissionRepository.existsUserAdminPermission(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(true); + + boolean result = service.canAdminCollection(otherUserId, CollectionFixture.COLLECTION_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("ROLE live 권한이 있으면 canAdminCollection이 true다") + void canAdminCollection_roleLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Long otherUserId = 99L; + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + given(collectionPermissionRepository.existsUserAdminPermission(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsRoleAdminPermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(true); + + boolean result = service.canAdminCollection(otherUserId, CollectionFixture.COLLECTION_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("DEPARTMENT live 권한이 있으면 canAdminCollection이 true다") + void canAdminCollection_deptLive_returnsTrue() { + User owner = CollectionFixture.createOwner(); + Long otherUserId = 99L; + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + given(collectionPermissionRepository.existsUserAdminPermission(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsRoleAdminPermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsDeptAdminPermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(true); + + boolean result = service.canAdminCollection(otherUserId, CollectionFixture.COLLECTION_ID); + + assertThat(result).isTrue(); + } + + @Test + @DisplayName("권한이 없으면 canAdminCollection이 false다") + void canAdminCollection_noPermission_returnsFalse() { + User owner = CollectionFixture.createOwner(); + Long otherUserId = 99L; + given(collectionRepository.findById(CollectionFixture.COLLECTION_ID)) + .willReturn(Optional.of(CollectionFixture.createCollection(owner))); + given(collectionPermissionRepository.existsUserAdminPermission(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsRoleAdminPermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + given(collectionPermissionRepository.existsDeptAdminPermissionForCollection(otherUserId, CollectionFixture.COLLECTION_ID)) + .willReturn(false); + + boolean result = service.canAdminCollection(otherUserId, CollectionFixture.COLLECTION_ID); + + assertThat(result).isFalse(); + } +}