Compare commits

1 Commits

Author SHA1 Message Date
Murat Özkorkmaz
fb416dff55 Several fixes
All checks were successful
Build, Push and Deploy / build-and-deploy (push) Successful in 55s
- added organizations
- added industries
- added logo in 2 colors for light and dark theme
- improved authorization to allow multi tenancy
- added Bruno configs
2025-11-13 19:56:12 +01:00
38 changed files with 948 additions and 53 deletions

View File

@@ -1,48 +0,0 @@
meta {
name: Auth App
type: http
seq: 1
}
post {
url: {{KEYCLOAK_BASE_URL}}/realms/{{KEYCLOAK_REALM}}/protocol/openid-connect/token
body: formUrlEncoded
auth: inherit
}
headers {
Content-Type: application/x-www-form-urlencoded
}
body:form-urlencoded {
grant_type: password
client_id: {{KEYCLOAK_CLIENT_ID}}
username: {{ADMIN_USERNAME}}
password: {{ADMIN_PASSWORD}}
}
script:post-response {
// Parse die JSON-Antwort
const jsonResponse = res.body;
if (jsonResponse.access_token) {
// Schreibe den access_token in eine Umgebungsvariable
// oder in eine collection-Variable
// Option 1: In eine Umgebungsvariable schreiben
// (z.B. für eine bestimmte Umgebung wie "Development")
bru.setEnvVar("BEARER_TOKEN", jsonResponse.access_token);
// Option 2: In eine Collection-Variable schreiben
// (Diese Variable ist global für alle Anfragen in deiner Collection)
// bru.setVar("bearerToken", "Bearer " + jsonResponse.access_token);
} else {
// optional: Error Handling, falls der Token nicht in der Antwort ist
console.log("Error: access_token not found in the response.");
}
}
settings {
encodeUrl: false
}

View File

