EbookFileService.java

package qwerty.chaekit.service.ebook;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import qwerty.chaekit.domain.ebook.Ebook;
import qwerty.chaekit.domain.ebook.repository.EbookRepository;
import qwerty.chaekit.domain.ebook.request.EbookRequest;
import qwerty.chaekit.domain.ebook.request.EbookRequestRepository;
import qwerty.chaekit.domain.ebook.request.EbookRequestStatus;
import qwerty.chaekit.domain.member.publisher.PublisherProfile;
import qwerty.chaekit.domain.member.user.UserProfile;
import qwerty.chaekit.dto.ebook.request.EbookRequestFetchResponse;
import qwerty.chaekit.dto.ebook.request.EbookRequestRejectRequest;
import qwerty.chaekit.dto.ebook.upload.EbookDownloadResponse;
import qwerty.chaekit.dto.ebook.upload.EbookPostRequest;
import qwerty.chaekit.dto.ebook.upload.EbookPostResponse;
import qwerty.chaekit.dto.page.PageResponse;
import qwerty.chaekit.global.enums.ErrorCode;
import qwerty.chaekit.global.exception.BadRequestException;
import qwerty.chaekit.global.exception.ForbiddenException;
import qwerty.chaekit.global.security.resolver.PublisherToken;
import qwerty.chaekit.global.security.resolver.UserToken;
import qwerty.chaekit.mapper.EbookRequestMapper;
import qwerty.chaekit.service.util.EmailNotificationService;
import qwerty.chaekit.service.util.EntityFinder;
import qwerty.chaekit.service.util.FileService;

import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
public class EbookFileService {
    private final EbookRepository ebookRepository;
    private final EbookPolicy ebookPolicy;
    private final FileService fileService;
    private final EntityFinder entityFinder;
    private final EbookRequestRepository ebookRequestRepository;
    private final EbookRequestMapper ebookRequestMapper;
    private final EmailNotificationService emailNotificationService;

    @Transactional
    public EbookPostResponse uploadEbook(PublisherToken publisherToken, EbookPostRequest request) {
        PublisherProfile publisher = entityFinder.findPublisher(publisherToken.publisherId());

        if(!publisher.isApproved()) {
            throw new ForbiddenException(ErrorCode.PUBLISHER_NOT_APPROVED);
        }

        String fileKey = fileService.uploadEbook(request.file());
        String coverImageKey = fileService.uploadEbookCoverImageIfPresent(request.coverImageFile());
        String coverImageURL = fileService.convertToPublicImageURL(coverImageKey);

        EbookRequest ebookRequest = EbookRequest.builder()
                .title(request.title())
                .author(request.author())
                .description(request.description())
                .size(request.file().getSize())
                .price(request.price())
                .fileKey(fileKey)
                .coverImageKey(coverImageKey)
                .publisher(publisher)
                .build();
        EbookRequest saved = ebookRequestRepository.save(ebookRequest);

        if (publisher.isAdmin()) {
            approveRequest(saved);
        }

        return EbookPostResponse.of(saved.toEbook(), ebookRequest.getId(), coverImageURL);
    }

    @Transactional
    public EbookDownloadResponse getPresignedEbookUrlForUser(UserToken userToken, Long ebookId) {
        UserProfile user = entityFinder.findUser(userToken.userId());
        Ebook ebook = entityFinder.findEbook(ebookId);

        ebookPolicy.assertEBookPurchased(user, ebook);
        
        String ebookFileKey = ebook.getFileKey();
        String downloadUrl = fileService.getEbookDownloadUrl(ebookFileKey);

        return EbookDownloadResponse.of(downloadUrl);
    }
    
    @Transactional
    public EbookDownloadResponse getPresignedEbookUrlForPublisher(PublisherToken publisherToken, Long ebookId) {
        PublisherProfile publisher = entityFinder.findPublisher(publisherToken.publisherId());
        Ebook ebook = entityFinder.findEbook(ebookId);

        if (!ebook.isOwnedBy(publisher)) {
            throw new ForbiddenException(ErrorCode.EBOOK_NOT_OWNED);
        }

        String ebookFileKey = ebook.getFileKey();
        String downloadUrl = fileService.getEbookDownloadUrl(ebookFileKey);

        return EbookDownloadResponse.of(downloadUrl);
    }
    
    @Transactional
    public EbookDownloadResponse getPresignedTempEbookUrlForPublisher(PublisherToken publisherToken, Long ebookRequestId) {
        PublisherProfile publisher = entityFinder.findPublisher(publisherToken.publisherId());
        EbookRequest ebookRequest = entityFinder.findEbookRequest(ebookRequestId);

        if (!ebookRequest.isRequestedBy(publisher) && publisher.isNotAdmin()) {
            throw new ForbiddenException(ErrorCode.EBOOK_REQUEST_NOT_YOURS);
        }

        String ebookFileKey = ebookRequest.getFileKey();
        String downloadUrl = fileService.getEbookDownloadUrl(ebookFileKey);

        return EbookDownloadResponse.of(downloadUrl);
    }
    
    @Transactional
    public void approveEbookByAdmin(PublisherToken publisherToken, Long requestId) {
        PublisherProfile publisher = entityFinder.findPublisher(publisherToken.publisherId());
        EbookRequest request = entityFinder.findEbookRequest(requestId);
        
        assertAdmin(publisher);
        assertRequestIsPending(request);

        approveRequest(request);
    }

    @Transactional
    public void rejectEbookByAdmin(PublisherToken publisherToken, Long requestId, EbookRequestRejectRequest requestBody) {
        PublisherProfile publisher = entityFinder.findPublisher(publisherToken.publisherId());
        EbookRequest request = entityFinder.findEbookRequest(requestId);
        
        assertAdmin(publisher);
        assertRequestIsPending(request);
        
        request.reject(requestBody.reason());
        emailNotificationService.sendEbookRejectionEmail(publisher.getMember().getEmail(), requestBody.reason());
    }

    @Transactional(readOnly = true)
    public PageResponse<EbookRequestFetchResponse> getEbookRequests(
            PublisherToken publisherToken, Pageable pageable
    ) {
        PublisherProfile publisher = entityFinder.findPublisher(publisherToken.publisherId());
        
        if (publisher.isNotAdmin()) {
            return PageResponse.of(
                    ebookRequestRepository.findByPublisher(publisher, pageable)
                            .map(ebookRequestMapper::toFetchResponse)
            );
        }

        return PageResponse.of(
                ebookRequestRepository.findByStatusIn(
                        List.of(
                                EbookRequestStatus.PENDING, 
                                EbookRequestStatus.REJECTED
                        ), pageable
                        ).map(ebookRequestMapper::toFetchResponse)
        );
    }

    private void assertAdmin(PublisherProfile publisher) {
        if (publisher.isNotAdmin()) {
            throw new ForbiddenException(ErrorCode.ONLY_ADMIN);
        }
    }

    private void assertRequestIsPending(EbookRequest request) {
        if (request.isNotPending()) {
            throw new BadRequestException(ErrorCode.EBOOK_REQUEST_NOT_PENDING);
        }
    }

    private void approveRequest(EbookRequest request) {
        Ebook savedEbook = ebookRepository.save(request.toEbook());
        request.approve(savedEbook);
    }
}