diff --git a/pom.xml b/pom.xml
index 8456b68..037287b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -90,11 +90,17 @@
- com.stripe
- stripe-java
- 25.0.0
+ com.stripe
+ stripe-java
+ 25.0.0
+
+
+ software.amazon.awssdk
+ s3
+ 2.20.26
+
diff --git a/src/main/java/fr/codecake/nbat/booking/application/dto/BookedListingDTO.java b/src/main/java/fr/codecake/nbat/booking/application/dto/BookedListingDTO.java
index c858393..cfadd27 100644
--- a/src/main/java/fr/codecake/nbat/booking/application/dto/BookedListingDTO.java
+++ b/src/main/java/fr/codecake/nbat/booking/application/dto/BookedListingDTO.java
@@ -1,6 +1,5 @@
package fr.codecake.nbat.booking.application.dto;
-import fr.codecake.nbat.listing.application.dto.sub.PictureDTO;
import fr.codecake.nbat.listing.application.dto.vo.PriceVO;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
@@ -8,10 +7,10 @@
import java.util.UUID;
-public record BookedListingDTO(@Valid PictureDTO cover,
- @NotEmpty String location,
- @Valid BookedDateDTO dates,
- @Valid PriceVO totalPrice,
- @NotNull UUID bookingPublicId,
- @NotNull UUID listingPublicId) {
+public record BookedListingDTO(@Valid String cover,
+ @NotEmpty String location,
+ @Valid BookedDateDTO dates,
+ @Valid PriceVO totalPrice,
+ @NotNull UUID bookingPublicId,
+ @NotNull UUID listingPublicId) {
}
diff --git a/src/main/java/fr/codecake/nbat/infrastructure/config/S3Configuration.java b/src/main/java/fr/codecake/nbat/infrastructure/config/S3Configuration.java
new file mode 100644
index 0000000..5bd1d42
--- /dev/null
+++ b/src/main/java/fr/codecake/nbat/infrastructure/config/S3Configuration.java
@@ -0,0 +1,34 @@
+package fr.codecake.nbat.infrastructure.config;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+
+@Configuration
+public class S3Configuration {
+
+ @Value("${aws.credentials.access-key}")
+ private String accessKey;
+
+ @Value("${aws.credentials.secret-key}")
+ private String secretKey;
+
+ @Value("${aws.s3.region}")
+ private String region;
+
+ @Bean
+ public S3Client S3Client() {
+ AwsBasicCredentials awsBasicCredentials = AwsBasicCredentials.create(accessKey, secretKey);
+
+ return S3Client.builder()
+ .region(Region.of(region))
+ .credentialsProvider(StaticCredentialsProvider.create(awsBasicCredentials))
+ .build();
+ }
+
+}
diff --git a/src/main/java/fr/codecake/nbat/listing/application/LandlordService.java b/src/main/java/fr/codecake/nbat/listing/application/LandlordService.java
index a95c9d7..0ec2200 100644
--- a/src/main/java/fr/codecake/nbat/listing/application/LandlordService.java
+++ b/src/main/java/fr/codecake/nbat/listing/application/LandlordService.java
@@ -54,7 +54,7 @@ public CreatedListingDTO create(SaveListingDTO saveListingDTO) {
@Transactional(readOnly = true)
public List getAllProperties(ReadUserDTO landlord) {
- List properties = listingRepository.findAllByLandlordPublicIdFetchCoverPicture(landlord.publicId());
+ List properties = listingRepository.findAllByLandlordPublicId(landlord.publicId());
return listingMapper.listingToDisplayCardListingDTOs(properties);
}
diff --git a/src/main/java/fr/codecake/nbat/listing/application/PictureService.java b/src/main/java/fr/codecake/nbat/listing/application/PictureService.java
index 5626a44..f0686e6 100644
--- a/src/main/java/fr/codecake/nbat/listing/application/PictureService.java
+++ b/src/main/java/fr/codecake/nbat/listing/application/PictureService.java
@@ -1,42 +1,74 @@
package fr.codecake.nbat.listing.application;
import fr.codecake.nbat.listing.application.dto.sub.PictureDTO;
+import fr.codecake.nbat.listing.application.dto.sub.PictureUploadDTO;
import fr.codecake.nbat.listing.domain.Listing;
import fr.codecake.nbat.listing.domain.ListingPicture;
import fr.codecake.nbat.listing.mapper.ListingPictureMapper;
import fr.codecake.nbat.listing.repository.ListingPictureRepository;
import org.springframework.stereotype.Service;
+import java.util.ArrayList;
import java.util.List;
-import java.util.Set;
@Service
public class PictureService {
private final ListingPictureRepository listingPictureRepository;
-
private final ListingPictureMapper listingPictureMapper;
+ // NEW: Inject the S3Service so we can upload files
+ private final S3Service s3Service;
- public PictureService(ListingPictureRepository listingPictureRepository, ListingPictureMapper listingPictureMapper) {
+ // CHANGED: Constructor now includes S3Service parameter
+ // Spring will automatically inject the S3Service bean
+ public PictureService(ListingPictureRepository listingPictureRepository,
+ ListingPictureMapper listingPictureMapper,
+ S3Service s3Service) {
this.listingPictureRepository = listingPictureRepository;
this.listingPictureMapper = listingPictureMapper;
+ this.s3Service = s3Service;
}
- public List saveAll(List pictures, Listing listing) {
+ // CHANGE: accept PictureUploadDTO (has bytes) instead of PictureDTO
+ public List saveAll(List pictures, Listing listing) {
if (pictures == null || pictures.isEmpty()) {
return List.of();
}
- Set listingPictures = listingPictureMapper.pictureDTOsToListingPictures(pictures);
+ List listingPictures = new ArrayList<>();
+ String firstCoverUrl = null; // we'll use this for Change 4
boolean isFirst = true;
- for (ListingPicture listingPicture : listingPictures) {
+ for (PictureUploadDTO pictureDTO : pictures) {
+ ListingPicture listingPicture = new ListingPicture();
+ listingPicture.setFileContentType(pictureDTO.fileContentType());
listingPicture.setCover(isFirst);
listingPicture.setListing(listing);
+
+ if (pictureDTO.file() != null) {
+ // NOW this compiles correctly because PictureUploadDTO has .file()
+ String s3Url = s3Service.uploadFile(
+ pictureDTO.file(),
+ pictureDTO.fileContentType(),
+ "listing-image");
+ listingPicture.setFileUrl(s3Url);
+
+ // Capture the cover URL to store directly on Listing (see Change 4)
+ if (isFirst) {
+ firstCoverUrl = s3Url;
+ }
+ }
+
+ listingPictures.add(listingPicture);
isFirst = false;
}
+ // Store the cover URL directly on the Listing entity (Change 4)
+ if (firstCoverUrl != null) {
+ listing.setCoverUrl(firstCoverUrl);
+ }
+
listingPictureRepository.saveAll(listingPictures);
- return listingPictureMapper.listingPictureToPictureDTO(listingPictures.stream().toList());
+ return listingPictureMapper.listingPictureToPictureDTO(listingPictures);
}
-}
+}
\ No newline at end of file
diff --git a/src/main/java/fr/codecake/nbat/listing/application/S3Service.java b/src/main/java/fr/codecake/nbat/listing/application/S3Service.java
new file mode 100644
index 0000000..0ea3f94
--- /dev/null
+++ b/src/main/java/fr/codecake/nbat/listing/application/S3Service.java
@@ -0,0 +1,69 @@
+package fr.codecake.nbat.listing.application;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+import software.amazon.awssdk.services.s3.model.ObjectCannedACL;
+
+import java.util.UUID;
+
+@Service
+public class S3Service {
+
+ private final S3Client s3Client;
+
+ @Value("${aws.s3.bucket-name}")
+ private String bucketName;
+
+ @Value("${aws.s3.region}")
+ private String region;
+
+ public S3Service(S3Client s3Client) {
+ this.s3Client = s3Client;
+ }
+
+ /**
+ * Uploads a file to S3 and returns the public URL where the file can be
+ * accessed.
+ *
+ * @param fileBytes The actual file data as a byte array
+ * @param contentType The MIME type of the file (e.g., "image/jpeg",
+ * "image/png")
+ * @param originalFileName The original name of the file (used to determine file
+ * extension)
+ * @return The public URL where the uploaded file can be accessed
+ */
+ public String uploadFile(byte[] fileBytes, String contentType, String originalFileName) {
+ // Generate a unique filename to avoid collisions
+ // We use UUID to ensure each file has a unique name
+ String fileExtension = getFileExtension(originalFileName);
+ String uniqueFileName = "listings/" + UUID.randomUUID().toString() + fileExtension;
+
+ // Build the S3 upload request
+ PutObjectRequest putObjectRequest = PutObjectRequest.builder()
+ .bucket(bucketName)
+ .key(uniqueFileName)
+ .contentType(contentType)
+ .build();
+
+ // Actually upload the file to S3
+ s3Client.putObject(putObjectRequest, RequestBody.fromBytes(fileBytes));
+
+ // Construct and return the public URL
+ return String.format("https://%s.s3.%s.amazonaws.com/%s",
+ bucketName, region, uniqueFileName);
+ }
+
+ /**
+ * Extracts the file extension from a filename.
+ * For example: "house.jpg" returns ".jpg"
+ */
+ private String getFileExtension(String fileName) {
+ if (fileName == null || !fileName.contains(".")) {
+ return "";
+ }
+ return fileName.substring(fileName.lastIndexOf("."));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/fr/codecake/nbat/listing/application/TenantService.java b/src/main/java/fr/codecake/nbat/listing/application/TenantService.java
index 646947a..99a0fff 100644
--- a/src/main/java/fr/codecake/nbat/listing/application/TenantService.java
+++ b/src/main/java/fr/codecake/nbat/listing/application/TenantService.java
@@ -32,8 +32,8 @@ public class TenantService {
private final UserService userService;
private final BookingService bookingService;
-
- public TenantService(ListingRepository listingRepository, ListingMapper listingMapper, UserService userService, BookingService bookingService) {
+ public TenantService(ListingRepository listingRepository, ListingMapper listingMapper, UserService userService,
+ BookingService bookingService) {
this.listingRepository = listingRepository;
this.listingMapper = listingMapper;
this.userService = userService;
@@ -43,9 +43,9 @@ public TenantService(ListingRepository listingRepository, ListingMapper listingM
public Page getAllByCategory(Pageable pageable, BookingCategory category) {
Page allOrBookingCategory;
if (category == BookingCategory.ALL) {
- allOrBookingCategory = listingRepository.findAllWithCoverOnly(pageable);
+ allOrBookingCategory = listingRepository.findAll(pageable);
} else {
- allOrBookingCategory = listingRepository.findAllByBookingCategoryWithCoverOnly(pageable, category);
+ allOrBookingCategory = listingRepository.findAllByBookingCategory(pageable, category);
}
return allOrBookingCategory.map(listingMapper::listingToDisplayCardListingDTO);
@@ -62,19 +62,20 @@ public State getOne(UUID publicId) {
DisplayListingDTO displayListingDTO = listingMapper.listingToDisplayListingDTO(listingByPublicIdOpt.get());
- ReadUserDTO readUserDTO = userService.getByPublicId(listingByPublicIdOpt.get().getLandlordPublicId()).orElseThrow();
+ ReadUserDTO readUserDTO = userService.getByPublicId(listingByPublicIdOpt.get().getLandlordPublicId())
+ .orElseThrow();
LandlordListingDTO landlordListingDTO = new LandlordListingDTO(readUserDTO.firstName(), readUserDTO.imageUrl());
displayListingDTO.setLandlord(landlordListingDTO);
return State.builder().forSuccess(displayListingDTO);
}
-
@SuppressWarnings("null")
@Transactional(readOnly = true)
public Page search(Pageable pageable, SearchDTO newSearch) {
- Page allMatchedListings = listingRepository.findAllByLocationAndBathroomsAndBedroomsAndGuestsAndBeds(pageable, newSearch.location(),
+ Page allMatchedListings = listingRepository.findAllByLocationAndBathroomsAndBedroomsAndGuestsAndBeds(
+ pageable, newSearch.location(),
newSearch.infos().baths().value(),
newSearch.infos().bedrooms().value(),
newSearch.infos().guests().value(),
@@ -82,9 +83,11 @@ public Page search(Pageable pageable, SearchDTO newSearch
List listingUUIDs = allMatchedListings.stream().map(Listing::getPublicId).toList();
- List bookingUUIDs = bookingService.getBookingMatchByListingIdsAndBookedDate(listingUUIDs, newSearch.dates());
+ List bookingUUIDs = bookingService.getBookingMatchByListingIdsAndBookedDate(listingUUIDs,
+ newSearch.dates());
- List listingsNotBooked = allMatchedListings.stream().filter(listing -> !bookingUUIDs.contains(listing.getPublicId()))
+ List listingsNotBooked = allMatchedListings.stream()
+ .filter(listing -> !bookingUUIDs.contains(listing.getPublicId()))
.map(listingMapper::listingToDisplayCardListingDTO)
.toList();
diff --git a/src/main/java/fr/codecake/nbat/listing/application/dto/DisplayCardListingDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/DisplayCardListingDTO.java
index d00c974..f329f67 100644
--- a/src/main/java/fr/codecake/nbat/listing/application/dto/DisplayCardListingDTO.java
+++ b/src/main/java/fr/codecake/nbat/listing/application/dto/DisplayCardListingDTO.java
@@ -1,14 +1,13 @@
package fr.codecake.nbat.listing.application.dto;
-import fr.codecake.nbat.listing.application.dto.sub.PictureDTO;
import fr.codecake.nbat.listing.application.dto.vo.PriceVO;
import fr.codecake.nbat.listing.domain.BookingCategory;
import java.util.UUID;
public record DisplayCardListingDTO(PriceVO price,
- String location,
- PictureDTO cover,
- BookingCategory bookingCategory,
- UUID publicId) {
+ String location,
+ String cover,
+ BookingCategory bookingCategory,
+ UUID publicId) {
}
diff --git a/src/main/java/fr/codecake/nbat/listing/application/dto/SaveListingDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/SaveListingDTO.java
index b43e0ee..215b773 100644
--- a/src/main/java/fr/codecake/nbat/listing/application/dto/SaveListingDTO.java
+++ b/src/main/java/fr/codecake/nbat/listing/application/dto/SaveListingDTO.java
@@ -2,7 +2,7 @@
import fr.codecake.nbat.listing.application.dto.sub.DescriptionDTO;
import fr.codecake.nbat.listing.application.dto.sub.ListingInfoDTO;
-import fr.codecake.nbat.listing.application.dto.sub.PictureDTO;
+import fr.codecake.nbat.listing.application.dto.sub.PictureUploadDTO;
import fr.codecake.nbat.listing.application.dto.vo.PriceVO;
import fr.codecake.nbat.listing.domain.BookingCategory;
import jakarta.validation.Valid;
@@ -15,19 +15,23 @@ public class SaveListingDTO {
@NotNull
BookingCategory category;
- @NotNull String location;
+ @NotNull
+ String location;
- @NotNull @Valid
+ @NotNull
+ @Valid
ListingInfoDTO infos;
- @NotNull @Valid
+ @NotNull
+ @Valid
DescriptionDTO description;
- @NotNull @Valid
+ @NotNull
+ @Valid
PriceVO price;
@NotNull
- List pictures;
+ List pictures;
public BookingCategory getCategory() {
return category;
@@ -69,11 +73,11 @@ public void setPrice(PriceVO price) {
this.price = price;
}
- public List getPictures() {
+ public List getPictures() {
return pictures;
}
- public void setPictures(List pictures) {
+ public void setPictures(List pictures) {
this.pictures = pictures;
}
}
diff --git a/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureDTO.java
index 0946b69..73c441f 100644
--- a/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureDTO.java
+++ b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureDTO.java
@@ -1,18 +1,17 @@
package fr.codecake.nbat.listing.application.dto.sub;
-import jakarta.validation.constraints.NotNull;
-
import java.util.Objects;
-public record PictureDTO(
- @NotNull byte[] file,
- @NotNull String fileContentType,
- @NotNull boolean isCover
-) {
+// It carries the S3 URL so Angular can display the image.
+public record PictureDTO(String url,
+ String fileContentType,
+ boolean isCover) {
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
PictureDTO that = (PictureDTO) o;
return isCover == that.isCover && Objects.equals(fileContentType, that.fileContentType);
}
diff --git a/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureUploadDTO.java b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureUploadDTO.java
new file mode 100644
index 0000000..94c033b
--- /dev/null
+++ b/src/main/java/fr/codecake/nbat/listing/application/dto/sub/PictureUploadDTO.java
@@ -0,0 +1,9 @@
+package fr.codecake.nbat.listing.application.dto.sub;
+
+// This DTO travels INWARD — from the frontend to your service layer.
+// It carries the raw bytes that need to be uploaded to S3.
+public record PictureUploadDTO(byte[] file, // the actual image bytes
+ String fileContentType, // e.g. "image/jpeg"
+ boolean isCover) {
+
+}
diff --git a/src/main/java/fr/codecake/nbat/listing/domain/Listing.java b/src/main/java/fr/codecake/nbat/listing/domain/Listing.java
index de616b4..de3b28f 100644
--- a/src/main/java/fr/codecake/nbat/listing/domain/Listing.java
+++ b/src/main/java/fr/codecake/nbat/listing/domain/Listing.java
@@ -54,6 +54,10 @@ public class Listing extends AbstractAuditingEntity {
@OneToMany(mappedBy = "listing", cascade = CascadeType.REMOVE)
private Set pictures = new HashSet<>();
+ // Add this field inside your Listing entity class
+ @Column(name = "cover_url", length = 500)
+ private String coverUrl;
+
@Override
public Long getId() {
return id;
@@ -159,17 +163,33 @@ public void setPictures(Set pictures) {
this.pictures = pictures;
}
+ // Add getter and setter
+ public String getCoverUrl() {
+ return coverUrl;
+ }
+
+ public void setCoverUrl(String coverUrl) {
+ this.coverUrl = coverUrl;
+ }
+
@Override
public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
Listing listing = (Listing) o;
- return guests == listing.guests && bedrooms == listing.bedrooms && beds == listing.beds && bathrooms == listing.bathrooms && price == listing.price && Objects.equals(title, listing.title) && Objects.equals(description, listing.description) && bookingCategory == listing.bookingCategory && Objects.equals(location, listing.location) && Objects.equals(landlordPublicId, listing.landlordPublicId);
+ return guests == listing.guests && bedrooms == listing.bedrooms && beds == listing.beds
+ && bathrooms == listing.bathrooms && price == listing.price && Objects.equals(title, listing.title)
+ && Objects.equals(description, listing.description) && bookingCategory == listing.bookingCategory
+ && Objects.equals(location, listing.location)
+ && Objects.equals(landlordPublicId, listing.landlordPublicId);
}
@Override
public int hashCode() {
- return Objects.hash(title, description, guests, bedrooms, beds, bathrooms, price, bookingCategory, location, landlordPublicId);
+ return Objects.hash(title, description, guests, bedrooms, beds, bathrooms, price, bookingCategory, location,
+ landlordPublicId);
}
@Override
diff --git a/src/main/java/fr/codecake/nbat/listing/domain/ListingPicture.java b/src/main/java/fr/codecake/nbat/listing/domain/ListingPicture.java
index 334d91b..5dfbffb 100644
--- a/src/main/java/fr/codecake/nbat/listing/domain/ListingPicture.java
+++ b/src/main/java/fr/codecake/nbat/listing/domain/ListingPicture.java
@@ -20,13 +20,9 @@ public class ListingPicture extends AbstractAuditingEntity {
@JoinColumn(name = "listing_fk", referencedColumnName = "id")
private Listing listing;
- /*
- * @Lob annotation is telling Hibernate
- * "use PostgreSQL's Large Object system for this field."
- */
- // @Lob
- @Column(name = "file", nullable = false)
- private byte[] file; // byte[]: an array of bites representing images, files, ect..
+ // store the url of the file instead of old bytes[]
+ @Column(name = "file", nullable = false, length = 500)
+ private String fileUrl;
@Column(name = "file_content_type")
private String fileContentType;
@@ -51,12 +47,12 @@ public void setListing(Listing listing) {
this.listing = listing;
}
- public byte[] getFile() {
- return file;
+ public String getFileUrl() {
+ return fileUrl;
}
- public void setFile(byte[] file) {
- this.file = file;
+ public void setFileUrl(String fileUrl) {
+ this.fileUrl = fileUrl;
}
public String getFileContentType() {
@@ -82,19 +78,20 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass())
return false;
ListingPicture that = (ListingPicture) o;
- return isCover == that.isCover && Objects.deepEquals(file, that.file)
+ return isCover == that.isCover
+ && Objects.equals(fileUrl, that.fileUrl)
&& Objects.equals(fileContentType, that.fileContentType);
}
@Override
public int hashCode() {
- return Objects.hash(Arrays.hashCode(file), fileContentType, isCover);
+ return Objects.hash(fileUrl, fileContentType, isCover);
}
@Override
public String toString() {
return "ListingPicture{" +
- "file=" + Arrays.toString(file) +
+ "fileUrl='" + fileUrl + '\'' +
", fileContentType='" + fileContentType + '\'' +
", isCover=" + isCover +
'}';
diff --git a/src/main/java/fr/codecake/nbat/listing/mapper/ListingMapper.java b/src/main/java/fr/codecake/nbat/listing/mapper/ListingMapper.java
index ce884c6..4764e45 100644
--- a/src/main/java/fr/codecake/nbat/listing/mapper/ListingMapper.java
+++ b/src/main/java/fr/codecake/nbat/listing/mapper/ListingMapper.java
@@ -8,7 +8,7 @@
import java.util.List;
-@Mapper(componentModel = "spring", uses = {ListingPictureMapper.class})
+@Mapper(componentModel = "spring", uses = { ListingPictureMapper.class })
public interface ListingMapper {
@Mapping(target = "landlordPublicId", ignore = true)
@@ -17,6 +17,7 @@ public interface ListingMapper {
@Mapping(target = "id", ignore = true)
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "pictures", ignore = true)
+ @Mapping(target = "coverUrl", ignore = true) // PictureService sets this after S3 upload
@Mapping(target = "title", source = "description.title.value")
@Mapping(target = "description", source = "description.description.value")
@Mapping(target = "bedrooms", source = "infos.bedrooms.value")
@@ -29,10 +30,11 @@ public interface ListingMapper {
CreatedListingDTO listingToCreatedListingDTO(Listing listing);
- @Mapping(target = "cover", source = "pictures")
+ // CHANGED: use coverUrl directly instead of scanning the pictures set
List listingToDisplayCardListingDTOs(List listings);
- @Mapping(target = "cover", source = "pictures", qualifiedByName = "extract-cover")
+ // CHANGED: was source = "pictures" + extract-cover, now just coverUrl
+ @Mapping(target = "cover", source = "coverUrl")
DisplayCardListingDTO listingToDisplayCardListingDTO(Listing listing);
default PriceVO mapPriceToPriceVO(int price) {
@@ -52,4 +54,4 @@ default PriceVO mapPriceToPriceVO(int price) {
@Mapping(target = "listingPublicId", source = "publicId")
ListingCreateBookingDTO mapListingToListingCreateBookingDTO(Listing listing);
-}
+}
\ No newline at end of file
diff --git a/src/main/java/fr/codecake/nbat/listing/mapper/ListingPictureMapper.java b/src/main/java/fr/codecake/nbat/listing/mapper/ListingPictureMapper.java
index d7de15f..b208d2c 100644
--- a/src/main/java/fr/codecake/nbat/listing/mapper/ListingPictureMapper.java
+++ b/src/main/java/fr/codecake/nbat/listing/mapper/ListingPictureMapper.java
@@ -1,6 +1,7 @@
package fr.codecake.nbat.listing.mapper;
import fr.codecake.nbat.listing.application.dto.sub.PictureDTO;
+import fr.codecake.nbat.listing.application.dto.sub.PictureUploadDTO;
import fr.codecake.nbat.listing.domain.ListingPicture;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@@ -12,24 +13,31 @@
@Mapper(componentModel = "spring")
public interface ListingPictureMapper {
-
- Set pictureDTOsToListingPictures(List pictureDTOs);
+ // CHANGE: input is now PictureUploadDTO (for the creation wizard)
+ Set pictureUploadDTOsToListingPictures(List pictureDTOs);
@Mapping(target = "id", ignore = true)
@Mapping(target = "listing", ignore = true)
@Mapping(target = "createdDate", ignore = true)
@Mapping(target = "lastModifiedDate", ignore = true)
@Mapping(target = "cover", source = "isCover")
- ListingPicture pictureDTOToListingPicture(PictureDTO pictureDTO);
+ @Mapping(target = "fileUrl", ignore = true) // still set manually in PictureService
+ ListingPicture pictureUploadDTOToListingPicture(PictureUploadDTO pictureDTO);
List listingPictureToPictureDTO(List listingPictures);
+ // CHANGE: map fileUrl from entity to fileUrl in display DTO
@Mapping(target = "isCover", source = "cover")
+ @Mapping(target = "url", source = "fileUrl")
PictureDTO convertToPictureDTO(ListingPicture listingPicture);
+ // This extract-cover works on the entity set and returns a display DTO
@Named("extract-cover")
default PictureDTO extractCover(Set pictures) {
- return pictures.stream().findFirst().map(this::convertToPictureDTO).orElseThrow();
+ return pictures.stream()
+ .filter(ListingPicture::isCover)
+ .findFirst()
+ .map(this::convertToPictureDTO)
+ .orElseThrow();
}
-
-}
+}
\ No newline at end of file
diff --git a/src/main/java/fr/codecake/nbat/listing/presentation/LandlordResource.java b/src/main/java/fr/codecake/nbat/listing/presentation/LandlordResource.java
index fa53acb..a0c5da4 100644
--- a/src/main/java/fr/codecake/nbat/listing/presentation/LandlordResource.java
+++ b/src/main/java/fr/codecake/nbat/listing/presentation/LandlordResource.java
@@ -1,12 +1,11 @@
package fr.codecake.nbat.listing.presentation;
import com.fasterxml.jackson.databind.ObjectMapper;
-import fr.codecake.nbat.infrastructure.config.SecurityUtils;
import fr.codecake.nbat.listing.application.LandlordService;
import fr.codecake.nbat.listing.application.dto.CreatedListingDTO;
import fr.codecake.nbat.listing.application.dto.DisplayCardListingDTO;
import fr.codecake.nbat.listing.application.dto.SaveListingDTO;
-import fr.codecake.nbat.listing.application.dto.sub.PictureDTO;
+import fr.codecake.nbat.listing.application.dto.sub.PictureUploadDTO;
import fr.codecake.nbat.sharedkernel.service.State;
import fr.codecake.nbat.sharedkernel.service.StatusNotification;
import fr.codecake.nbat.user.application.UserException;
@@ -55,8 +54,11 @@ public ResponseEntity create(
MultipartHttpServletRequest request,
// get the mulitipart/form-data labeled "dto" as string
@RequestPart(name = "dto") String saveListingDTOString) throws IOException {
- List files = request.getFiles("file");
- List pictures = files.stream().map(mapMultipartFileToPictureDTO()).toList();
+ List pictures = request.getFiles("file")
+ .stream()
+ .map(mapMultipartFileToPictureUploadDTO())
+ .toList();
+
SaveListingDTO saveListingDTO = objectMapper.readValue(saveListingDTOString, SaveListingDTO.class);
saveListingDTO.setPictures(pictures);
@@ -75,10 +77,10 @@ public ResponseEntity create(
}
}
- private static Function mapMultipartFileToPictureDTO() {
+ private static Function mapMultipartFileToPictureUploadDTO() {
return multipartFile -> {
try {
- return new PictureDTO(multipartFile.getBytes(), multipartFile.getContentType(), false);
+ return new PictureUploadDTO(multipartFile.getBytes(), multipartFile.getContentType(), false);
} catch (IOException ioe) {
throw new UserException(
String.format("Cannot parse multipart file: %s", multipartFile.getOriginalFilename()));
@@ -107,10 +109,9 @@ public ResponseEntity update(
MultipartHttpServletRequest request,
@RequestParam UUID publicId,
@RequestPart(name = "dto") String saveListingDTOString) throws IOException {
- List pictures = request.getFileMap()
- .values()
+ List pictures = request.getFiles("file")
.stream()
- .map(mapMultipartFileToPictureDTO())
+ .map(mapMultipartFileToPictureUploadDTO())
.toList();
SaveListingDTO saveListingDTO = objectMapper.readValue(saveListingDTOString, SaveListingDTO.class);
diff --git a/src/main/java/fr/codecake/nbat/listing/repository/ListingRepository.java b/src/main/java/fr/codecake/nbat/listing/repository/ListingRepository.java
index 4dbfc47..267e2f8 100644
--- a/src/main/java/fr/codecake/nbat/listing/repository/ListingRepository.java
+++ b/src/main/java/fr/codecake/nbat/listing/repository/ListingRepository.java
@@ -5,7 +5,6 @@
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
-import org.springframework.data.jpa.repository.Query;
import java.util.List;
import java.util.Optional;
@@ -13,27 +12,20 @@
public interface ListingRepository extends JpaRepository {
- @Query("SELECT listing FROM Listing listing LEFT JOIN FETCH listing.pictures picture" +
- " WHERE listing.landlordPublicId = :landlordPublicId AND picture.isCover = true")
- List findAllByLandlordPublicIdFetchCoverPicture(UUID landlordPublicId);
+ List findAllByLandlordPublicId(UUID landlordPublicId);
- long deleteByPublicIdAndLandlordPublicId(UUID publicId, UUID landlordPublicId);
+ long deleteByPublicIdAndLandlordPublicId(UUID publicId, UUID landlordPublicId);
- @Query("SELECT listing from Listing listing LEFT JOIN FETCH listing.pictures picture" +
- " WHERE picture.isCover = true AND listing.bookingCategory = :bookingCategory")
- Page findAllByBookingCategoryWithCoverOnly(Pageable pageable, BookingCategory bookingCategory);
+ Page findAllByBookingCategory(Pageable pageable, BookingCategory bookingCategory);
- @Query("SELECT listing from Listing listing LEFT JOIN FETCH listing.pictures picture" +
- " WHERE picture.isCover = true")
- Page findAllWithCoverOnly(Pageable pageable);
+ Page findAll(Pageable pageable);
- Optional findByPublicId(UUID publicId);
+ Optional findByPublicId(UUID publicId);
- List findAllByPublicIdIn(List allListingPublicIDs);
+ List findAllByPublicIdIn(List allListingPublicIDs);
- Optional findOneByPublicIdAndLandlordPublicId(UUID listingPublicId, UUID landlordPublicId);
+ Optional findOneByPublicIdAndLandlordPublicId(UUID listingPublicId, UUID landlordPublicId);
- Page findAllByLocationAndBathroomsAndBedroomsAndGuestsAndBeds(
- Pageable pageable, String location, int bathrooms, int bedrooms, int guests, int beds
- );
+ Page findAllByLocationAndBathroomsAndBedroomsAndGuestsAndBeds(
+ Pageable pageable, String location, int bathrooms, int bedrooms, int guests, int beds);
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 2e0c2b1..9e775dc 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -6,7 +6,7 @@ logging:
spring:
application:
- name: nbat-clone-back
+ name: nbat-back
docker:
compose:
@@ -16,7 +16,7 @@ spring:
datasource:
url: jdbc:postgresql://localhost:5432/nbat_db?currentSchema=nbat_schema
username: postgres
- password: 1234
+ password: "0000"
driver-class-name: org.postgresql.Driver
hikari: # ← ajoute ici
keepalive-time: 60000
@@ -55,4 +55,10 @@ stripe:
success-url: http://localhost:4200/payment-success
cancel-url: http://localhost:4200/payment-cancel
# ================================================
-
+aws:
+ s3:
+ bucket-name: npat-files-prod
+ region: eu-west-3
+ credentials:
+ access-key: "AKIAUGGODHXX736JYEST"
+ secret-key: "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"