refactoring, adaption, community membership handing start implementing
This commit is contained in:
parent
ab28328408
commit
5f845833cb
@ -5,9 +5,9 @@ Content-Type: application/json
|
|||||||
{
|
{
|
||||||
"firstName": "Admin",
|
"firstName": "Admin",
|
||||||
"lastName": "Admin",
|
"lastName": "Admin",
|
||||||
"email": "admin@admin.at",
|
"email": "test10@qwer.as",
|
||||||
"password": "SuperAdmin",
|
"password": "testtest",
|
||||||
"atNumber": "AT0010000000000000000000001234567"
|
"atNumber": "AT0010000000000000000000001234568"
|
||||||
}
|
}
|
||||||
|
|
||||||
### User Login
|
### User Login
|
||||||
|
|||||||
@ -2,8 +2,10 @@ package at.mueller.eeg.backend;
|
|||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
|
@EnableAsync
|
||||||
public class EegPortalApplication {
|
public class EegPortalApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
package at.mueller.eeg.backend.common.config.event;
|
package at.mueller.eeg.backend.common.event;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public record EdaTopologyCheckEvent(
|
public record EdaTopologyCheckEvent(
|
||||||
UUID userId,
|
UUID userId,
|
||||||
|
UUID meteringPointId,
|
||||||
String atNumber
|
String atNumber
|
||||||
) {}
|
) {}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
package at.mueller.eeg.backend.common.event;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record EdaTopologyCompletedEvent(
|
||||||
|
UUID userId,
|
||||||
|
UUID meteringPointId,
|
||||||
|
boolean isEligible,
|
||||||
|
String gridOperatorId,
|
||||||
|
String substationId,
|
||||||
|
String transformerId,
|
||||||
|
String gridLevel
|
||||||
|
) {}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
package at.mueller.eeg.backend.common.event;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record EdaTopologyFailedEvent(
|
||||||
|
UUID userId,
|
||||||
|
UUID meteringPointId,
|
||||||
|
String errorMessage
|
||||||
|
) {}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package at.mueller.eeg.backend.common.config.event;
|
package at.mueller.eeg.backend.common.event;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -1,3 +1,3 @@
|
|||||||
package at.mueller.eeg.backend.common.config.exception;
|
package at.mueller.eeg.backend.common.exception;
|
||||||
|
|
||||||
public record ErrorResponse(String message, int status) {}
|
public record ErrorResponse(String message, int status) {}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package at.mueller.eeg.backend.common.config.exception;
|
package at.mueller.eeg.backend.common.exception;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
package at.mueller.eeg.backend.community.api;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.community.api.dto.EligibleCommunityResponse;
|
||||||
|
import at.mueller.eeg.backend.community.api.dto.MembershipRequest;
|
||||||
|
import at.mueller.eeg.backend.community.service.MembershipService;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/community")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MembershipController {
|
||||||
|
|
||||||
|
private final MembershipService membershipService;
|
||||||
|
|
||||||
|
// --- USER ENDPUNKTE ---
|
||||||
|
|
||||||
|
@GetMapping("/metering-points/{id}/eligible-communities")
|
||||||
|
public ResponseEntity<List<EligibleCommunityResponse>> getEligibleCommunities(@PathVariable UUID id) {
|
||||||
|
return ResponseEntity.ok(membershipService.getEligibleCommunities(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/memberships/request")
|
||||||
|
public ResponseEntity<UUID> requestMembership(@Valid @RequestBody MembershipRequest request) {
|
||||||
|
UUID membershipId = membershipService.requestMembership(request);
|
||||||
|
return ResponseEntity.status(HttpStatus.CREATED).body(membershipId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ADMIN ENDPUNKTE ---
|
||||||
|
|
||||||
|
@PutMapping("/admin/memberships/{id}/approve")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')") // Verknüpfung mit deiner UserRole.ADMIN aus dem iam-Modul
|
||||||
|
public ResponseEntity<Void> approveMembership(@PathVariable UUID id) {
|
||||||
|
membershipService.approveMembership(id);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/admin/memberships/{id}/reject")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseEntity<Void> rejectMembership(@PathVariable UUID id) {
|
||||||
|
membershipService.rejectMembership(id);
|
||||||
|
return ResponseEntity.ok().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package at.mueller.eeg.backend.community.api.dto;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.community.domain.CommunityType;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record EligibleCommunityResponse(
|
||||||
|
UUID id,
|
||||||
|
String name,
|
||||||
|
CommunityType type,
|
||||||
|
String edaEcId
|
||||||
|
) {}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package at.mueller.eeg.backend.community.api.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Max;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record MembershipRequest(
|
||||||
|
@NotNull UUID meteringPointId,
|
||||||
|
@NotNull UUID energyCommunityId,
|
||||||
|
@NotNull @Min(1) @Max(5) Integer priorityLevel
|
||||||
|
) {}
|
||||||
@ -16,10 +16,19 @@ public class EnergyCommunity {
|
|||||||
private UUID id;
|
private UUID id;
|
||||||
|
|
||||||
@Column(unique = true, nullable = false)
|
@Column(unique = true, nullable = false)
|
||||||
private String edaEcId; // z.B. CC123456
|
private String edaEcId;
|
||||||
|
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private CommunityType type; // EEG_LOKAL, EEG_REGIONAL, BEG
|
private CommunityType type;
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
|
@Column(name = "grid_operator_id", length = 50)
|
||||||
|
private String gridOperatorId;
|
||||||
|
|
||||||
|
@Column(name = "substation_id", length = 50)
|
||||||
|
private String substationId;
|
||||||
|
|
||||||
|
@Column(name = "transformer_id", length = 50)
|
||||||
|
private String transformerId;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package at.mueller.eeg.backend.community.domain;
|
package at.mueller.eeg.backend.community.domain;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.community.domain.state.MakoState;
|
||||||
|
import at.mueller.eeg.backend.community.domain.state.MakoTrigger;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
@ -36,8 +38,49 @@ public class MeteringPoint {
|
|||||||
private String transformerId;
|
private String transformerId;
|
||||||
|
|
||||||
@Column(name = "grid_level")
|
@Column(name = "grid_level")
|
||||||
private Integer gridLevel;
|
private String gridLevel;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private MakoState makoState = MakoState.NEW;
|
||||||
|
|
||||||
@OneToMany(mappedBy = "meteringPoint", cascade = CascadeType.ALL)
|
@OneToMany(mappedBy = "meteringPoint", cascade = CascadeType.ALL)
|
||||||
private List<Membership> memberships;
|
private List<Membership> memberships;
|
||||||
|
|
||||||
|
public void fireTrigger(MakoTrigger trigger) {
|
||||||
|
this.makoState = switch (this.makoState) {
|
||||||
|
|
||||||
|
case NEW ->
|
||||||
|
trigger == MakoTrigger.SEND_REQUEST ? MakoState.WAITING_FOR_CONSENT : invalid(trigger);
|
||||||
|
|
||||||
|
case WAITING_FOR_CONSENT -> switch (trigger) {
|
||||||
|
case CONSENT_APPROVED -> MakoState.CONSENT_GRANTED;
|
||||||
|
case CONSENT_DENIED -> MakoState.REJECTED;
|
||||||
|
case TECHNICAL_FAILURE -> MakoState.ERROR;
|
||||||
|
default -> invalid(trigger);
|
||||||
|
};
|
||||||
|
|
||||||
|
case CONSENT_GRANTED -> switch (trigger) {
|
||||||
|
case TOPOLOGY_VALID -> MakoState.ACTIVE;
|
||||||
|
case TOPOLOGY_INVALID -> MakoState.REJECTED;
|
||||||
|
case TECHNICAL_FAILURE -> MakoState.ERROR;
|
||||||
|
default -> invalid(trigger);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Aus einem Fehlerzustand heraus kann man den Prozess z.B. neu starten
|
||||||
|
case ERROR ->
|
||||||
|
trigger == MakoTrigger.SEND_REQUEST ? MakoState.WAITING_FOR_CONSENT : invalid(trigger);
|
||||||
|
|
||||||
|
// Endzustände (ACTIVE, REJECTED) erlauben standardmäßig keine weiteren Trigger
|
||||||
|
default -> invalid(trigger);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private MakoState invalid(MakoTrigger trigger) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format("Ungültiger EDA-Zustandsübergang: Von Status '%s' mit Trigger '%s' nicht erlaubt.",
|
||||||
|
this.makoState, trigger)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package at.mueller.eeg.backend.community.domain.state;
|
||||||
|
|
||||||
|
public enum MakoState {
|
||||||
|
NEW, // Neu angelegt, noch nichts passiert
|
||||||
|
WAITING_FOR_CONSENT, // CMRequest an EDA gesendet, warten auf Kunden
|
||||||
|
CONSENT_GRANTED, // Kunde hat beim Netzbetreiber zugestimmt
|
||||||
|
ACTIVE, // Topologie passt, Zählpunkt ist bereit für die EEG
|
||||||
|
REJECTED, // Kunde hat abgelehnt oder Zählpunkt passt nicht
|
||||||
|
ERROR // Technischer Fehler (Timeout, falsche AT-Nummer)
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package at.mueller.eeg.backend.community.domain.state;
|
||||||
|
|
||||||
|
public enum MakoTrigger {
|
||||||
|
SEND_REQUEST, // Wir schicken den Consent-Request raus
|
||||||
|
CONSENT_APPROVED, // Netzbetreiber meldet Zustimmung
|
||||||
|
CONSENT_DENIED, // Netzbetreiber meldet Ablehnung
|
||||||
|
TOPOLOGY_VALID, // Topologie-Abfrage war erfolgreich und passend
|
||||||
|
TOPOLOGY_INVALID, // Topologie passt nicht zur EEG
|
||||||
|
TECHNICAL_FAILURE // EDA-Schnittstelle liefert Fehler
|
||||||
|
}
|
||||||
@ -1,24 +1,35 @@
|
|||||||
package at.mueller.eeg.backend.community.event.listener;
|
package at.mueller.eeg.backend.community.event.listener;
|
||||||
|
|
||||||
import at.mueller.eeg.backend.common.config.event.EdaTopologyCheckEvent;
|
import at.mueller.eeg.backend.common.event.EdaTopologyCheckEvent;
|
||||||
import at.mueller.eeg.backend.common.config.event.UserRegisteredEvent;
|
import at.mueller.eeg.backend.common.event.EdaTopologyCompletedEvent;
|
||||||
|
import at.mueller.eeg.backend.common.event.EdaTopologyFailedEvent;
|
||||||
|
import at.mueller.eeg.backend.common.event.UserRegisteredEvent;
|
||||||
import at.mueller.eeg.backend.community.domain.MeteringPoint;
|
import at.mueller.eeg.backend.community.domain.MeteringPoint;
|
||||||
import at.mueller.eeg.backend.community.domain.PointType;
|
import at.mueller.eeg.backend.community.domain.PointType;
|
||||||
|
import at.mueller.eeg.backend.community.domain.state.MakoTrigger;
|
||||||
import at.mueller.eeg.backend.community.repository.MeteringPointRepository;
|
import at.mueller.eeg.backend.community.repository.MeteringPointRepository;
|
||||||
|
import at.mueller.eeg.backend.mako.service.EdaTopologyService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.event.TransactionPhase;
|
import org.springframework.transaction.event.TransactionPhase;
|
||||||
import org.springframework.transaction.event.TransactionalEventListener;
|
import org.springframework.transaction.event.TransactionalEventListener;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class MeteringPointEventListener {
|
public class MeteringPointEventListener {
|
||||||
|
|
||||||
private final MeteringPointRepository meteringPointRepository;
|
private final MeteringPointRepository meteringPointRepository;
|
||||||
private final ApplicationEventPublisher eventPublisher;
|
private final ApplicationEventPublisher eventPublisher;
|
||||||
|
private final EdaTopologyService edaTopologyService;
|
||||||
|
|
||||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||||
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
public void handleUserRegistered(UserRegisteredEvent event) {
|
public void handleUserRegistered(UserRegisteredEvent event) {
|
||||||
MeteringPoint point = new MeteringPoint();
|
MeteringPoint point = new MeteringPoint();
|
||||||
point.setUserId(event.userId());
|
point.setUserId(event.userId());
|
||||||
@ -26,6 +37,42 @@ public class MeteringPointEventListener {
|
|||||||
point.setType(PointType.CONSUMER);
|
point.setType(PointType.CONSUMER);
|
||||||
|
|
||||||
meteringPointRepository.save(point);
|
meteringPointRepository.save(point);
|
||||||
eventPublisher.publishEvent(new EdaTopologyCheckEvent(point.getId(), event.atNumber()));
|
eventPublisher.publishEvent(new EdaTopologyCheckEvent(event.userId(), point.getId(), event.atNumber()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
@Transactional
|
||||||
|
public void handleEdaTopologyCompleted(EdaTopologyCompletedEvent event) {
|
||||||
|
meteringPointRepository.findById(event.meteringPointId()).ifPresent(point -> {
|
||||||
|
var data = event.topologyData();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (data.isEligible()) {
|
||||||
|
point.fireTrigger(MakoTrigger.TOPOLOGY_VALID);
|
||||||
|
|
||||||
|
point.setGridOperatorId(data.gridOperatorName());
|
||||||
|
point.setGridLevel(data.gridLevel());
|
||||||
|
point.setSubstationId(data.substationId());
|
||||||
|
point.setTransformerId(data.transformerId());
|
||||||
|
} else {
|
||||||
|
point.fireTrigger(MakoTrigger.TOPOLOGY_INVALID);
|
||||||
|
}
|
||||||
|
|
||||||
|
meteringPointRepository.save(point);
|
||||||
|
log.info("Zählpunkt {} ist nun im Status: {}", point.getId(), point.getMakoState());
|
||||||
|
|
||||||
|
} catch (IllegalStateException ex) {
|
||||||
|
log.error("Fehler beim Verarbeiten der EDA-Antwort: {}", ex.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
@Transactional
|
||||||
|
public void handleEdaTopologyFailed(EdaTopologyFailedEvent event) {
|
||||||
|
meteringPointRepository.findById(event.meteringPointId()).ifPresent(point -> {
|
||||||
|
point.fireTrigger(MakoTrigger.TECHNICAL_FAILURE);
|
||||||
|
meteringPointRepository.save(point);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
package at.mueller.eeg.backend.community.repository;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.community.domain.EnergyCommunity;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface EnergyCommunityRepository extends JpaRepository<EnergyCommunity, UUID> {
|
||||||
|
|
||||||
|
@Query("""
|
||||||
|
SELECT c FROM EnergyCommunity c
|
||||||
|
WHERE c.type = 'BEG'
|
||||||
|
OR (c.type = 'EEG_REGIONAL' AND c.substationId = :substationId AND c.gridOperatorId = :gridOperatorId)
|
||||||
|
OR (c.type = 'EEG_LOKAL' AND c.transformerId = :transformerId AND c.gridOperatorId = :gridOperatorId)
|
||||||
|
""")
|
||||||
|
List<EnergyCommunity> findEligibleCommunities(
|
||||||
|
@Param("substationId") String substationId,
|
||||||
|
@Param("transformerId") String transformerId,
|
||||||
|
@Param("gridOperatorId") String gridOperatorId
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
package at.mueller.eeg.backend.community.repository;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.community.domain.Membership;
|
||||||
|
import at.mueller.eeg.backend.community.domain.MembershipStatus;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface MembershipRepository extends JpaRepository<Membership, UUID> {
|
||||||
|
|
||||||
|
boolean existsByMeteringPointIdAndEnergyCommunityId(UUID meteringPointId, UUID energyCommunityId);
|
||||||
|
|
||||||
|
List<Membership> findByMeteringPointId(UUID meteringPointId);
|
||||||
|
|
||||||
|
boolean existsByMeteringPointIdAndPriorityLevelAndStatusIn(
|
||||||
|
UUID meteringPointId,
|
||||||
|
Integer priorityLevel,
|
||||||
|
List<MembershipStatus> statuses
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -3,5 +3,7 @@ package at.mueller.eeg.backend.community.repository;
|
|||||||
import at.mueller.eeg.backend.community.domain.MeteringPoint;
|
import at.mueller.eeg.backend.community.domain.MeteringPoint;
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
public interface MeteringPointRepository extends JpaRepository<MeteringPoint, String> {
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public interface MeteringPointRepository extends JpaRepository<MeteringPoint, UUID> {
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,8 +42,8 @@ public interface IamDtos {
|
|||||||
String firstName,
|
String firstName,
|
||||||
String lastName,
|
String lastName,
|
||||||
String email,
|
String email,
|
||||||
String status, // PENDING, APPROVED, REJECTED
|
String status,
|
||||||
String primaryAtNumber // Zur schnellen Anzeige in der Liste
|
String primaryAtNumber
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
record ErrorResponse(String message, int status) {}
|
record ErrorResponse(String message, int status) {}
|
||||||
|
|||||||
@ -49,39 +49,22 @@ public class SecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// 1. CSRF deaktivieren: Da wir stateless mit JWTs arbeiten und keine
|
|
||||||
// klassischen Session-Cookies verwenden, ist CSRF-Schutz hier nicht nötig.
|
|
||||||
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
|
||||||
.csrf(AbstractHttpConfigurer::disable)
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
|
||||||
// 2. Stateless Session Management: Spring Security darf keine HTTP-Session anlegen
|
|
||||||
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
|
||||||
// 3. Routing & Autorisierung
|
|
||||||
.authorizeHttpRequests(auth -> auth
|
.authorizeHttpRequests(auth -> auth
|
||||||
// Erlaubt interne Forwards (wichtig für deinen SpaRoutingController -> /index.html)
|
|
||||||
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
|
.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll()
|
||||||
|
|
||||||
// Erlaubt den Zugriff auf statische Frontend-Dateien im Root
|
|
||||||
.requestMatchers("/", "/index.html", "/assets/**", "/static/**", "/*.js", "/*.css", "/*.ico").permitAll()
|
.requestMatchers("/", "/index.html", "/assets/**", "/static/**", "/*.js", "/*.css", "/*.ico").permitAll()
|
||||||
|
|
||||||
// OpenAPI / Swagger Docs für das Frontend-Tooling freigeben
|
|
||||||
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
.requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||||
|
|
||||||
// Deine öffentlichen API-Endpunkte
|
|
||||||
.requestMatchers("/api/auth/**").permitAll()
|
.requestMatchers("/api/auth/**").permitAll()
|
||||||
|
.requestMatchers(
|
||||||
// Admin-Routen auf URL-Ebene absichern (zusätzlich zur @PreAuthorize Annotation)
|
"/api/community/admin/**",
|
||||||
.requestMatchers("/api/admin/**").hasRole("ADMIN")
|
"/api/iam/admin/**",
|
||||||
|
"/api/mako/admin/**"
|
||||||
// Alle anderen /api/ Anfragen erfordern zumindest einen gültigen Login
|
).hasRole("ADMIN")
|
||||||
.requestMatchers("/api/**").authenticated()
|
.requestMatchers("/api/**").authenticated()
|
||||||
|
|
||||||
// Alles andere (SPA-Routen, die nicht von obigen Matches gefangen wurden) freigeben
|
|
||||||
.anyRequest().permitAll()
|
.anyRequest().permitAll()
|
||||||
)
|
)
|
||||||
|
|
||||||
// 4. JWT als Authentifizierungsmethode aktivieren
|
|
||||||
.oauth2ResourceServer(oauth2 -> oauth2
|
.oauth2ResourceServer(oauth2 -> oauth2
|
||||||
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
|
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
|
||||||
);
|
);
|
||||||
@ -110,20 +93,10 @@ public class SecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
public CorsConfigurationSource corsConfigurationSource() {
|
public CorsConfigurationSource corsConfigurationSource() {
|
||||||
CorsConfiguration configuration = new CorsConfiguration();
|
CorsConfiguration configuration = new CorsConfiguration();
|
||||||
|
|
||||||
// Erlaube exakt dein Angular-Frontend
|
|
||||||
configuration.setAllowedOrigins(List.of("http://localhost:4200"));
|
configuration.setAllowedOrigins(List.of("http://localhost:4200"));
|
||||||
|
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
|
||||||
// Erlaube die gängigen HTTP-Methoden
|
|
||||||
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
|
|
||||||
|
|
||||||
// Erlaube Header, die Angular mitsendet (besonders wichtig für Content-Type und später Authorization/Token)
|
|
||||||
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
|
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
|
||||||
|
|
||||||
// Wichtig, wenn Anmeldedaten (Credentials) oder Cookies im Spiel sind
|
|
||||||
configuration.setAllowCredentials(true);
|
configuration.setAllowCredentials(true);
|
||||||
|
|
||||||
// Wende diese Regeln auf alle Endpunkte (/**) an
|
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
source.registerCorsConfiguration("/**", configuration);
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
package at.mueller.eeg.backend.iam.service;
|
package at.mueller.eeg.backend.iam.service;
|
||||||
|
|
||||||
import at.mueller.eeg.backend.common.config.event.UserRegisteredEvent;
|
import at.mueller.eeg.backend.common.event.UserRegisteredEvent;
|
||||||
import at.mueller.eeg.backend.iam.domain.User;
|
import at.mueller.eeg.backend.iam.domain.User;
|
||||||
import at.mueller.eeg.backend.iam.mapper.UserMapper;
|
import at.mueller.eeg.backend.iam.mapper.UserMapper;
|
||||||
import at.mueller.eeg.backend.iam.repository.UserRepository;
|
import at.mueller.eeg.backend.iam.repository.UserRepository;
|
||||||
@ -16,6 +16,7 @@ import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
|||||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.temporal.ChronoUnit;
|
import java.time.temporal.ChronoUnit;
|
||||||
@ -53,6 +54,7 @@ public class AuthService {
|
|||||||
/**
|
/**
|
||||||
* Verarbeitet die Registrierung des neuen EEG-Mitglieds.
|
* Verarbeitet die Registrierung des neuen EEG-Mitglieds.
|
||||||
*/
|
*/
|
||||||
|
@Transactional
|
||||||
public void register(RegistrationRequest request) {
|
public void register(RegistrationRequest request) {
|
||||||
|
|
||||||
userRepository.findByEmail(request.email()).ifPresent(user -> {
|
userRepository.findByEmail(request.email()).ifPresent(user -> {
|
||||||
|
|||||||
@ -0,0 +1,11 @@
|
|||||||
|
package at.mueller.eeg.backend.mako.api.dto;
|
||||||
|
|
||||||
|
public record EdaTopologyResponse(
|
||||||
|
String atNumber,
|
||||||
|
boolean isEligible,
|
||||||
|
String gridOperatorName,
|
||||||
|
String gridLevel,
|
||||||
|
String substationId,
|
||||||
|
String transformerId
|
||||||
|
) {
|
||||||
|
}
|
||||||
@ -0,0 +1,54 @@
|
|||||||
|
package at.mueller.eeg.backend.mako.event.listener;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.common.event.EdaTopologyCheckEvent;
|
||||||
|
import at.mueller.eeg.backend.common.event.EdaTopologyCompletedEvent;
|
||||||
|
import at.mueller.eeg.backend.common.event.EdaTopologyFailedEvent;
|
||||||
|
import at.mueller.eeg.backend.mako.api.dto.EdaTopologyResponse;
|
||||||
|
import at.mueller.eeg.backend.mako.service.EdaTopologyService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.ApplicationEventPublisher;
|
||||||
|
import org.springframework.context.event.EventListener;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class EdaCommuncationEventListener {
|
||||||
|
|
||||||
|
private final EdaTopologyService edaTopologyService;
|
||||||
|
private final ApplicationEventPublisher eventPublisher;
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
@Async
|
||||||
|
public void handleEdaTopologyCheckEvent(EdaTopologyCheckEvent event) {
|
||||||
|
log.info("Asynchroner Topologie-Check gestartet für ID: {}", event.meteringPointId());
|
||||||
|
try {
|
||||||
|
EdaTopologyResponse edaResponse = edaTopologyService.checkTopology(event.atNumber());
|
||||||
|
|
||||||
|
eventPublisher.publishEvent(new EdaTopologyCompletedEvent(
|
||||||
|
event.userId(),
|
||||||
|
event.meteringPointId(),
|
||||||
|
edaResponse.isEligible(),
|
||||||
|
edaResponse.gridOperatorName(),
|
||||||
|
edaResponse.substationId(),
|
||||||
|
edaResponse.transformerId(),
|
||||||
|
edaResponse.gridLevel()
|
||||||
|
));
|
||||||
|
|
||||||
|
log.info("MeteringPoint erfolgreich mit EDA-Daten aktualisiert.");
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Fehler bei der EDA Topologie-Abfrage für AT-Nummer {}: {}",
|
||||||
|
event.atNumber(), e.getMessage());
|
||||||
|
|
||||||
|
eventPublisher.publishEvent(new EdaTopologyFailedEvent(
|
||||||
|
event.userId(),
|
||||||
|
event.meteringPointId(),
|
||||||
|
e.getMessage()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package at.mueller.eeg.backend.mako.service;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.mako.api.dto.EdaTopologyResponse;
|
||||||
|
|
||||||
|
public interface EdaTopologyService {
|
||||||
|
/**
|
||||||
|
* Führt die Topologieabfrage für einen Zählpunkt durch.
|
||||||
|
*/
|
||||||
|
EdaTopologyResponse checkTopology(String atNumber);
|
||||||
|
}
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
package at.mueller.eeg.backend.mako.service.impl;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.mako.api.dto.EdaTopologyResponse;
|
||||||
|
import at.mueller.eeg.backend.mako.service.EdaTopologyService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class SimulatedEdaTopologyService implements EdaTopologyService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EdaTopologyResponse checkTopology(String atNumber) {
|
||||||
|
log.info("Starte simulierte EDA-Topologieabfrage für Zählpunkt: {}", atNumber);
|
||||||
|
try {
|
||||||
|
TimeUnit.SECONDS.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new RuntimeException("EDA Simulation unterbrochen", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (atNumber == null || !atNumber.startsWith("AT")) {
|
||||||
|
throw new IllegalArgumentException("Ungültiges Zählpunkt-Format (muss mit AT beginnen)");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEligible = !atNumber.endsWith("999");
|
||||||
|
|
||||||
|
String operator = atNumber.contains("001000") ? "Wiener Netze GmbH" : "Netz NÖ GmbH";
|
||||||
|
|
||||||
|
log.info("Simulierte EDA-Abfrage abgeschlossen für: {}", atNumber);
|
||||||
|
|
||||||
|
return new EdaTopologyResponse(
|
||||||
|
atNumber,
|
||||||
|
isEligible,
|
||||||
|
operator,
|
||||||
|
"7",
|
||||||
|
"UW_WAP_21_STREBERSDORF",
|
||||||
|
"TRAFO_21_042_BISAMBERG"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user