@@ -33,6 +33,7 @@ script:post-response {
// (z.B. für eine bestimmte Umgebung wie "Development")
bru.setEnvVar("BEARER_TOKEN", jsonResponse.access_token);
console.log("Updated access token.");
// Option 2: In eine Collection-Variable schreiben
// (Diese Variable ist global für alle Anfragen in deiner Collection)
// bru.setVar("bearerToken", "Bearer " + jsonResponse.access_token);

View File

@@ -1,7 +1,7 @@
meta {
name: Upload Organizations
type: http
seq: 3
seq: 2
}
post {

View File

@@ -1,7 +1,7 @@
meta {
name: Upload Persons
type: http
seq: 2
seq: 1
}
post {

View File

@@ -0,0 +1,31 @@
meta {
name: Create new
type: http
seq: 3
}
post {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/industries
body: json
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"
}
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,31 @@
meta {
name: Delete one by ID
type: http
seq: 4
}
delete {
url: API_BASE_URL}}/api/{{API_VERSION}}/industries/e64d5e53-cd45-45fb-9237-46078077bf22
body: json
auth: bearer
}
auth:bearer {
token: {{BEARER_TOKEN}}
}
body:json {
{
"name": "Mustername 1",
"street": "Musterstraße",
"houseNumber": "1",
"zipCode": "55123",
"city": "Musterstadt",
"country": "de",
"notes": "Lorem ipsum"
}
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,19 @@
meta {
name: Get all
type: http
seq: 1
}
get {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/industries
body: none
auth: bearer
}
auth:bearer {
token: {{BEARER_TOKEN}}
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,19 @@
meta {
name: Get one by ID
type: http
seq: 2
}
get {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/industries/da002464-33e9-4bfd-9ae2-836b26955502
body: none
auth: bearer
}
auth:bearer {
token: {{BEARER_TOKEN}}
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,39 @@
meta {
name: Upload files
type: http
seq: 5
}
post {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/industries/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

@@ -0,0 +1,8 @@
meta {
name: Industries
seq: 9
}
auth {
mode: inherit
}

View File

@@ -0,0 +1,31 @@
meta {
name: Create new
type: http
seq: 3
}
post {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/organizations
body: json
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"
}
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,31 @@
meta {
name: Delete one by ID
type: http
seq: 4
}
delete {
url: API_BASE_URL}}/api/{{API_VERSION}}/organizations/e64d5e53-cd45-45fb-9237-46078077bf22
body: json
auth: bearer
}
auth:bearer {
token: {{BEARER_TOKEN}}
}
body:json {
{
"name": "Mustername 1",
"street": "Musterstraße",
"houseNumber": "1",
"zipCode": "55123",
"city": "Musterstadt",
"country": "de",
"notes": "Lorem ipsum"
}
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,19 @@
meta {
name: Get all
type: http
seq: 1
}
get {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/organizations
body: none
auth: bearer
}
auth:bearer {
token: {{BEARER_TOKEN}}
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,19 @@
meta {
name: Get one by ID
type: http
seq: 2
}
get {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/organizations/5dba067e-d7fd-4d79-a08a-ec379834938a
body: none
auth: bearer
}
auth:bearer {
token: {{BEARER_TOKEN}}
}
settings {
encodeUrl: true
}

View File

@@ -0,0 +1,39 @@
meta {
name: Upload files
type: http
seq: 5
}
post {
url: {{API_BASE_URL}}/api/{{API_VERSION}}/organizations/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

@@ -0,0 +1,8 @@
meta {
name: Organizations
seq: 8
}
auth {
mode: inherit
}

View File

@@ -9,8 +9,8 @@ vars {
KEYCLOAK_CLIENT_ID: skamp-app
KEYCLOAK_REALM: skamp
KEYCLOAK_BRUNO_CLIENT_ID: skamp-bruno
KEYCLOAK_BRUNO_CLIENT_SECRET: sNQpCVyVckGo5AZw7FqeW0POtgWuXzJt
}
vars:secret [
BEARER_TOKEN
BEARER_TOKEN,
KEYCLOAK_BRUNO_CLIENT_SECRET
]

View File

@@ -0,0 +1,134 @@
package de.iwomm.propify_api.controller;
import de.iwomm.propify_api.dto.*;
import de.iwomm.propify_api.entity.Industry;
import de.iwomm.propify_api.mapper.IndustryMapper;
import de.iwomm.propify_api.service.IndustryService;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1/industries")
public class IndustryController {
private final IndustryService industryService;
private final IndustryMapper industryMapper;
public IndustryController(IndustryService industryService, IndustryMapper industryMapper) {
this.industryService = industryService;
this.industryMapper = industryMapper;
}
@GetMapping
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_DEV')")
public ResponseEntity<?> getAll() {
List<IndustryDTO> industryDTOs = industryService.toDTOs(industryService.findAll());
return ResponseEntity
.ok(industryDTOs);
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_DEV')")
public ResponseEntity<?> getById(@PathVariable UUID id) {
try {
Industry industry = industryService.findById(id).orElseThrow(EntityNotFoundException::new);
IndustryDTO projectDTO = industryMapper.toDto(industry);
return ResponseEntity
.ok(projectDTO);
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@PostMapping
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_DEV')")
public ResponseEntity<?> create(@RequestBody IndustryDTO newIndustryDTO) {
try {
Industry newItem = industryService.save(newIndustryDTO);
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("hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_DEV')")
public ResponseEntity<?> update(@PathVariable UUID id, @RequestBody IndustryDTO industryDTO) {
try {
return ResponseEntity
.ok(industryService.update(id, industryDTO));
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_DEV')")
public ResponseEntity<?> delete(@PathVariable UUID id) {
try {
industryService.deleteById(id);
return ResponseEntity
.noContent()
.build();
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@PostMapping("/bulk-delete")
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER', 'ROLE_DEV')")
public ResponseEntity<?> deleteMany(@RequestBody BulkDeleteIdsDTO bulkDeleteIdsDTO) {
try {
industryService.deleteByIds(bulkDeleteIdsDTO);
return ResponseEntity
.noContent()
.build();
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
}

View File

@@ -0,0 +1,102 @@
package de.iwomm.propify_api.controller;
import de.iwomm.propify_api.dto.OrganizationDTO;
import de.iwomm.propify_api.dto.ProjectDetailsDTO;
import de.iwomm.propify_api.entity.Organization;
import de.iwomm.propify_api.entity.Project;
import de.iwomm.propify_api.mapper.OrganizationMapper;
import de.iwomm.propify_api.service.OrganisationService;
import jakarta.persistence.EntityNotFoundException;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import java.net.URI;
import java.util.List;
import java.util.UUID;
@RestController
@RequestMapping("/api/v1/organizations")
public class OrganizationController {
private final OrganizationMapper organizationMapper;
private OrganisationService organisationService;
public OrganizationController(OrganisationService organisationService, OrganizationMapper organizationMapper) {
this.organisationService = organisationService;
this.organizationMapper = organizationMapper;
}
@GetMapping
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')")
public ResponseEntity<?> getAll() {
List<OrganizationDTO> organizationDTOs = organisationService.toDTOs(organisationService.findAll());
return ResponseEntity
.ok(organizationDTOs);
}
@GetMapping("/{id}")
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')")
public ResponseEntity<?> getById(@PathVariable UUID id) {
try {
Organization organization = organisationService.findById(id).orElseThrow(EntityNotFoundException::new);
OrganizationDTO organizationDTO = organizationMapper.toDto(organization);
return ResponseEntity
.ok(organizationDTO);
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@DeleteMapping("/{id}")
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')")
public ResponseEntity<?> deleteById(@PathVariable UUID id) {
try {
Organization organization = organisationService.findById(id).orElseThrow(EntityNotFoundException::new);
organisationService.delete(organization);
return ResponseEntity
.ok()
.build();
} catch (EntityNotFoundException e) {
return ResponseEntity
.notFound()
.build();
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
@PostMapping
@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_DEV')")
public ResponseEntity<?> create(@RequestBody OrganizationDTO organizationDTO) {
try {
Organization newItem = organisationService.save(organizationDTO);
URI location = ServletUriComponentsBuilder.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(newItem.getId())
.toUri();
return ResponseEntity
.created(location)
.body(newItem);
} catch (Exception e) {
return ResponseEntity
.internalServerError()
.build();
}
}
}

View File

@@ -0,0 +1,9 @@
package de.iwomm.propify_api.dto;
import java.util.List;
import java.util.UUID;
public record BulkDeleteIdsDTO(
List<UUID> ids
) {
}

View File

@@ -0,0 +1,9 @@
package de.iwomm.propify_api.dto;
import java.util.UUID;
public record IndustryDTO(
UUID id,
String name
) {
}

View File

@@ -0,0 +1,10 @@
package de.iwomm.propify_api.dto;
import java.util.UUID;
public record NewOrganizationDTO(
String name,
String owner,
UUID industryId
) {
}

View File

@@ -0,0 +1,14 @@
package de.iwomm.propify_api.dto;
import de.iwomm.propify_api.entity.Industry;
import java.util.Set;
import java.util.UUID;
public record OrganizationDTO(
UUID id,
String name,
IndustryDTO industry,
String owner,
Set<OrganizationLabelDTO> labels
) { }

View File

@@ -0,0 +1,9 @@
package de.iwomm.propify_api.dto;
import java.util.UUID;
public record OrganizationLabelDTO(
UUID id,
String name
) {
}

View File

@@ -0,0 +1,24 @@
package de.iwomm.propify_api.entity;
import de.iwomm.propify_api.entitylistener.TenantEntityListener;
import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.MappedSuperclass;
@MappedSuperclass
@EntityListeners(TenantEntityListener.class)
public abstract class MultiTenantEntity {
@Column(name = "tenant_id")
private String tenantId;
public String getTenantId() {
return tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
}

View File

@@ -2,6 +2,8 @@ package de.iwomm.propify_api.entity;
import jakarta.persistence.*;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@Entity
@@ -24,6 +26,15 @@ public class Organization {
@Column(nullable = false)
private LocalDateTime createdAt;
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(
name = "organization_labels",
joinColumns = @JoinColumn(name = "organization_id"),
inverseJoinColumns = @JoinColumn(name = "label_id"),
foreignKey = @ForeignKey(name = "fk_organization_labels_organization", foreignKeyDefinition = "FOREIGN KEY (organization_id) REFERENCES organization(id) ON DELETE CASCADE")
)
private Set<OrganizationLabel> labels = new HashSet<>();
@PrePersist
protected void onCreate() {
this.createdAt = LocalDateTime.now();
@@ -68,4 +79,12 @@ public class Organization {
public void setCreatedAt(LocalDateTime createdAt) {
this.createdAt = createdAt;
}
public Set<OrganizationLabel> getLabels() {
return labels;
}
public void setLabels(Set<OrganizationLabel> labels) {
this.labels = labels;
}
}

View File

@@ -0,0 +1,31 @@
package de.iwomm.propify_api.entity;
import jakarta.persistence.*;
import java.util.UUID;
@Entity
public class OrganizationLabel {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(nullable = false)
private UUID id;
@Column(nullable = false)
private String name;
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;
}
}

View File

@@ -2,6 +2,9 @@ package de.iwomm.propify_api.entity;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.*;
import org.hibernate.annotations.Filter;
import org.hibernate.annotations.FilterDef;
import org.hibernate.annotations.ParamDef;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -10,7 +13,9 @@ import java.util.List;
import java.util.UUID;
@Entity
public class Project {
@FilterDef(name = "tenantFilter", parameters = @ParamDef(name = "tenantId", type = String.class))
@Filter(name = "tenantFilter", condition = "tenant_id = :tenantId")
public class Project extends MultiTenantEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Column(nullable = false)

View File

@@ -0,0 +1,18 @@
package de.iwomm.propify_api.entitylistener;
import de.iwomm.propify_api.entity.MultiTenantEntity;
import de.iwomm.propify_api.security.TenantContext;
import jakarta.persistence.PrePersist;
public class TenantEntityListener {
@PrePersist
public void setTenant(Object entity) {
if (entity instanceof MultiTenantEntity mte) {
String tenantId = TenantContext.getTenantId();
if (tenantId != null && mte.getTenantId() == null) {
mte.setTenantId(tenantId);
}
}
}
}

View File

@@ -0,0 +1,11 @@
package de.iwomm.propify_api.mapper;
import de.iwomm.propify_api.dto.IndustryDTO;
import de.iwomm.propify_api.entity.Industry;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface IndustryMapper {
IndustryDTO toDto(Industry industry);
Industry fromDto(IndustryDTO industryDTO);
}

View File

@@ -0,0 +1,11 @@
package de.iwomm.propify_api.mapper;
import de.iwomm.propify_api.dto.OrganizationLabelDTO;
import de.iwomm.propify_api.entity.OrganizationLabel;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring")
public interface OrganizationLabelMapper {
OrganizationLabelDTO toDto(OrganizationLabel organizationLabel);
OrganizationLabel fromDto(OrganizationLabelDTO organizationLabelDTO);
}

View File

@@ -0,0 +1,12 @@
package de.iwomm.propify_api.mapper;
import de.iwomm.propify_api.dto.NewOrganizationDTO;
import de.iwomm.propify_api.dto.OrganizationDTO;
import de.iwomm.propify_api.entity.Organization;
import org.mapstruct.Mapper;
@Mapper(componentModel = "spring", uses = { OrganizationLabelMapper.class, IndustryMapper.class })
public interface OrganizationMapper {
OrganizationDTO toDto(Organization organization);
Organization fromDto(OrganizationDTO organizationDTO);
}

View File

@@ -0,0 +1,9 @@
package de.iwomm.propify_api.repository;
import de.iwomm.propify_api.entity.OrganizationLabel;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface OrganizationLabelRepository extends JpaRepository<OrganizationLabel, UUID> {
}

View File

@@ -0,0 +1,17 @@
package de.iwomm.propify_api.security;
public class TenantContext {
private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>();
public static void setTenantId(String tenantId) {
CURRENT_TENANT.set(tenantId);
}
public static String getTenantId() {
return CURRENT_TENANT.get();
}
public static void clear() {
CURRENT_TENANT.remove();
}
}

View File

@@ -0,0 +1,75 @@
package de.iwomm.propify_api.security;
import jakarta.persistence.EntityManager;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.hibernate.Session;
import org.springframework.core.annotation.Order;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.List;
@Component
@Order(1)
public class TenantFilter extends OncePerRequestFilter {
private final EntityManager entityManager;
public TenantFilter(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String tenantId = null;
// 1⃣ Tenant-ID aus JWT lesen
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof JwtAuthenticationToken jwtAuth) {
Jwt jwt = jwtAuth.getToken();
// tenant_id Claim
tenantId = jwt.getClaimAsString("tenant_id");
// Rollen prüfen, Superadmin darf alles sehen
List<String> roles = jwt.getClaimAsStringList("roles");
if (roles != null && roles.contains("ROLE_SUPERADMIN")) {
tenantId = null; // kein Filter für Superadmin
}
}
// 2⃣ Fallback: Header (optional, falls JWT fehlt)
if (tenantId == null) {
tenantId = request.getHeader("X-Tenant-ID");
}
// 3⃣ TenantContext für diesen Thread setzen
TenantContext.setTenantId(tenantId);
// 4⃣ Hibernate Filter aktivieren (falls Tenant-ID vorhanden)
if (tenantId != null) {
Session session = entityManager.unwrap(Session.class);
session.enableFilter("tenantFilter")
.setParameter("tenantId", tenantId);
}
try {
filterChain.doFilter(request, response);
} finally {
// 5⃣ Aufräumen nach Request
TenantContext.clear();
}
}
}

View File

@@ -0,0 +1,69 @@
package de.iwomm.propify_api.service;
import de.iwomm.propify_api.dto.*;
import de.iwomm.propify_api.entity.Industry;
import de.iwomm.propify_api.entity.Organization;
import de.iwomm.propify_api.entity.Project;
import de.iwomm.propify_api.entity.Property;
import de.iwomm.propify_api.mapper.IndustryMapper;
import de.iwomm.propify_api.repository.IndustryRepository;
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;
@Service
public class IndustryService {
private IndustryRepository industryRepository;
private IndustryMapper industryMapper;
public IndustryService(IndustryRepository industryRepository, IndustryMapper industryMapper) {
this.industryRepository = industryRepository;
this.industryMapper = industryMapper;
}
public List<Industry> findAll() {
return industryRepository.findAll();
}
public Optional<Industry> findById(UUID id) {
return industryRepository.findById(id);
}
public Industry save(IndustryDTO dto) {
return industryRepository.save(industryMapper.fromDto(dto));
}
public void deleteByIds(BulkDeleteIdsDTO bulkDeleteIdsDTO) {
industryRepository.deleteAllById(bulkDeleteIdsDTO.ids());
}
public List<IndustryDTO> toDTOs(List<Industry> industries) {
List<IndustryDTO> dtos = new ArrayList<>();
industries.forEach(industry -> {
dtos.add(this.industryMapper.toDto(industry));
});
return dtos;
}
public Industry update(UUID id, IndustryDTO industryDTO) {
Industry updated = industryRepository.findById(id).orElseThrow(EntityNotFoundException::new);
updated.setName(industryDTO.name());
industryRepository.save(updated);
return updated;
}
public void deleteById(UUID id) {
Industry industry = industryRepository.findById(id).orElseThrow(EntityNotFoundException::new);
industryRepository.delete(industry);
}
}

View File

@@ -0,0 +1,59 @@
package de.iwomm.propify_api.service;
import de.iwomm.propify_api.dto.NewOrganizationDTO;
import de.iwomm.propify_api.dto.OrganizationDTO;
import de.iwomm.propify_api.dto.ProjectDTO;
import de.iwomm.propify_api.entity.Organization;
import de.iwomm.propify_api.entity.Project;
import de.iwomm.propify_api.mapper.OrganizationMapper;
import de.iwomm.propify_api.repository.OrganizationRepository;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@Service
public class OrganisationService {
private final OrganizationRepository organizationRepository;
private final OrganizationMapper organizationMapper;
public OrganisationService(
OrganizationRepository organizationRepository,
OrganizationMapper organizationMapper
) {
this.organizationRepository = organizationRepository;
this.organizationMapper = organizationMapper;
}
public List<Organization> findAll() {
return organizationRepository.findAll();
}
public Optional<Organization> findById(UUID id) {
return organizationRepository.findById(id);
}
public void deleteBbyId(UUID id) {
organizationRepository.deleteById(id);
}
public void delete(Organization organization) {
organizationRepository.delete(organization);
}
public List<OrganizationDTO> toDTOs(List<Organization> organizations) {
List<OrganizationDTO> dtos = new ArrayList<>();
organizations.forEach(organization -> {
dtos.add(this.organizationMapper.toDto(organization));
});
return dtos;
}
public Organization save(OrganizationDTO dto) {
return organizationRepository.save(organizationMapper.fromDto(dto));
}
}

View File

@@ -39,6 +39,7 @@ spring:
resourceserver:
jwt:
issuer-uri: ${KEYCLOAK_ISSUER_URI:https://kc.dev.localhost/realms/skamp}
#issuer-uri: ${KEYCLOAK_ISSUER_URI:http://localhost:8888/realms/skamp}
jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs
logging: