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

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# SKAMP Base API
## Environment
The default profile is `dev`.
In production, set it to `prod` via environment variables:
```bash
java ...
```

View File

@@ -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
}

View File

@@ -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"
}
}

View File

@@ -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
}

View File

@@ -1,6 +1,6 @@
{
"version": "1",
"name": "propify",
"name": "skamp-api",
"type": "collection",
"ignore": [
"node_modules",

View File

@@ -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
]

20
pom.xml
View File

@@ -9,10 +9,10 @@
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>de.iwomm</groupId>
<artifactId>propify-api</artifactId>
<artifactId>skamp-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>propify-api</name>
<description>Propify API</description>
<name>skamp-api</name>
<description>SKAMP API</description>
<url/>
<licenses>
<license/>
@@ -84,6 +84,20 @@
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/s3 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>2.33.0</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>

View File

@@ -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);
}
}

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();
}
}
}

View File

@@ -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.");
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<UUID> ids = new ArrayList<>();
public List<UUID> getIds() {
return ids;
}
public void setIds(List<UUID> ids) {
this.ids = ids;
}
}

View File

@@ -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<AttachmentDTO> attachments;
public PropertyDTO(UUID id, String name, String street, String houseNumber, String zipCode, String city, String country, String notes, List<AttachmentDTO> 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<AttachmentDTO> getAttachments() {
return attachments;
}
public void setAttachments(List<AttachmentDTO> attachments) {
this.attachments = attachments;
}
}

View File

@@ -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;
}
}

View File

@@ -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<Attachment> attachments;
@Column(nullable = false)
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
public List<Attachment> getAttachments() {
return attachments;
}
public void setAttachments(List<Attachment> 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;
}

View File

@@ -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<Attachment, UUID> {
}

View File

@@ -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;
}
}

View File

@@ -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()));
}
}

View File

@@ -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<String> messages;
public UploadResponse(HttpStatus status, List<String> 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<String> getMessages() {
return messages;
}
public void setMessages(List<String> messages) {
this.messages = messages;
}
public void addMessage(String message) {
this.messages.add(message);
}
}

View File

@@ -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
}

View File

@@ -14,17 +14,17 @@ public class KeycloakRoleConverter implements Converter<Jwt, Collection<GrantedA
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
// Holen Sie sich das "realm_access" Feld aus dem Token
// Get the "realm_access" field from token
Map<String, Object> realmAccess = (Map<String, Object>) 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<String> roles = (List<String>) 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)

View File

@@ -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());

View File

@@ -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<AttachmentDTO> toDTOs(List<Attachment> attachments) {
List<AttachmentDTO> dtos = new ArrayList<>();
attachments.forEach(attachment -> {
dtos.add(new AttachmentDTO(
attachment.getId(),
attachment.getStoragePath(),
attachment.getFileName()
));
});
return dtos;
}
}

View File

@@ -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<PropertyDTO> toDTOs(List<Property> properties) {
List<PropertyDTO> dtos = new ArrayList<>();
properties.forEach(property -> {
List<AttachmentDTO> 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;
}
}

View File

@@ -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: "*"
s3:
access-key: dev
secret-key: dev123456
endpoint: http://localhost:9000