66import org .devkor .apu .saerok_server .domain .collection .application .dto .CreateCollectionCommand ;
77import org .devkor .apu .saerok_server .domain .collection .application .dto .DeleteCollectionCommand ;
88import org .devkor .apu .saerok_server .domain .collection .application .dto .UpdateCollectionCommand ;
9+ import org .devkor .apu .saerok_server .domain .collection .core .entity .AccessLevelType ;
910import org .devkor .apu .saerok_server .domain .collection .core .entity .UserBirdCollection ;
1011import org .devkor .apu .saerok_server .domain .collection .core .repository .CollectionImageRepository ;
1112import org .devkor .apu .saerok_server .domain .collection .core .repository .CollectionRepository ;
1213import org .devkor .apu .saerok_server .domain .collection .core .util .PointFactory ;
1314import org .devkor .apu .saerok_server .domain .collection .mapper .CollectionWebMapper ;
1415import org .devkor .apu .saerok_server .domain .dex .bird .core .entity .Bird ;
1516import org .devkor .apu .saerok_server .domain .dex .bird .core .repository .BirdRepository ;
17+ import org .devkor .apu .saerok_server .domain .stat .application .BirdIdRequestHistoryRecorder ;
1618import org .devkor .apu .saerok_server .domain .user .core .entity .User ;
1719import org .devkor .apu .saerok_server .domain .user .core .repository .UserRepository ;
1820import org .devkor .apu .saerok_server .global .shared .exception .BadRequestException ;
1921import org .devkor .apu .saerok_server .global .shared .exception .ForbiddenException ;
2022import org .devkor .apu .saerok_server .global .shared .exception .NotFoundException ;
2123import org .devkor .apu .saerok_server .global .shared .infra .ImageDomainService ;
2224import org .devkor .apu .saerok_server .global .shared .infra .ImageService ;
23- import org .locationtech .jts .geom .Coordinate ;
24- import org .locationtech .jts .geom .GeometryFactory ;
2525import org .locationtech .jts .geom .Point ;
26- import org .locationtech .jts .geom .PrecisionModel ;
2726import org .springframework .stereotype .Service ;
2827
28+ import java .time .OffsetDateTime ;
2929import java .util .List ;
3030
3131import static org .devkor .apu .saerok_server .global .shared .util .TransactionUtils .runAfterCommitOrNow ;
@@ -42,29 +42,19 @@ public class CollectionCommandService {
4242 private final ImageDomainService imageDomainService ;
4343 private final CollectionWebMapper collectionWebMapper ;
4444 private final ImageService imageService ;
45+ private final BirdIdRequestHistoryRecorder birdReqHistory ;
4546
4647 public Long createCollection (CreateCollectionCommand command ) {
4748 User user = userRepository .findById (command .userId ()).orElseThrow (() -> new NotFoundException ("존재하지 않는 사용자 id예요" ));
4849
49- Bird bird ;
50+ Bird bird = (command .birdId () != null )
51+ ? birdRepository .findById (command .birdId ()).orElseThrow (() -> new NotFoundException ("존재하지 않는 조류 id예요" ))
52+ : null ;
5053
51- if (command .birdId () != null ) {
52- bird = birdRepository .findById (command .birdId ()).orElseThrow (() -> new NotFoundException ("존재하지 않는 조류 id예요" ));
53- } else {
54- bird = null ;
55- }
56-
57- if (command .discoveredDate () == null ) {
58- throw new BadRequestException ("관찰 날짜를 포함해주세요" );
59- }
60-
61- if (command .longitude () == null || command .latitude () == null ) {
62- throw new BadRequestException ("관찰 위치 정보를 포함해주세요" );
63- }
64-
65- if (command .note () != null && command .note ().length () > UserBirdCollection .NOTE_MAX_LENGTH ) {
54+ if (command .discoveredDate () == null ) throw new BadRequestException ("관찰 날짜를 포함해주세요" );
55+ if (command .longitude () == null || command .latitude () == null ) throw new BadRequestException ("관찰 위치 정보를 포함해주세요" );
56+ if (command .note () != null && command .note ().length () > UserBirdCollection .NOTE_MAX_LENGTH )
6657 throw new BadRequestException ("한 줄 평 길이는 " + UserBirdCollection .NOTE_MAX_LENGTH + "자 이하여야 해요" );
67- }
6858
6959 Point location = PointFactory .create (command .latitude (), command .longitude ());
7060
@@ -80,7 +70,12 @@ public Long createCollection(CreateCollectionCommand command) {
8070 .accessLevel (command .accessLevel ())
8171 .build ();
8272
83- return collectionRepository .save (collection );
73+ Long id = collectionRepository .save (collection );
74+
75+ // 생성 직후 bird가 비어 있고 PUBLIC이면 '대기 시작' 기록
76+ birdReqHistory .onCollectionCreatedIfPending (collection , collection .getCreatedAt ());
77+
78+ return id ;
8479 }
8580
8681 public void deleteCollection (DeleteCollectionCommand command ) {
@@ -90,11 +85,15 @@ public void deleteCollection(DeleteCollectionCommand command) {
9085 throw new ForbiddenException ("해당 컬렉션에 대한 권한이 없어요" );
9186 }
9287
93- List <String > objectKeys = collectionImageRepository .findObjectKeysByCollectionId (command .collectionId ());
88+ // 1) 열린 동정 요청 히스토리 사전 정리
89+ birdReqHistory .onCollectionDeleted (collection .getId ());
9490
91+ // 2) 연관 이미지 정리 후 컬렉션 삭제
92+ List <String > objectKeys = collectionImageRepository .findObjectKeysByCollectionId (command .collectionId ());
9593 collectionImageRepository .removeByCollectionId (command .collectionId ());
9694 collectionRepository .remove (collection );
9795 runAfterCommitOrNow (() -> imageService .deleteAll (objectKeys ));
96+ // 3) 닫힌 히스토리는 FK가 SET NULL로 남는다
9897 }
9998
10099 public UpdateCollectionResponse updateCollection (UpdateCollectionCommand command ) {
@@ -104,41 +103,51 @@ public UpdateCollectionResponse updateCollection(UpdateCollectionCommand command
104103 throw new ForbiddenException ("해당 컬렉션에 대한 권한이 없어요" );
105104 }
106105
107- System .out .println (command .isBirdIdUpdated ());
108- if (command .isBirdIdUpdated () != null && command .isBirdIdUpdated ()) {
109- if (command .birdId () != null ) {
110- Bird bird = birdRepository .findById (command .birdId ()).orElseThrow (() -> new NotFoundException ("존재하지 않는 조류 id예요" ));
111- collection .changeBird (bird );
112- } else {
113- collection .changeBird (null );
106+ OffsetDateTime now = OffsetDateTime .now ();
107+ // 변경 전 상태 스냅샷
108+ AccessLevelType oldLevel = collection .getAccessLevel ();
109+
110+ // 새 ID 변경
111+ if (Boolean .TRUE .equals (command .isBirdIdUpdated ())) {
112+ Bird before = collection .getBird ();
113+ Bird after = (command .birdId () != null )
114+ ? birdRepository .findById (command .birdId ()).orElseThrow (() -> new NotFoundException ("존재하지 않는 조류 id예요" ))
115+ : null ;
116+
117+ if (before == null && after != null ) {
118+ // null -> not null : EDIT로 해결 → 열린 기록 삭제
119+ birdReqHistory .onResolvedByEdit (collection );
120+ } else if (before != null && after == null ) {
121+ // not null -> null : PUBLIC이면 다시 대기 시작
122+ birdReqHistory .onBirdSetToUnknown (collection , now );
114123 }
124+
125+ collection .changeBird (after );
115126 }
116127
117128 if (command .discoveredDate () != null ) collection .setDiscoveredDate (command .discoveredDate ());
118129
119130 if ((command .latitude () == null ) ^ (command .longitude () == null )) {
120131 throw new BadRequestException ("위도와 경도 둘 중 하나만 수정할 수는 없어요" );
121132 } else if (command .latitude () != null ) {
122- GeometryFactory geometryFactory = new GeometryFactory (new PrecisionModel (), 4326 );
123- Coordinate coordinate = new Coordinate (command .longitude (), command .latitude ());
124- Point location = geometryFactory .createPoint (coordinate );
133+ var location = PointFactory .create (command .latitude (), command .longitude ());
125134 collection .setLocation (location );
126135 }
127136
128137 if (command .locationAlias () != null ) collection .setLocationAlias (command .locationAlias ());
129-
130138 if (command .address () != null ) collection .setAddress (command .address ());
131139
132140 if (command .note () != null ) {
133141 if (command .note ().length () > UserBirdCollection .NOTE_MAX_LENGTH ) {
134142 throw new BadRequestException ("한 줄 평 길이는 " + UserBirdCollection .NOTE_MAX_LENGTH + "자 이하여야 해요" );
135143 }
136-
137144 collection .setNote (command .note ());
138145 }
139146
147+ // 액세스 레벨 변경 처리 (전/후 비교)
140148 if (command .accessLevel () != null ) {
141149 collection .setAccessLevel (command .accessLevel ());
150+ birdReqHistory .onAccessLevelChanged (collection , oldLevel , now );
142151 }
143152
144153 String imageUrl = collectionImageRepository .findObjectKeysByCollectionId (command .collectionId ()).stream ()
0 commit comments