Some improvements:

* Switches to PostgreSQL
* Added Minio storage
* Added attachments to properties
* Introduced DTOs for improved security
This commit is contained in:
2025-09-03 15:40:26 +02:00
parent 9735f1f398
commit 5eb6b6e738
26 changed files with 879 additions and 67 deletions

View File

@@ -0,0 +1,36 @@
package de.iwomm.propify_api.controller;
import de.iwomm.propify_api.service.AttachmentService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1/attachments")
public class AttachmentController {
private final AttachmentService attachmentService;
public AttachmentController(AttachmentService attachmentService) {
this.attachmentService = attachmentService;
}
@DeleteMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> delete(@PathVariable UUID id) {
try {
this.attachmentService.deleteById(id);
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
return ResponseEntity
.noContent()
.build();
}
}

View File

@@ -1,58 +1,185 @@
package de.iwomm.propify_api.controller;
import de.iwomm.propify_api.dto.BulkDeletePropertyIdsDTO;
import de.iwomm.propify_api.dto.PropertyDTO;
import de.iwomm.propify_api.entity.Attachment;
import de.iwomm.propify_api.entity.Property;
import de.iwomm.propify_api.s3.S3Service;
import de.iwomm.propify_api.s3.UploadResponse;
import de.iwomm.propify_api.service.AttachmentService;
import de.iwomm.propify_api.service.PropertyService;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.io.IOException;
import java.net.URI;
import java.util.*;
@RestController
@RequestMapping("/api/v1/properties")
public class PropertyController {
private final PropertyService propertyService;
private final AttachmentService attachmentService;
private final S3Service s3service;
public PropertyController(PropertyService propertyService) {
public PropertyController(PropertyService propertyService, AttachmentService attachmentService, S3Service s3Service) {
this.propertyService = propertyService;
}
@GetMapping("/info")
@PreAuthorize("isAuthenticated()")
public String infoEndpoint() {
return "Hello, you are authenticated!";
this.attachmentService = attachmentService;
this.s3service = s3Service;
}
@GetMapping
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')")
public List<Property> getAllProperties() {
return propertyService.findAll();
public ResponseEntity<?> getAllProperties() {
List<PropertyDTO> propertiesDTO = propertyService.toDTOs(propertyService.findAll());
return ResponseEntity
.ok(propertiesDTO);
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
public Property getPropertyById(@PathVariable UUID id) {
return propertyService.findById(id).orElse(null);
public ResponseEntity<?> getPropertyById(@PathVariable UUID id) {
try {
return ResponseEntity
.ok(propertyService.findById(id).orElseThrow(EntityNotFoundException::new));
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@PostMapping("/{id}/upload")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> upload(@RequestParam("attachments") List<MultipartFile> files, @PathVariable UUID id) {
List<String> messages = new ArrayList<>();
if (files.isEmpty()) {
return ResponseEntity
.ok()
.body(new UploadResponse(HttpStatus.BAD_REQUEST, "No attachments provided."));
}
Property property;
try {
property = propertyService.findById(id).orElseThrow(EntityNotFoundException::new);
} catch (EntityNotFoundException e) {
return ResponseEntity
.unprocessableEntity()
.build();
}
files.forEach(file -> {
String originalFileName = file.getOriginalFilename();
String uniqueFileName = id + "/" + UUID.randomUUID() + "_" + originalFileName;
try {
s3service.putObject("skamp", uniqueFileName, file);
Attachment newAttachment = new Attachment();
newAttachment.setProperty(property);
newAttachment.setStoragePath(uniqueFileName);
newAttachment.setFileName(originalFileName);
this.attachmentService.save(newAttachment);
property.getAttachments().add(newAttachment);
messages.add("Successfully uploaded " + originalFileName + " to " + uniqueFileName);
} catch (IOException e) {
messages.add("Failed uploading " + originalFileName);
}
});
propertyService.save(property);
return ResponseEntity.ok(new UploadResponse(HttpStatus.OK, messages));
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@PreAuthorize("hasRole('ADMIN')")
public Property createProperty(@RequestBody Property property) {
return propertyService.save(property);
public ResponseEntity<?> createProperty(@RequestBody PropertyDTO propertyDTO) {
try {
Property newItem = propertyService.saveDTO(propertyDTO);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(newItem.getId())
.toUri();
return ResponseEntity
.created(location)
.body(newItem);
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@PatchMapping("/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> updateProperty(@PathVariable UUID id, @RequestBody PropertyDTO propertyDTO) {
try {
return ResponseEntity
.ok(propertyService.update(id, propertyDTO));
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorize("hasRole('ADMIN')")
public void deleteProperty(@PathVariable UUID id) {
Optional<Property> property = propertyService.findById(id);
if (property.isEmpty()) {
return;
}
public ResponseEntity<?> deleteProperty(@PathVariable UUID id) {
try {
propertyService.deleteById(id);
propertyService.delete(property.get());
return ResponseEntity
.noContent()
.build();
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@PostMapping("/bulk-delete")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> deleteProperties(@RequestBody BulkDeletePropertyIdsDTO propertiesDTO) {
try {
propertyService.deleteByIds(propertiesDTO);
return ResponseEntity
.noContent()
.build();
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
}