99import com .back .domain .artist .entity .ArtistDocument ;
1010import com .back .domain .artist .entity .DocumentType ;
1111import com .back .domain .artist .repository .ArtistApplicationRepository ;
12+ import com .back .domain .artist .repository .ArtistDocumentRepository ;
1213import com .back .domain .notification .entity .NotificationType ;
1314import com .back .domain .notification .service .NotificationService ;
1415import com .back .domain .user .entity .User ;
1516import com .back .domain .user .repository .UserRepository ;
1617import com .back .global .exception .ServiceException ;
18+ import com .back .global .s3 .FileType ;
1719import com .back .global .s3 .S3FileRequest ;
20+ import com .back .global .s3 .S3Service ;
21+ import com .back .global .s3 .UploadResultResponse ;
1822import lombok .RequiredArgsConstructor ;
1923import lombok .extern .slf4j .Slf4j ;
2024import org .springframework .stereotype .Service ;
2125import org .springframework .transaction .annotation .Transactional ;
26+ import org .springframework .web .multipart .MultipartFile ;
2227
2328import java .util .ArrayList ;
2429import java .util .List ;
2530import java .util .Map ;
31+ import java .util .stream .Collectors ;
2632
2733/**
2834 * 사용자용 작가 신청 서비스
3440public class ArtistApplicationService {
3541
3642 private final ArtistApplicationRepository artistApplicationRepository ;
43+ private final ArtistDocumentRepository artistDocumentRepository ;
3744 private final UserRepository userRepository ;
3845 private final NotificationService notificationService ;
46+ private final S3Service s3Service ;
3947
4048 /**
4149 * 작가 신청서 생성
4250 */
4351 @ Transactional
44- public Long createApplication (Long userId , ArtistApplicationRequest request ) {
52+ public Long createApplication (
53+ Long userId ,
54+ ArtistApplicationRequest request ,
55+ List <MultipartFile > documentFiles ) {
56+
4557 // 1. 사용자 조회
4658 User user = userRepository .findById (userId )
4759 .orElseThrow (() -> new ServiceException ("404" , "사용자를 찾을 수 없습니다." ));
4860
49- // 2. 중복 신청 검증 (심사 대기 중인 신청서가 있는지)
61+ // 2. 중복 신청 검증
5062 validateDuplicateApplication (userId );
5163
52- // 3. 필수 서류 검증
53- if (! request . hasRequiredDocuments () ) {
54- throw new ServiceException ("400" , "필수 서류가 누락되었습니다." + request . getMissingRequiredDocuments () );
64+ // 3. 필수 서류 파일 검증
65+ if (documentFiles == null || documentFiles . size () < 2 ) {
66+ throw new ServiceException ("400" , "필수 서류 파일이 누락되었습니다. (최소 2개: 사업자등록증, 통신판매업신고증)" );
5567 }
5668
5769 // 4. 작가 신청서 엔티티 생성 및 저장
5870 ArtistApplication savedApplication = createAndSaveApplication (user , request );
5971
60- // 5. 서류 정보 저장 (ArtistDocument 엔티티 생성)
61- List <ArtistDocument > documents = createDocuments (savedApplication , request .documents ());
62- savedApplication .getDocuments ().addAll (documents );
72+ // 5. S3에 서류 파일 업로드
73+ List <UploadResultResponse > uploadResults = s3Service .uploadFiles (
74+ documentFiles ,
75+ "artist-documents/" + userId , // 경로: artist-documents/{userId}/
76+ documentFiles .stream ()
77+ .map (file -> FileType .DOCUMENT ) // 서류 타입
78+ .collect (Collectors .toList ())
79+ );
80+
81+ // 6. 업로드된 파일 정보로 ArtistDocument 엔티티 생성
82+ List <ArtistDocument > documents = new ArrayList <>();
83+ for (int i = 0 ; i < uploadResults .size (); i ++) {
84+ UploadResultResponse result = uploadResults .get (i );
85+ MultipartFile file = documentFiles .get (i );
86+
87+ // ✅ UploadResultResponse 구조에 맞게 수정
88+ ArtistDocument document = ArtistDocument .builder ()
89+ .artistApplication (savedApplication )
90+ .documentType (determineDocumentType (file .getOriginalFilename ()))
91+ .fileName (result .originalFileName ()) // ✅ originalFileName
92+ .fileUrl (result .url ()) // ✅ url
93+ .s3Key (result .s3Key ()) // ✅ s3Key
94+ .build ();
95+
96+ documents .add (document );
97+ }
98+
99+ // ✅ DB에 저장
100+ artistDocumentRepository .saveAll (documents );
101+
102+ // 7. 필수 서류 존재 여부 확인
103+ validateRequiredDocuments (documents );
63104
64- log .info ("작가 신청서 생성 완료: userId={}, applicationId={}" , userId , savedApplication .getId ());
105+ log .info ("작가 신청서 생성 완료: userId={}, applicationId={}, 업로드된 서류: {}" ,
106+ userId , savedApplication .getId (), documents .size ());
65107
66- // 6 . 알림 발송 - 모든 관리자에게 작가 인증 신청 알림
108+ // 8 . 알림 발송 - 모든 관리자에게
67109 List <User > admins = userRepository .findAllAdmins ();
68110 for (User admin : admins ) {
69111 notificationService .sendNotification (
70- admin ,
71- NotificationType .ARTIST_VERIFICATION_REQUEST ,
72- user .getName () + "님이 작가 인증을 신청했습니다." ,
73- "/admin/artist-applications/" + savedApplication .getId ()
112+ admin ,
113+ NotificationType .ARTIST_VERIFICATION_REQUEST ,
114+ user .getName () + "님이 작가 인증을 신청했습니다." ,
115+ "/admin/artist-applications/" + savedApplication .getId ()
74116 );
75117 }
76118
@@ -81,12 +123,12 @@ public Long createApplication(Long userId, ArtistApplicationRequest request) {
81123 * 내 신청서 목록 조회
82124 */
83125 public List <ArtistApplicationSimpleResponse > getMyApplications (Long userId ) {
84- // 사용자 존재 여부 확인
85126 if (!userRepository .existsById (userId )) {
86127 throw new ServiceException ("404" , "사용자를 찾을 수 없습니다." );
87128 }
88129
89- List <ArtistApplication > applications = artistApplicationRepository .findByUserIdOrderByCreateDateDesc (userId );
130+ List <ArtistApplication > applications =
131+ artistApplicationRepository .findByUserIdOrderByCreateDateDesc (userId );
90132
91133 return applications .stream ()
92134 .map (ArtistApplicationSimpleResponse ::from )
@@ -100,7 +142,6 @@ public ArtistApplicationResponse getApplicationById(Long userId, Long applicatio
100142 ArtistApplication application = artistApplicationRepository .findById (applicationId )
101143 .orElseThrow (() -> new ServiceException ("404" , "신청서를 찾을 수 없습니다." ));
102144
103- // 본인 신청서인지 확인
104145 if (!application .getUser ().getId ().equals (userId )) {
105146 throw new ServiceException ("403" , "본인의 신청서만 조회할 수 있습니다." );
106147 }
@@ -109,17 +150,12 @@ public ArtistApplicationResponse getApplicationById(Long userId, Long applicatio
109150 }
110151
111152 /**
112- * 작가 신청 취소 (상태 변경)
153+ * 작가 신청 취소
113154 */
114155 @ Transactional
115156 public void cancelApplication (Long userId , Long applicationId ) {
116- // 1. 신청서 조회
117157 ArtistApplication application = getApplicationIfOwner (userId , applicationId );
118-
119- // 2. 취소 가능한 상태인지 확인
120158 validateCancellable (application );
121-
122- // 3. 신청서 상태를 취소로 변경
123159 application .cancel ();
124160
125161 log .info ("작가 신청 취소 완료: userId={}, applicationId={}" , userId , applicationId );
@@ -233,4 +269,46 @@ public ArtistBusinessInfoResponse getBusinessInfo(Long userId) {
233269 application .getTelecomSalesNumber () // 통신판매업 신고 번호
234270 );
235271 }
272+
273+ /**
274+ * 파일명으로 서류 타입 판단
275+ */
276+ private DocumentType determineDocumentType (String filename ) {
277+ String lowerName = filename .toLowerCase ();
278+
279+ if (lowerName .contains ("business" ) || lowerName .contains ("사업자" )) {
280+ return DocumentType .BUSINESS_LICENSE ;
281+ } else if (lowerName .contains ("telecom" ) || lowerName .contains ("통신판매" ) || lowerName .contains ("신고증" )) {
282+ return DocumentType .TELECOM_CERTIFICATION ;
283+ } else if (lowerName .contains ("portfolio" ) || lowerName .contains ("포트폴리오" )) {
284+ return DocumentType .PORTFOLIO ;
285+ }
286+
287+ return DocumentType .OTHER ;
288+ }
289+
290+ /**
291+ * 필수 서류 검증 (사업자등록증, 통신판매업신고증)
292+ */
293+ private void validateRequiredDocuments (List <ArtistDocument > documents ) {
294+ boolean hasBusinessLicense = documents .stream ()
295+ .anyMatch (doc -> doc .getDocumentType () == DocumentType .BUSINESS_LICENSE );
296+
297+ boolean hasTelecomCertification = documents .stream ()
298+ .anyMatch (doc -> doc .getDocumentType () == DocumentType .TELECOM_CERTIFICATION );
299+
300+ List <String > missingDocs = new ArrayList <>();
301+ if (!hasBusinessLicense ) {
302+ missingDocs .add ("사업자등록증" );
303+ }
304+ if (!hasTelecomCertification ) {
305+ missingDocs .add ("통신판매업신고증" );
306+ }
307+
308+ if (!missingDocs .isEmpty ()) {
309+ throw new ServiceException ("400" ,
310+ "필수 서류가 누락되었습니다: " + String .join (", " , missingDocs ) +
311+ ". 파일명에 '사업자' 또는 '통신판매'를 포함해주세요." );
312+ }
313+ }
236314}
0 commit comments