diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1470a6c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,10 @@
+# SKAMP Base API
+
+## Environment
+
+The default profile is `dev`.
+In production, set it to `prod` via environment variables:
+
+```bash
+java ...
+```
\ No newline at end of file
diff --git a/bruno/propify/Authenticate/Auth.bru b/bruno/propify/Authenticate/Auth.bru
index 2b1b156..c223dd4 100644
--- a/bruno/propify/Authenticate/Auth.bru
+++ b/bruno/propify/Authenticate/Auth.bru
@@ -5,7 +5,7 @@ meta {
}
post {
- url: {{KEYCLOAK_BASE_URL}}/realms/propify/protocol/openid-connect/token
+ url: {{KEYCLOAK_BASE_URL}}/realms/{{KEYCLOAK_REALM}}/protocol/openid-connect/token
body: formUrlEncoded
auth: inherit
}
diff --git a/bruno/propify/Properties/Create new.bru b/bruno/propify/Properties/Create new.bru
index a79b3d5..4309d4e 100644
--- a/bruno/propify/Properties/Create new.bru
+++ b/bruno/propify/Properties/Create new.bru
@@ -16,12 +16,12 @@ auth:bearer {
body:json {
{
- "name": "Mustername 1",
- "street": "Musterstraße",
- "houseNumber": "1",
- "zipCode": "55123",
- "city": "Musterstadt",
- "country": "de",
+ "name": "Bungalow",
+ "street": "Hebbelstraße",
+ "houseNumber": "30",
+ "zipCode": "55127",
+ "city": "Mainz",
+ "country": "DE",
"notes": "Lorem ipsum"
}
}
diff --git a/bruno/propify/Properties/Upload files.bru b/bruno/propify/Properties/Upload files.bru
new file mode 100644
index 0000000..439c459
--- /dev/null
+++ b/bruno/propify/Properties/Upload files.bru
@@ -0,0 +1,39 @@
+meta {
+ name: Upload files
+ type: http
+ seq: 5
+}
+
+post {
+ url: {{API_BASE_URL}}/api/{{API_VERSION}}/properties/f4eb0c54-7c8f-4e60-b71f-1d08b8b6e2d4/upload
+ body: multipartForm
+ auth: bearer
+}
+
+auth:bearer {
+ token: {{BEARER_TOKEN}}
+}
+
+body:json {
+ {
+ "name": "Bungalow",
+ "street": "Hebbelstraße",
+ "houseNumber": "30",
+ "zipCode": "55127",
+ "city": "Mainz",
+ "country": "DE",
+ "notes": "Lorem ipsum"
+ }
+}
+
+body:multipart-form {
+ attachments: @file(/Users/murat/Pictures/IMG_0229.jpeg|/Users/murat/Pictures/schnürsenkel technik.gif)
+}
+
+body:file {
+ file: @file(/Users/murat/Pictures/IMG_0229.jpeg) @contentType(image/jpeg)
+}
+
+settings {
+ encodeUrl: true
+}
diff --git a/bruno/propify/bruno.json b/bruno/propify/bruno.json
index 5066d2c..d425f89 100644
--- a/bruno/propify/bruno.json
+++ b/bruno/propify/bruno.json
@@ -1,6 +1,6 @@
{
"version": "1",
- "name": "propify",
+ "name": "skamp-api",
"type": "collection",
"ignore": [
"node_modules",
diff --git a/bruno/propify/environments/local.bru b/bruno/propify/environments/local.bru
index de3d934..917650f 100644
--- a/bruno/propify/environments/local.bru
+++ b/bruno/propify/environments/local.bru
@@ -1,11 +1,14 @@
vars {
API_BASE_URL: http://localhost:8080
API_VERSION: v1
- BEARER_TOKEN: -
KEYCLOAK_BASE_URL: http://localhost:8280
DEV_USERNAME: dev@example.com
DEV_PASSWORD: dev
ADMIN_USERNAME: admin@example.com
ADMIN_PASSWORD: admin
- KEYCLOAK_CLIENT_ID: propify-app
+ KEYCLOAK_CLIENT_ID: skamp-app
+ KEYCLOAK_REALM: skamp
}
+vars:secret [
+ BEARER_TOKEN
+]
diff --git a/pom.xml b/pom.xml
index d3cb5cf..2a89485 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,10 +9,10 @@
de.iwomm
- propify-api
+ skamp-api
0.0.1-SNAPSHOT
- propify-api
- Propify API
+ skamp-api
+ SKAMP API
@@ -84,6 +84,20 @@
spring-boot-starter-security
+
+ org.postgresql
+ postgresql
+ 42.6.0
+
+
+
+
+ software.amazon.awssdk
+ s3
+ 2.33.0
+ compile
+
+
diff --git a/src/main/java/de/iwomm/propify_api/PropifyApiApplication.java b/src/main/java/de/iwomm/propify_api/SkampApiApplication.java
similarity index 69%
rename from src/main/java/de/iwomm/propify_api/PropifyApiApplication.java
rename to src/main/java/de/iwomm/propify_api/SkampApiApplication.java
index fe197f3..48b9017 100644
--- a/src/main/java/de/iwomm/propify_api/PropifyApiApplication.java
+++ b/src/main/java/de/iwomm/propify_api/SkampApiApplication.java
@@ -4,10 +4,10 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
-public class PropifyApiApplication {
+public class SkampApiApplication {
public static void main(String[] args) {
- SpringApplication.run(PropifyApiApplication.class, args);
+ SpringApplication.run(SkampApiApplication.class, args);
}
}
diff --git a/src/main/java/de/iwomm/propify_api/controller/AttachmentController.java b/src/main/java/de/iwomm/propify_api/controller/AttachmentController.java
new file mode 100644
index 0000000..25bb253
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/controller/AttachmentController.java
@@ -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();
+ }
+}
diff --git a/src/main/java/de/iwomm/propify_api/controller/PropertyController.java b/src/main/java/de/iwomm/propify_api/controller/PropertyController.java
index 809bdfa..269751f 100644
--- a/src/main/java/de/iwomm/propify_api/controller/PropertyController.java
+++ b/src/main/java/de/iwomm/propify_api/controller/PropertyController.java
@@ -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 getAllProperties() {
- return propertyService.findAll();
+ public ResponseEntity> getAllProperties() {
+ List 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 files, @PathVariable UUID id) {
+ List 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 = 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();
+ }
}
}
diff --git a/src/main/java/de/iwomm/propify_api/database/DatabaseSeeder.java b/src/main/java/de/iwomm/propify_api/database/DatabaseSeeder.java
new file mode 100644
index 0000000..5c46d2b
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/database/DatabaseSeeder.java
@@ -0,0 +1,29 @@
+package de.iwomm.propify_api.database;
+
+import de.iwomm.propify_api.entity.Property;
+import de.iwomm.propify_api.repository.PropertyRepository;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Component;
+
+@Component
+@Profile("dev") // Runs only in "dev" environments
+public class DatabaseSeeder implements CommandLineRunner {
+ private final PropertyRepository propertyRepository;
+
+ public DatabaseSeeder(PropertyRepository propertyRepository) {
+ this.propertyRepository = propertyRepository;
+ }
+
+ @Override
+ public void run(String... args) throws Exception {
+ if (propertyRepository.count() == 0) {
+ propertyRepository.save(new Property("Mustergebäude 1", "Musterstraße", "1", "12345", "Musterstadt", "DE", "Musterbemerkung 1"));
+ propertyRepository.save(new Property("Mustergebäude 2", "Dagobertstraße", "2", "22345", "Entenhausen", "AT", "Musterbemerkung 2"));
+ propertyRepository.save(new Property("Mustergebäude 3", "Mustersteet", "3", "32345", "New York", "CH", "Musterbemerkung 3"));
+ System.out.println("Countries seeded.");
+ } else {
+ System.out.println("Countries already seeded.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/iwomm/propify_api/dto/AttachmentDTO.java b/src/main/java/de/iwomm/propify_api/dto/AttachmentDTO.java
new file mode 100644
index 0000000..c260b1b
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/dto/AttachmentDTO.java
@@ -0,0 +1,39 @@
+package de.iwomm.propify_api.dto;
+
+import java.util.UUID;
+
+public class AttachmentDTO {
+ private UUID id;
+ private String storagePath;
+ private String fileName;
+
+ public AttachmentDTO(UUID id, String storagePath, String fileName) {
+ this.id = id;
+ this.storagePath = storagePath;
+ this.fileName = fileName;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+ public String getStoragePath() {
+ return storagePath;
+ }
+
+ public void setStoragePath(String storagePath) {
+ this.storagePath = storagePath;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+}
diff --git a/src/main/java/de/iwomm/propify_api/dto/BulkDeletePropertyIdsDTO.java b/src/main/java/de/iwomm/propify_api/dto/BulkDeletePropertyIdsDTO.java
new file mode 100644
index 0000000..5dade0c
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/dto/BulkDeletePropertyIdsDTO.java
@@ -0,0 +1,17 @@
+package de.iwomm.propify_api.dto;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class BulkDeletePropertyIdsDTO {
+ List ids = new ArrayList<>();
+
+ public List getIds() {
+ return ids;
+ }
+
+ public void setIds(List ids) {
+ this.ids = ids;
+ }
+}
diff --git a/src/main/java/de/iwomm/propify_api/dto/PropertyDTO.java b/src/main/java/de/iwomm/propify_api/dto/PropertyDTO.java
new file mode 100644
index 0000000..d12d9fe
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/dto/PropertyDTO.java
@@ -0,0 +1,101 @@
+package de.iwomm.propify_api.dto;
+
+import java.util.List;
+import java.util.UUID;
+
+public class PropertyDTO {
+ private UUID id;
+ private String name;
+ private String street;
+ private String houseNumber;
+ private String zipCode;
+ private String city;
+ private String country;
+ private String notes;
+ private List attachments;
+
+
+ public PropertyDTO(UUID id, String name, String street, String houseNumber, String zipCode, String city, String country, String notes, List attachments) {
+ this.id = id;
+ this.name = name;
+ this.street = street;
+ this.houseNumber = houseNumber;
+ this.zipCode = zipCode;
+ this.city = city;
+ this.country = country;
+ this.notes = notes;
+ this.attachments = attachments;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getStreet() {
+ return street;
+ }
+
+ public void setStreet(String street) {
+ this.street = street;
+ }
+
+ public String getHouseNumber() {
+ return houseNumber;
+ }
+
+ public void setHouseNumber(String houseNumber) {
+ this.houseNumber = houseNumber;
+ }
+
+ public String getZipCode() {
+ return zipCode;
+ }
+
+ public void setZipCode(String zipCode) {
+ this.zipCode = zipCode;
+ }
+
+ public String getCity() {
+ return city;
+ }
+
+ public void setCity(String city) {
+ this.city = city;
+ }
+
+ public String getNotes() {
+ return notes;
+ }
+
+ public void setNotes(String notes) {
+ this.notes = notes;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public List getAttachments() {
+ return attachments;
+ }
+
+ public void setAttachments(List attachments) {
+ this.attachments = attachments;
+ }
+}
diff --git a/src/main/java/de/iwomm/propify_api/entity/Attachment.java b/src/main/java/de/iwomm/propify_api/entity/Attachment.java
new file mode 100644
index 0000000..68fccff
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/entity/Attachment.java
@@ -0,0 +1,72 @@
+package de.iwomm.propify_api.entity;
+
+import jakarta.persistence.*;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Entity
+public class Attachment {
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ @Column(nullable = false)
+ private UUID id;
+
+ @Column(nullable = false)
+ private String storagePath;
+
+ @Column(nullable = false)
+ private String fileName;
+
+ @ManyToOne()
+ @JoinColumn(name = "property_id")
+ private Property property;
+
+ @Column(nullable = false)
+ private LocalDateTime createdAt;
+
+ @PrePersist
+ protected void onCreate() {
+ this.createdAt = LocalDateTime.now();
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public void setProperty(Property property) {
+ this.property = property;
+ }
+
+ public Property getProperty() {
+ return property;
+ }
+
+ public String getStoragePath() {
+ return storagePath;
+ }
+
+ public void setStoragePath(String storagePath) {
+ this.storagePath = storagePath;
+ }
+
+ public UUID getId() {
+ return id;
+ }
+
+ public void setId(UUID id) {
+ this.id = id;
+ }
+
+ public String getFileName() {
+ return fileName;
+ }
+
+ public void setFileName(String fileName) {
+ this.fileName = fileName;
+ }
+}
diff --git a/src/main/java/de/iwomm/propify_api/entity/Property.java b/src/main/java/de/iwomm/propify_api/entity/Property.java
index cbfe165..0dc27a9 100644
--- a/src/main/java/de/iwomm/propify_api/entity/Property.java
+++ b/src/main/java/de/iwomm/propify_api/entity/Property.java
@@ -1,9 +1,9 @@
package de.iwomm.propify_api.entity;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.Id;
+import jakarta.persistence.*;
+import java.time.LocalDateTime;
+import java.util.List;
import java.util.UUID;
@Entity
@@ -12,16 +12,83 @@ public class Property {
@GeneratedValue
private UUID id;
+ @Column(nullable = false)
private String name;
+
+ @Column(nullable = false)
private String street;
+
+ @Column(nullable = false)
private String houseNumber;
+
+ @Column(nullable = false)
private String zipCode;
+
+ @Column(nullable = false)
private String city;
- private String country;
+
private String notes;
+ @Column(nullable = false)
+ private String country;
+
+ @OneToMany(mappedBy = "property", orphanRemoval = true)
+ @OrderBy("fileName")
+ private List attachments;
+
+ @Column(nullable = false)
+ private LocalDateTime createdAt;
+
+ private LocalDateTime updatedAt;
+
+ public List getAttachments() {
+ return attachments;
+ }
+
+ public void setAttachments(List attachment) {
+ this.attachments = attachment;
+ }
+
+
public Property() {}
+ public Property(String name, String street, String houseNumber, String zipCode, String city, String country, String notes) {
+ this.name = name;
+ this.street = street;
+ this.houseNumber = houseNumber;
+ this.zipCode = zipCode;
+ this.city = city;
+ this.country = country;
+ this.notes = notes;
+ }
+
+ @PrePersist
+ protected void onCreate() {
+ this.createdAt = LocalDateTime.now();
+ this.updatedAt = LocalDateTime.now();
+ }
+
+ @PreUpdate
+ protected void onUpdate() {
+ this.updatedAt = LocalDateTime.now();
+ }
+
+ public LocalDateTime getCreatedAt() {
+ return createdAt;
+ }
+
+ public void setCreatedAt(LocalDateTime createdAt) {
+ this.createdAt = createdAt;
+ }
+
+ public LocalDateTime getUpdatedAt() {
+ return updatedAt;
+ }
+
+ public void setUpdatedAt(LocalDateTime updatedAt) {
+ this.updatedAt = updatedAt;
+ }
+
public void setId(UUID id) {
this.id = id;
}
diff --git a/src/main/java/de/iwomm/propify_api/repository/AttachmentRepository.java b/src/main/java/de/iwomm/propify_api/repository/AttachmentRepository.java
new file mode 100644
index 0000000..21034cf
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/repository/AttachmentRepository.java
@@ -0,0 +1,10 @@
+package de.iwomm.propify_api.repository;
+
+import de.iwomm.propify_api.entity.Attachment;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+import java.util.Optional;
+import java.util.UUID;
+
+public interface AttachmentRepository extends JpaRepository {
+}
diff --git a/src/main/java/de/iwomm/propify_api/s3/S3ClientConfig.java b/src/main/java/de/iwomm/propify_api/s3/S3ClientConfig.java
new file mode 100644
index 0000000..e0d93dc
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/s3/S3ClientConfig.java
@@ -0,0 +1,37 @@
+package de.iwomm.propify_api.s3;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties(prefix = "s3")
+public class S3ClientConfig {
+
+ private String accessKey;
+ private String secretKey;
+ private String endpoint;
+
+ public String getAccessKey() {
+ return accessKey;
+ }
+
+ public void setAccessKey(String accessKey) {
+ this.accessKey = accessKey;
+ }
+
+ public String getSecretKey() {
+ return secretKey;
+ }
+
+ public void setSecretKey(String secretKey) {
+ this.secretKey = secretKey;
+ }
+
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+ public void setEndpoint(String endpoint) {
+ this.endpoint = endpoint;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/iwomm/propify_api/s3/S3Service.java b/src/main/java/de/iwomm/propify_api/s3/S3Service.java
new file mode 100644
index 0000000..0f2df2a
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/s3/S3Service.java
@@ -0,0 +1,46 @@
+package de.iwomm.propify_api.s3;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.sync.RequestBody;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.s3.S3Client;
+import software.amazon.awssdk.services.s3.S3Configuration;
+import software.amazon.awssdk.services.s3.model.PutObjectRequest;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+
+@Service
+public class S3Service {
+
+ private final S3Client s3Client;
+
+ public S3Service(S3ClientConfig s3ClientConfig) {
+ AwsBasicCredentials credentials = AwsBasicCredentials.create(s3ClientConfig.getAccessKey(), s3ClientConfig.getSecretKey());
+
+ this.s3Client = S3Client.builder()
+ .endpointOverride(URI.create(s3ClientConfig.getEndpoint()))
+ .credentialsProvider(StaticCredentialsProvider.create(credentials))
+ .region(Region.US_EAST_1)
+ .serviceConfiguration(S3Configuration.builder()
+ .pathStyleAccessEnabled(true)
+ .build())
+ .build();
+ }
+
+
+ public void putObject(String bucket, String key, MultipartFile file) throws IOException {
+ PutObjectRequest request = PutObjectRequest.builder()
+ .bucket(bucket)
+ .key(key)
+ .contentType(file.getContentType())
+ .build();
+
+ s3Client.putObject(request, RequestBody.fromBytes(file.getBytes()));
+ }
+}
diff --git a/src/main/java/de/iwomm/propify_api/s3/UploadResponse.java b/src/main/java/de/iwomm/propify_api/s3/UploadResponse.java
new file mode 100644
index 0000000..1d4025e
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/s3/UploadResponse.java
@@ -0,0 +1,33 @@
+package de.iwomm.propify_api.s3;
+
+import org.springframework.http.HttpStatus;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class UploadResponse {
+ private final HttpStatus status;
+ private List messages;
+
+ public UploadResponse(HttpStatus status, List messages) {
+ this.status = status;
+ this.messages = messages;
+ }
+
+ public UploadResponse(HttpStatus status, String message) {
+ this.status = status;
+ this.messages = new ArrayList<>();
+ this.messages.add(message);
+ }
+
+ public List getMessages() {
+ return messages;
+ }
+
+ public void setMessages(List messages) {
+ this.messages = messages;
+ }
+ public void addMessage(String message) {
+ this.messages.add(message);
+ }
+}
diff --git a/src/main/java/de/iwomm/propify_api/security/CorsConfig.java b/src/main/java/de/iwomm/propify_api/security/CorsConfig.java
index 6705614..ccc8ae6 100644
--- a/src/main/java/de/iwomm/propify_api/security/CorsConfig.java
+++ b/src/main/java/de/iwomm/propify_api/security/CorsConfig.java
@@ -1,14 +1,9 @@
package de.iwomm.propify_api.security;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-import java.util.ArrayList;
-import java.util.List;
-
@Configuration
public class CorsConfig implements WebMvcConfigurer {
private final CorsProperties corsProperties;
@@ -21,7 +16,7 @@ public class CorsConfig implements WebMvcConfigurer {
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // Apply rules to all endpoints
.allowedOrigins(corsProperties.getAllowedOrigins().toArray(new String[0])) // This targets the frontend app's URLs (you can allow multiple URLs, e.g. "http://localhost:4200,http://example.com"
- .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+ .allowedMethods("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS")
.allowedHeaders(corsProperties.getAllowedHeaders())
.allowCredentials(true); // Allow cookies and authentication headers
}
diff --git a/src/main/java/de/iwomm/propify_api/security/KeycloakRoleConverter.java b/src/main/java/de/iwomm/propify_api/security/KeycloakRoleConverter.java
index d09984e..625cd4a 100644
--- a/src/main/java/de/iwomm/propify_api/security/KeycloakRoleConverter.java
+++ b/src/main/java/de/iwomm/propify_api/security/KeycloakRoleConverter.java
@@ -14,17 +14,17 @@ public class KeycloakRoleConverter implements Converter convert(Jwt jwt) {
- // Holen Sie sich das "realm_access" Feld aus dem Token
+ // Get the "realm_access" field from token
Map realmAccess = (Map) jwt.getClaims().get("realm_access");
if (realmAccess == null || realmAccess.isEmpty()) {
return List.of();
}
- // Holen Sie sich die Liste der Rollen
+ // Get list of roles
List roles = (List) realmAccess.get("roles");
- // Konvertieren Sie die Rollen in Spring Security GrantedAuthority-Objekte
+ // KConvert roles to Spring Security GrantedAuthority-Objects
return roles.stream()
.map(roleName -> "ROLE_" + roleName.toUpperCase()) // Empfohlene Namenskonvention
.map(SimpleGrantedAuthority::new)
diff --git a/src/main/java/de/iwomm/propify_api/security/SecurityConfig.java b/src/main/java/de/iwomm/propify_api/security/SecurityConfig.java
index 361dde4..b74fd57 100644
--- a/src/main/java/de/iwomm/propify_api/security/SecurityConfig.java
+++ b/src/main/java/de/iwomm/propify_api/security/SecurityConfig.java
@@ -9,7 +9,6 @@ import org.springframework.security.config.annotation.web.configurers.AbstractHt
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;
-import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
@@ -36,7 +35,7 @@ public class SecurityConfig {
return http.build();
}
- // Konvertiert die Keycloak-Rollen (im JWT) in Spring Security Authorities
+ // Convert Keycloak-Roles (in JWT) in Spring Security Authorities
private JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRoleConverter());
diff --git a/src/main/java/de/iwomm/propify_api/service/AttachmentService.java b/src/main/java/de/iwomm/propify_api/service/AttachmentService.java
new file mode 100644
index 0000000..270f8bc
--- /dev/null
+++ b/src/main/java/de/iwomm/propify_api/service/AttachmentService.java
@@ -0,0 +1,46 @@
+package de.iwomm.propify_api.service;
+
+import de.iwomm.propify_api.dto.AttachmentDTO;
+import de.iwomm.propify_api.dto.PropertyDTO;
+import de.iwomm.propify_api.entity.Attachment;
+import de.iwomm.propify_api.repository.AttachmentRepository;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+@Service
+public class AttachmentService {
+ private final AttachmentRepository attachmentRepository;
+
+ public AttachmentService(AttachmentRepository attachmentRepository) {
+ this.attachmentRepository = attachmentRepository;
+ }
+
+ public void save(Attachment attachment) {
+ attachmentRepository.save(attachment);
+ }
+
+ public void delete(Attachment attachment) {
+ attachmentRepository.delete(attachment);
+ }
+
+ public void deleteById(UUID id) {
+ attachmentRepository.deleteById(id);
+ }
+
+ public List toDTOs(List attachments) {
+ List dtos = new ArrayList<>();
+
+ attachments.forEach(attachment -> {
+ dtos.add(new AttachmentDTO(
+ attachment.getId(),
+ attachment.getStoragePath(),
+ attachment.getFileName()
+ ));
+ });
+
+ return dtos;
+ }
+}
diff --git a/src/main/java/de/iwomm/propify_api/service/PropertyService.java b/src/main/java/de/iwomm/propify_api/service/PropertyService.java
index 7e51012..a392ba9 100644
--- a/src/main/java/de/iwomm/propify_api/service/PropertyService.java
+++ b/src/main/java/de/iwomm/propify_api/service/PropertyService.java
@@ -1,9 +1,15 @@
package de.iwomm.propify_api.service;
+import de.iwomm.propify_api.dto.AttachmentDTO;
+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.repository.PropertyRepository;
+import jakarta.persistence.EntityNotFoundException;
import org.springframework.stereotype.Service;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -32,7 +38,65 @@ public class PropertyService {
return propertyRepository.save(property);
}
- public void delete(Property property) {
+ public void deleteById(UUID id) {
+ Property property = propertyRepository.findById(id).orElseThrow(EntityNotFoundException::new);
propertyRepository.delete(property);
}
+
+ public void deleteByIds(BulkDeletePropertyIdsDTO propertyIdsDTO) {
+ propertyRepository.deleteAllById(propertyIdsDTO.getIds());
+ }
+
+ public Property update(UUID id, PropertyDTO propertyDto) {
+ Property updated = propertyRepository.findById(id).orElseThrow(EntityNotFoundException::new);
+
+ updated.setName(propertyDto.getName());
+ updated.setStreet(propertyDto.getStreet());
+ updated.setHouseNumber(propertyDto.getHouseNumber());
+ updated.setZipCode(propertyDto.getZipCode());
+ updated.setCity(propertyDto.getCity());
+ updated.setCountry(propertyDto.getCountry());
+ updated.setNotes(propertyDto.getNotes());
+
+ propertyRepository.save(updated);
+
+ return updated;
+ }
+
+ public Property saveDTO(PropertyDTO dto) {
+ return this.save(new Property(
+ dto.getName(),
+ dto.getStreet(),
+ dto.getHouseNumber(),
+ dto.getZipCode(),
+ dto.getCity(),
+ dto.getCountry(),
+ dto.getNotes()
+ ));
+ }
+
+ public List toDTOs(List properties) {
+ List dtos = new ArrayList<>();
+
+ properties.forEach(property -> {
+ List attachments = new ArrayList<>();
+ property.getAttachments().forEach(attachment -> {
+ attachments.add(new AttachmentDTO(attachment.getId(), attachment.getStoragePath(), attachment.getFileName()));
+ });
+
+ dtos.add(new PropertyDTO(
+ property.getId(),
+ property.getName(),
+ property.getStreet(),
+ property.getHouseNumber(),
+ property.getZipCode(),
+ property.getCity(),
+ property.getCountry(),
+ property.getNotes(),
+ attachments
+ ));
+ });
+
+ return dtos;
+ }
}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 872f6f7..9ae804c 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,32 +1,60 @@
spring:
+
+ servlet:
+ multipart:
+ max-file-size: 1000MB # Maximal zulässige Größe pro Datei (z.B. 10 MB)
+ max-request-size: 5000MB # Maximal zulässige Größe der gesamten Anfrage (z.B. 50 MB)
+ profiles:
+ active: dev
application:
- name: propify-api
+ name: skamp-api
datasource:
- url: jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1
- driver-class-name: org.h2.Driver
- username: sa
- password:
+ url: jdbc:postgresql://localhost:5432/skamp
+ username: dev
+ password: dev
+ driver-class-name: org.postgresql.Driver
jpa:
hibernate:
- ddl-auto: update
+ ddl-auto: update # möglich: validate, update, create, create-drop
show-sql: true
- h2:
- console:
- enabled: true
+ properties:
+ hibernate:
+ format_sql: true
+ database-platform: org.hibernate.dialect.PostgreSQLDialect
+# datasource:
+# url: jdbc:h2:mem:demo;DB_CLOSE_DELAY=-1
+# driver-class-name: org.h2.Driver
+# username: sa
+# password:
+# jpa:
+# hibernate:
+# ddl-auto: update
+# show-sql: true
+# h2:
+# console:
+# enabled: true
+
security:
oauth2:
resourceserver:
jwt:
- issuer-uri: http://localhost:8280/realms/propify
+ issuer-uri: http://localhost:8280/realms/skamp
jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs
+
logging:
level:
org:
springframework:
security: DEBUG
+
+
cors:
allowed-origins:
- http://localhost:4200
- - http://localhost:8080
- allowed-headers: "*"
\ No newline at end of file
+ allowed-headers: "*"
+
+s3:
+ access-key: dev
+ secret-key: dev123456
+ endpoint: http://localhost:9000
\ No newline at end of file