fix(mako): fix findPendingWithDetails query and OpenApiGeneratorTest

- Remove invalid JOIN FETCH mp.user from MembershipRepository query
  (MeteringPoint has no @ManyToOne User field, only UUID userId)
- Fix OpenApiGeneratorTest: disable security filters for MockMvc,
  use UTF-8 for getContentAsString and Files.writeString
This commit is contained in:
Bernhard Müller 2026-07-22 07:41:52 +02:00
parent b0145f3b30
commit caf282cb1e
3 changed files with 431 additions and 50 deletions

View File

@ -34,6 +34,6 @@ public interface MembershipRepository extends JpaRepository<Membership, UUID> {
@Query("SELECT m FROM Membership m JOIN FETCH m.meteringPoint mp JOIN FETCH m.energyCommunity ec WHERE mp.userId = :userId") @Query("SELECT m FROM Membership m JOIN FETCH m.meteringPoint mp JOIN FETCH m.energyCommunity ec WHERE mp.userId = :userId")
List<Membership> findByUserIdWithDetails(@Param("userId") UUID userId); List<Membership> findByUserIdWithDetails(@Param("userId") UUID userId);
@Query("SELECT m FROM Membership m JOIN FETCH m.meteringPoint mp JOIN FETCH m.energyCommunity ec JOIN FETCH mp.user u WHERE m.status = 'PENDING'") @Query("SELECT m FROM Membership m JOIN FETCH m.meteringPoint mp JOIN FETCH m.energyCommunity ec WHERE m.status = 'PENDING'")
List<Membership> findPendingWithDetails(); List<Membership> findPendingWithDetails();
} }

View File

