1+ package org .tuna .zoopzoop .backend .domain .datasource .controller ;
2+
3+ import io .swagger .v3 .oas .annotations .Operation ;
4+ import io .swagger .v3 .oas .annotations .tags .Tag ;
5+ import jakarta .validation .Valid ;
6+ import lombok .RequiredArgsConstructor ;
7+ import org .openapitools .jackson .nullable .JsonNullable ;
8+ import org .springframework .data .domain .Page ;
9+ import org .springframework .data .domain .Pageable ;
10+ import org .springframework .data .domain .Sort ;
11+ import org .springframework .data .web .PageableDefault ;
12+ import org .springframework .http .ResponseEntity ;
13+ import org .springframework .security .core .annotation .AuthenticationPrincipal ;
14+ import org .springframework .web .bind .annotation .*;
15+ import org .tuna .zoopzoop .backend .domain .datasource .dto .*;
16+ import org .tuna .zoopzoop .backend .domain .datasource .entity .Category ;
17+ import org .tuna .zoopzoop .backend .domain .datasource .service .DataSourceService ;
18+ import org .tuna .zoopzoop .backend .domain .datasource .service .PersonalDataSourceService ;
19+ import org .tuna .zoopzoop .backend .global .rsData .RsData ;
20+ import org .tuna .zoopzoop .backend .global .security .jwt .CustomUserDetails ;
21+
22+ import java .io .IOException ;
23+ import java .util .Map ;
24+
25+ @ RestController
26+ @ RequestMapping ("/api/v1/archive" )
27+ @ RequiredArgsConstructor
28+ @ Tag (name = "ApiV1DataSource(Personal)" , description = "개인 아카이브 자료 API" )
29+ public class DataSourceController {
30+
31+ private final PersonalDataSourceService personalApp ;
32+
33+ // ===== 등록 (개인만) =====
34+ // DataSourceController
35+
36+ @ Operation (summary = "자료 등록" , description = "내 PersonalArchive 안에 자료를 등록합니다." )
37+ @ PostMapping ("" )
38+ public ResponseEntity <RsData <Map <String , Integer >>> createDataSource (
39+ @ Valid @ RequestBody reqBodyForCreateDataSource rq ,
40+ @ AuthenticationPrincipal CustomUserDetails user
41+ ) throws IOException {
42+ int id = personalApp .create (
43+ user .getMember ().getId (),
44+ rq .sourceUrl (),
45+ rq .folderId (),
46+ DataSourceService .CreateCmd .builder ().build ()
47+ );
48+ return ResponseEntity .ok (
49+ new RsData <>("200" , "새로운 자료가 등록됐습니다." , Map .of ("dataSourceId" , id ))
50+ );
51+ }
52+
53+
54+ // ===== 단건 삭제 =====
55+ @ Operation (summary = "자료 단건 삭제" , description = "내 PersonalArchive 안에 자료를 단건 삭제합니다." )
56+ @ DeleteMapping ("/{dataSourceId}" )
57+ public ResponseEntity <RsData <Map <String , Integer >>> delete (
58+ @ PathVariable Integer dataSourceId ,
59+ @ AuthenticationPrincipal CustomUserDetails user
60+ ) {
61+ int deletedId = personalApp .deleteOne (user .getMember ().getId (), dataSourceId );
62+ return ResponseEntity .ok (
63+ new RsData <>("200" , deletedId + "번 자료가 삭제됐습니다." , Map .of ("dataSourceId" , deletedId ))
64+ );
65+ }
66+
67+ // ===== 다건 삭제 =====
68+ @ Operation (summary = "자료 다건 삭제" , description = "내 PersonalArchive 안에 자료를 다건 삭제합니다." )
69+ @ PostMapping ("/delete" )
70+ public ResponseEntity <RsData <Void >> deleteMany (
71+ @ Valid @ RequestBody reqBodyForDeleteMany rq ,
72+ @ AuthenticationPrincipal CustomUserDetails user
73+ ) {
74+ personalApp .deleteMany (user .getMember ().getId (), rq .dataSourceId ());
75+ return ResponseEntity .ok (new RsData <>("200" , "복수개의 자료가 삭제됐습니다." , null ));
76+ }
77+
78+ // ===== 소프트 삭제/복원 =====
79+ @ Operation (summary = "자료 다건 임시 삭제" , description = "내 PersonalArchive 안에 자료들을 임시 삭제합니다." )
80+ @ PatchMapping ("/soft-delete" )
81+ public ResponseEntity <RsData <Void >> softDelete (@ RequestBody @ Valid IdsRequest rq ,
82+ @ AuthenticationPrincipal CustomUserDetails user ) {
83+ personalApp .softDelete (user .getMember ().getId (), rq .dataSourceId ());
84+ return ResponseEntity .ok (new RsData <>("200" , "자료들이 임시 삭제됐습니다." , null ));
85+ }
86+
87+ @ Operation (summary = "자료 다건 복원" , description = "내 PersonalArchive 안에 자료들을 복원합니다." )
88+ @ PatchMapping ("/restore" )
89+ public ResponseEntity <RsData <Void >> restore (@ RequestBody @ Valid IdsRequest rq ,
90+ @ AuthenticationPrincipal CustomUserDetails user ) {
91+ personalApp .restore (user .getMember ().getId (), rq .dataSourceId ());
92+ return ResponseEntity .ok (new RsData <>("200" , "자료들이 복구됐습니다." , null ));
93+ }
94+
95+ // ===== 이동 =====
96+ @ Operation (summary = "자료 단건 이동" , description = "내 PersonalArchive 안에 자료를 단건 이동합니다." )
97+ @ PatchMapping ("/{dataSourceId}/move" )
98+ public ResponseEntity <RsData <Map <String , Integer >>> moveDataSource (
99+ @ PathVariable Integer dataSourceId ,
100+ @ Valid @ RequestBody reqBodyForMoveDataSource rq ,
101+ @ AuthenticationPrincipal CustomUserDetails user
102+ ) {
103+ var result = personalApp .moveOne (user .getMember ().getId (), dataSourceId , rq .folderId ());
104+ String msg = result .dataSourceId () + "번 자료가 " + result .folderId () + "번 폴더로 이동했습니다." ;
105+ return ResponseEntity .ok (
106+ new RsData <>("200" , msg ,
107+ Map .of ("folderId" , result .folderId (), "dataSourceId" , result .dataSourceId ()))
108+ );
109+ }
110+
111+ @ Operation (summary = "자료 다건 이동" , description = "내 PersonalArchive 안에 자료들을 다건 이동합니다." )
112+ @ PatchMapping ("/move" )
113+ public ResponseEntity <RsData <Void >> moveMany (
114+ @ Valid @ RequestBody reqBodyForMoveMany rq ,
115+ @ AuthenticationPrincipal CustomUserDetails user
116+ ) {
117+ personalApp .moveMany (user .getMember ().getId (), rq .folderId (), rq .dataSourceId ());
118+ return ResponseEntity .ok (new RsData <>("200" , "복수 개의 자료를 이동했습니다." , null ));
119+ }
120+
121+ // ===== 수정 =====
122+ @ Operation (summary = "자료 수정" , description = "내 PersonalArchive 안에 자료를 수정합니다." )
123+ @ PatchMapping ("/{dataSourceId}" )
124+ public ResponseEntity <RsData <Map <String , Integer >>> updateDataSource (
125+ @ PathVariable Integer dataSourceId ,
126+ @ RequestBody reqBodyForUpdateDataSource body ,
127+ @ AuthenticationPrincipal CustomUserDetails user
128+ ) {
129+ boolean anyPresent =
130+ (body .title () != null && body .title ().isPresent ()) ||
131+ (body .summary () != null && body .summary ().isPresent ()) ||
132+ (body .sourceUrl () != null && body .sourceUrl ().isPresent ()) ||
133+ (body .imageUrl () != null && body .imageUrl ().isPresent ()) ||
134+ (body .source () != null && body .source ().isPresent ()) ||
135+ (body .tags () != null && body .tags ().isPresent ()) ||
136+ (body .category () != null && body .category ().isPresent ());
137+ if (!anyPresent ) throw new IllegalArgumentException ("변경할 값이 없습니다." );
138+
139+
140+ var catNullable = body .category ();
141+
142+ // category enum 변환 시도
143+ JsonNullable <Category > enumCat = null ;
144+ if (catNullable != null && catNullable .isPresent ()) {
145+ String raw = catNullable .get ();
146+ try {
147+ // 필요하면 대소문자 허용 로직 추가
148+ enumCat = JsonNullable .of (Category .valueOf (raw .toUpperCase ()));
149+ } catch (IllegalArgumentException ex ) {
150+ throw new IllegalArgumentException ("유효하지 않은 카테고리입니다: " + raw );
151+ }
152+ }
153+
154+ int updatedId = personalApp .update (
155+ user .getMember ().getId (),
156+ dataSourceId ,
157+ DataSourceService .UpdateCmd .builder ()
158+ .title (body .title ()).summary (body .summary ()).sourceUrl (body .sourceUrl ())
159+ .imageUrl (body .imageUrl ()).source (body .source ())
160+ .tags (body .tags ()).category (enumCat )
161+ .build ()
162+ );
163+
164+ return ResponseEntity .ok (
165+ new RsData <>("200" , updatedId + "번 자료가 수정됐습니다." , Map .of ("dataSourceId" , updatedId ))
166+ );
167+ }
168+
169+ // ===== 검색 =====
170+ @ Operation (summary = "자료 검색" , description = "내 PersonalArchive 안에 자료들을 검색합니다." )
171+ @ GetMapping ("" )
172+ public ResponseEntity <RsData <SearchResponse <DataSourceSearchItem >>> search (
173+ @ RequestParam (required = false ) String title ,
174+ @ RequestParam (required = false ) String summary ,
175+ @ RequestParam (required = false ) String category ,
176+ @ RequestParam (required = false ) String keyword ,
177+ @ RequestParam (required = false ) Integer folderId ,
178+ @ RequestParam (required = false ) String folderName ,
179+ @ RequestParam (required = false , defaultValue = "true" ) Boolean isActive ,
180+ @ PageableDefault (size = 8 , sort = "createdAt" , direction = Sort .Direction .DESC ) Pageable pageable ,
181+ @ AuthenticationPrincipal CustomUserDetails user
182+ ) {
183+ var cond = DataSourceSearchCondition .builder ()
184+ .title (title ).summary (summary ).category (category ).folderId (folderId )
185+ .folderName (folderName ).isActive (isActive ).keyword (keyword ).build ();
186+
187+ Page <DataSourceSearchItem > page = personalApp .search (user .getMember ().getId (), cond , pageable );
188+ String sorted = pageable .getSort ().toString ().replace (": " , "," );
189+
190+ var pageInfo = new PageInfo (
191+ page .getNumber (), page .getSize (), page .getTotalElements (), page .getTotalPages (),
192+ page .isFirst (), page .isLast (), sorted
193+ );
194+ var body = new SearchResponse <>(page .getContent (), pageInfo );
195+
196+ return ResponseEntity .ok (new RsData <>("200" , "복수개의 자료가 조회됐습니다." , body ));
197+ }
198+ }
0 commit comments