@ -2,10 +2,11 @@ package at.mueller.eeg.backend;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc; import org.springframework.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -14,7 +15,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@SpringBootTest @SpringBootTest
@AutoConfigureMockMvc @AutoConfigureMockMvc(addFilters = false)
class OpenApiGeneratorTest { class OpenApiGeneratorTest {
@Autowired @Autowired
@ -26,11 +27,11 @@ class OpenApiGeneratorTest {
.andExpect(status().isOk()) .andExpect(status().isOk())
.andReturn() .andReturn()
.getResponse() .getResponse()
.getContentAsString(); .getContentAsString(StandardCharsets.UTF_8);
Path outputPath = Paths.get("../eeg_frontend/openapi.yaml"); Path outputPath = Paths.get("../eeg_frontend/openapi.yaml");
Files.writeString(outputPath, openApiYaml); Files.writeString(outputPath, openApiYaml, StandardCharsets.UTF_8);
System.out.println("OpenAPI YAML erfolgreich nach " + outputPath.toAbsolutePath() + " exportiert."); System.out.println("OpenAPI YAML erfolgreich nach " + outputPath.toAbsolutePath() + " exportiert.");
} }

View File

@ -7,10 +7,135 @@ servers:
description: Generated server url description: Generated server url
tags: tags:
- name: Authentication - name: Authentication
description: Public Endpoints für Login und Registrierung description: Public Endpoints für Login und Registrierung
- name: User Profile
description: Profilverwaltung und Passwort-Reset
- name: Admin IAM - name: Admin IAM
description: User-Verwaltung und Freigabe-Workflow description: User-Verwaltung und Freigabe-Workflow
paths: paths:
/api/users/me:
get:
tags:
- User Profile
summary: Eigenes Profil abrufen
operationId: getOwnProfile
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: "#/components/schemas/UserProfileResponse"
put:
tags:
- User Profile
summary: Profil aktualisieren
operationId: updateOwnProfile
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateProfileRequest"
required: true
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: "#/components/schemas/UserProfileResponse"
/api/users/me/password:
put:
tags:
- User Profile
summary: Passwort ändern
operationId: changePassword
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ChangePasswordRequest"
required: true
responses:
"200":
description: OK
/api/users/me/email:
put:
tags:
- User Profile
summary: E-Mail ändern (mit erneuter Verifizierung)
operationId: changeEmail
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ChangeEmailRequest"
required: true
responses:
"200":
description: OK
/api/notifications/{id}/read:
put:
tags:
- notification-controller
operationId: markAsRead
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
/api/notifications/read-all:
put:
tags:
- notification-controller
operationId: markAllAsRead
responses:
"200":
description: OK
/api/community/metering-points/{id}:
put:
tags:
- metering-point-controller
operationId: updateOwnMeteringPoint
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/UpdateMeteringPointRequest"
required: true
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: "#/components/schemas/MeteringPointResponse"
delete:
tags:
- metering-point-controller
operationId: deleteOwnMeteringPoint
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
/api/community/admin/memberships/{id}/reject: /api/community/admin/memberships/{id}/reject:
put: put:
tags: tags:
@ -148,11 +273,26 @@ paths:
schema: schema:
type: string type: string
format: uuid format: uuid
/api/auth/reset-password:
post:
tags:
- User Profile
summary: Passwort zurücksetzen
operationId: resetPassword
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ResetPasswordRequest"
required: true
responses:
"200":
description: OK
/api/auth/register: /api/auth/register:
post: post:
tags: tags:
- Authentication - Authentication
summary: Registriert einen neuen User inkl. Zählpunkt summary: Registriert einen neuen User inkl. Zählpunkt
description: Setzt den User auf Status PENDING und versendet eine Verifizierungs-E-Mail. description: Setzt den User auf Status PENDING und versendet eine Verifizierungs-E-Mail.
operationId: register operationId: register
requestBody: requestBody:
@ -169,7 +309,7 @@ paths:
tags: tags:
- Authentication - Authentication
summary: User Login summary: User Login
description: "Gibt einen JWT zurück. Schlägt fehl, wenn Status noch PENDING\ description: "Gibt einen JWT zurück. Schlägt fehl, wenn Status noch PENDING\
\ ist." \ ist."
operationId: login operationId: login
requestBody: requestBody:
@ -185,12 +325,27 @@ paths:
'*/*': '*/*':
schema: schema:
$ref: "#/components/schemas/AuthResponse" $ref: "#/components/schemas/AuthResponse"
/api/auth/forgot-password:
post:
tags:
- User Profile
summary: Passwort-Reset anfordern
operationId: forgotPassword
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/ForgotPasswordRequest"
required: true
responses:
"200":
description: OK
/api/admin/users/{userId}/reject: /api/admin/users/{userId}/reject:
post: post:
tags: tags:
- Admin IAM - Admin IAM
summary: User ablehnen summary: User ablehnen
description: Lehnt den Antrag ab (z.B. weil Zählpunkt nicht ins Netz passt). description: Lehnt den Antrag ab (z.B. weil Zählpunkt nicht ins Netz passt).
operationId: rejectUser operationId: rejectUser
parameters: parameters:
- name: userId - name: userId
@ -250,6 +405,33 @@ paths:
application/json: application/json:
schema: schema:
$ref: "#/components/schemas/EnergyCommunityDto" $ref: "#/components/schemas/EnergyCommunityDto"
/api/notifications:
get:
tags:
- notification-controller
operationId: getNotifications
responses:
"200":
description: OK
content:
'*/*':
schema:
type: array
items:
$ref: "#/components/schemas/Notification"
/api/notifications/unread:
get:
tags:
- notification-controller
operationId: getUnreadCount
responses:
"200":
description: OK
content:
'*/*':
schema:
type: integer
format: int64
/api/dashboard/user: /api/dashboard/user:
get: get:
tags: tags:
@ -309,6 +491,34 @@ paths:
type: array type: array
items: items:
$ref: "#/components/schemas/MeteringPointResponse" $ref: "#/components/schemas/MeteringPointResponse"
/api/community/memberships/me:
get:
tags:
- membership-controller
operationId: getOwnMemberships
responses:
"200":
description: OK
content:
'*/*':
schema:
type: array
items:
$ref: "#/components/schemas/MembershipResponse"
/api/community/admin/memberships/pending:
get:
tags:
- membership-controller
operationId: getPendingMemberships
responses:
"200":
description: OK
content:
'*/*':
schema:
type: array
items:
$ref: "#/components/schemas/PendingMembershipResponse"
/api/auth/verify-email: /api/auth/verify-email:
get: get:
tags: tags:
@ -327,7 +537,7 @@ paths:
get: get:
tags: tags:
- Admin IAM - Admin IAM
summary: Lädt alle wartenden User summary: Lädt alle wartenden User
description: Zeigt User im Status PENDING inkl. der Ergebnisse des EDA-Netz-Checks. description: Zeigt User im Status PENDING inkl. der Ergebnisse des EDA-Netz-Checks.
operationId: getPendingUsers operationId: getPendingUsers
responses: responses:
@ -341,6 +551,126 @@ paths:
$ref: "#/components/schemas/UserProfileResponse" $ref: "#/components/schemas/UserProfileResponse"
components: components:
schemas: schemas:
UpdateProfileRequest:
type: object
description: Payload für die Profilaktualisierung
properties:
firstName:
type: string
description: Vorname
example: Max
minLength: 1
lastName:
type: string
description: Nachname
example: Mustermann
minLength: 1
organizationName:
type: string
description: "Nur bei juristischer Person: Firmen-, Vereins- oder Gemeindename"
participantType:
type: string
description: Privatperson oder juristische Person
enum:
- PRIVATE
- COMPANY
example: PRIVATE
required:
- firstName
- lastName
- participantType
UserProfileResponse:
type: object
description: Repräsentation eines Users für das Admin-Dashboard
properties:
id:
type: string
format: uuid
participantType:
type: string
enum:
- PRIVATE
- COMPANY
firstName:
type: string
lastName:
type: string
organizationName:
type: string
email:
type: string
status:
type: string
ChangePasswordRequest:
type: object
description: Payload für die Passwortänderung
properties:
currentPassword:
type: string
description: Aktuelles Passwort
minLength: 1
newPassword:
type: string
description: Neues Passwort
example: NeuesPasswort123!
maxLength: 2147483647
minLength: 8
required:
- currentPassword
- newPassword
ChangeEmailRequest:
type: object
description: Payload für die E-Mail-Änderung
properties:
newEmail:
type: string
format: email
description: Neue E-Mail-Adresse
example: neu@example.com
minLength: 1
required:
- newEmail
UpdateMeteringPointRequest:
type: object
properties:
type:
type: string
enum:
- CONSUMER
- PRODUCER
- PROSUME
required:
- type
MeteringPointResponse:
type: object
properties:
id:
type: string
format: uuid
userId:
type: string
format: uuid
atNumber:
type: string
type:
type: string
enum:
- CONSUMER
- PRODUCER
- PROSUME
makoState:
type: string
enum:
- NEW
- WAITING_FOR_CONSENT
- CONSENT_GRANTED
- ACTIVE
- REJECTED
- ERROR
gridOperatorId:
type: string
ownerEmail:
type: string
EnergyCommunityDto: EnergyCommunityDto:
type: object type: object
properties: properties:
@ -381,36 +711,6 @@ components:
required: required:
- atNumber - atNumber
- type - type
MeteringPointResponse:
type: object
properties:
id:
type: string
format: uuid
userId:
type: string
format: uuid
atNumber:
type: string
type:
type: string
enum:
- CONSUMER
- PRODUCER
- PROSUME
makoState:
type: string
enum:
- NEW
- WAITING_FOR_CONSENT
- CONSENT_GRANTED
- ACTIVE
- REJECTED
- ERROR
gridOperatorId:
type: string
ownerEmail:
type: string
MembershipRequest: MembershipRequest:
type: object type: object
properties: properties:
@ -429,9 +729,26 @@ components:
- energyCommunityId - energyCommunityId
- meteringPointId - meteringPointId
- priorityLevel - priorityLevel
ResetPasswordRequest:
type: object
description: Payload für Passwort zurücksetzen
properties:
token:
type: string
description: Reset-Token
minLength: 1
newPassword:
type: string
description: Neues Passwort
example: NeuesPasswort123!
maxLength: 2147483647
minLength: 8
required:
- newPassword
- token
RegistrationRequest: RegistrationRequest:
type: object type: object
description: Payload für die Registrierung eines neuen Users description: Payload für die Registrierung eines neuen Users
properties: properties:
participantType: participantType:
type: string type: string
@ -472,7 +789,7 @@ components:
- password - password
LoginRequest: LoginRequest:
type: object type: object
description: Payload für den Login description: Payload für den Login
properties: properties:
email: email:
type: string type: string
@ -501,6 +818,43 @@ components:
type: string type: string
description: Rolle des Users description: Rolle des Users
example: USER example: USER
ForgotPasswordRequest:
type: object
description: Payload für Passwort-Reset anfordern
properties:
email:
type: string
format: email
description: E-Mail-Adresse
example: max@example.com
minLength: 1
required:
- email
Notification:
type: object
properties:
id:
type: string
format: uuid
userId:
type: string
format: uuid
title:
type: string
message:
type: string
type:
type: string
enum:
- INFO
- SUCCESS
- WARNING
- ERROR
read:
type: boolean
createdAt:
type: string
format: date-time
UserStats: UserStats:
type: object type: object
properties: properties:
@ -541,25 +895,51 @@ components:
- BEG - BEG
edaEcId: edaEcId:
type: string type: string
UserProfileResponse: MembershipResponse:
type: object type: object
description: Repräsentation eines Users für das Admin-Dashboard
properties: properties:
id: id:
type: string type: string
format: uuid format: uuid
participantType: meteringPointId:
type: string
format: uuid
atNumber:
type: string
energyCommunityId:
type: string
format: uuid
communityName:
type: string
priorityLevel:
type: integer
format: int32
status:
type: string
validFrom:
type: string
format: date
validTo:
type: string
format: date
PendingMembershipResponse:
type: object
properties:
id:
type: string
format: uuid
userEmail:
type: string type: string
enum:
- PRIVATE
- COMPANY
firstName: firstName:
type: string type: string
lastName: lastName:
type: string type: string
organizationName: atNumber:
type: string type: string
email: communityName:
type: string type: string
priorityLevel:
type: integer
format: int32
status: status:
type: string type: string