eeg_portal/eeg_frontend/openapi.yaml
Bernhard Müller 5949359216 fix(tariff): fix critical security and correctness issues from code review
- Fix UserTariffController: use @CurrentUserId instead of broken extractUserId()
- Fix UserTariffController: use @PathVariable instead of @RequestParam for userId in delete
- Add @PreAuthorize annotations to all tariff endpoints (ADMIN for community, MEMBER for user)
- Add duplicate tariff prevention for (source, target) pairs
- Optimize membership check: replace N+1 query with efficient isActiveMemberOfCommunity()
- Update tests to match new membership check API
2026-07-22 10:11:12 +02:00

1163 lines
28 KiB
YAML

openapi: 3.1.0
info:
title: OpenAPI definition
version: v0
servers:
- url: http://localhost
description: Generated server url
tags:
- name: Authentication
description: Public Endpoints für Login und Registrierung
- name: User Profile
description: Profilverwaltung und Passwort-Reset
- name: Admin IAM
description: User-Verwaltung und Freigabe-Workflow
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/tariffs/{tariffId}:
put:
tags:
- user-tariff-controller
operationId: updateUserTariff
parameters:
- name: tariffId
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/UserTariffRequest"
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/UserTariffResponse"
delete:
tags:
- user-tariff-controller
operationId: deleteUserTariff
parameters:
- name: tariffId
in: path
required: true
schema:
type: string
format: uuid
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:
put:
tags:
- membership-controller
operationId: rejectMembership
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
/api/community/admin/memberships/{id}/approve:
put:
tags:
- membership-controller
operationId: approveMembership
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
/api/admin/energy-communities/{id}:
get:
tags:
- energy-community-admin-controller
operationId: getEnergyCommunityById
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/EnergyCommunityDto"
put:
tags:
- energy-community-admin-controller
operationId: updateEnergyCommunity
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/EnergyCommunityDto"
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/EnergyCommunityDto"
delete:
tags:
- energy-community-admin-controller
operationId: deleteEnergyCommunity
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
/api/admin/energy-communities/{communityId}/tariff:
get:
tags:
- tariff-controller
operationId: getCommunityTariff
parameters:
- name: communityId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/CommunityTariffResponse"
put:
tags:
- tariff-controller
operationId: createOrUpdateCommunityTariff
parameters:
- name: communityId
in: path
required: true
schema:
type: string
format: uuid
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/CommunityTariffRequest"
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/CommunityTariffResponse"
/api/tariffs:
post:
tags:
- user-tariff-controller
operationId: createUserTariff
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/UserTariffRequest"
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/UserTariffResponse"
/api/community/metering-points:
get:
tags:
- metering-point-controller
operationId: getAllMeteringPoints
responses:
"200":
description: OK
content:
'*/*':
schema:
type: array
items:
$ref: "#/components/schemas/MeteringPointResponse"
post:
tags:
- metering-point-controller
operationId: addOwnMeteringPoint
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/CreateMeteringPointRequest"
required: true
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: "#/components/schemas/MeteringPointResponse"
/api/community/memberships/request:
post:
tags:
- membership-controller
operationId: requestMembership
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/MembershipRequest"
required: true
responses:
"200":
description: OK
content:
'*/*':
schema:
type: string
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:
post:
tags:
- Authentication
summary: Registriert einen neuen User inkl. Zählpunkt
description: Setzt den User auf Status PENDING und versendet eine Verifizierungs-E-Mail.
operationId: register
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/RegistrationRequest"
required: true
responses:
"200":
description: OK
/api/auth/login:
post:
tags:
- Authentication
summary: User Login
description: "Gibt einen JWT zurück. Schlägt fehl, wenn Status noch PENDING\
\ ist."
operationId: login
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/LoginRequest"
required: true
responses:
"200":
description: OK
content:
'*/*':
schema:
$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:
post:
tags:
- Admin IAM
summary: User ablehnen
description: Lehnt den Antrag ab (z.B. weil Zählpunkt nicht ins Netz passt).
operationId: rejectUser
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
/api/admin/users/{userId}/approve:
post:
tags:
- Admin IAM
summary: User freigeben
description: "Setzt enabled=true, status=APPROVED und triggert die finale EDA-Anmeldung."
operationId: approveUser
parameters:
- name: userId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
/api/admin/energy-communities:
get:
tags:
- energy-community-admin-controller
operationId: getAllEnergyCommunities
responses:
"200":
description: Liste der wartenden Benutzer erfolgreich geladen
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/EnergyCommunityDto"
post:
tags:
- energy-community-admin-controller
operationId: createEnergyCommunity
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/EnergyCommunityDto"
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/EnergyCommunityDto"
/api/tariffs/community/{communityId}:
get:
tags:
- user-tariff-controller
operationId: getUserTariffsForCommunity
parameters:
- name: communityId
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/UserTariffResponse"
/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:
get:
tags:
- dashboard-controller
operationId: getUserDashboard
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: "#/components/schemas/UserStats"
/api/dashboard/admin:
get:
tags:
- dashboard-controller
operationId: getAdminDashboard
responses:
"200":
description: OK
content:
'*/*':
schema:
$ref: "#/components/schemas/AdminStats"
/api/community/metering-points/{id}/eligible-communities:
get:
tags:
- membership-controller
operationId: getEligibleCommunities
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
"200":
description: OK
content:
'*/*':
schema:
type: array
items:
$ref: "#/components/schemas/EligibleCommunityResponse"
/api/community/metering-points/me:
get:
tags:
- metering-point-controller
operationId: getOwnMeteringPoints
responses:
"200":
description: OK
content:
'*/*':
schema:
type: array
items:
$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:
get:
tags:
- Authentication
operationId: verifyEmail
parameters:
- name: token
in: query
required: true
schema:
type: string
responses:
"200":
description: OK
/api/admin/users/pending:
get:
tags:
- Admin IAM
summary: Lädt alle wartenden User
description: Zeigt User im Status PENDING inkl. der Ergebnisse des EDA-Netz-Checks.
operationId: getPendingUsers
responses:
"200":
description: Liste der wartenden Benutzer erfolgreich geladen
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/UserProfileResponse"
components:
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
UserTariffRequest:
type: object
properties:
energyCommunityId:
type: string
format: uuid
sourceMeteringPointId:
type: string
format: uuid
targetMeteringPointId:
type: string
format: uuid
pricePerKwhCents:
type: number
minimum: 0.0001
validFrom:
type: string
format: date
validTo:
type: string
format: date
required:
- energyCommunityId
- pricePerKwhCents
- sourceMeteringPointId
- targetMeteringPointId
UserTariffResponse:
type: object
properties:
id:
type: string
format: uuid
energyCommunityId:
type: string
format: uuid
sourceMeteringPointId:
type: string
format: uuid
targetMeteringPointId:
type: string
format: uuid
pricePerKwhCents:
type: number
validFrom:
type: string
format: date
validTo:
type: string
format: date
createdAt:
type: string
format: date-time
UpdateMeteringPointRequest:
type: object
properties:
type:
type: string
enum:
- CONSUMER
- PRODUCER
- PROSUMER
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
- PROSUMER
makoState:
type: string
enum:
- NEW
- WAITING_FOR_CONSENT
- CONSENT_GRANTED
- ACTIVE
- REJECTED
- ERROR
gridOperatorId:
type: string
ownerEmail:
type: string
EnergyCommunityDto:
type: object
properties:
id:
type: string
format: uuid
edaEcId:
type: string
type:
type: string
enum:
- EEG_LOKAL
- EEG_REGIONAL
- BEG
name:
type: string
gridOperatorId:
type: string
substationId:
type: string
transformerId:
type: string
memberCount:
type: integer
format: int32
CommunityTariffRequest:
type: object
properties:
maxPricePerKwhCents:
type: number
minimum: 0.0001
surchargePercent:
type: number
maximum: 100
minimum: 0
validFrom:
type: string
format: date
validTo:
type: string
format: date
required:
- maxPricePerKwhCents
- surchargePercent
CommunityTariffResponse:
type: object
properties:
id:
type: string
format: uuid
energyCommunityId:
type: string
format: uuid
maxPricePerKwhCents:
type: number
surchargePercent:
type: number
validFrom:
type: string
format: date
validTo:
type: string
format: date
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
CreateMeteringPointRequest:
type: object
properties:
atNumber:
type: string
pattern: "^AT[0-9]{31}$"
type:
type: string
enum:
- CONSUMER
- PRODUCER
- PROSUMER
required:
- atNumber
- type
MembershipRequest:
type: object
properties:
meteringPointId:
type: string
format: uuid
energyCommunityId:
type: string
format: uuid
priorityLevel:
type: integer
format: int32
maximum: 5
minimum: 1
required:
- energyCommunityId
- meteringPointId
- 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:
type: object
description: Payload für die Registrierung eines neuen Users
properties:
participantType:
type: string
description: Privatperson oder juristische Person (Firma/Verein/Gemeinde)
enum:
- PRIVATE
- COMPANY
example: PRIVATE
firstName:
type: string
description: "Vorname (bei juristischer Person: Vorname der Ansprechperson)"
example: Max
minLength: 1
lastName:
type: string
description: "Nachname (bei juristischer Person: Nachname der Ansprechperson)"
example: Mustermann
minLength: 1
organizationName:
type: string
description: "Nur bei juristischer Person: Firmen-, Vereins- oder Gemeindename"
example: Musterverein
email:
type: string
format: email
example: max@example.com
minLength: 1
password:
type: string
example: SecurePass123!
maxLength: 2147483647
minLength: 8
required:
- email
- firstName
- lastName
- participantType
- password
LoginRequest:
type: object
description: Payload für den Login
properties:
email:
type: string
format: email
example: max@example.com
minLength: 1
password:
type: string
example: SecurePass123!
minLength: 1
required:
- email
- password
AuthResponse:
type: object
description: Antwort nach erfolgreichem Login
properties:
token:
type: string
description: JWT Bearer Token
status:
type: string
description: Status des Users
example: PENDING
role:
type: string
description: Rolle des Users
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:
type: object
properties:
totalMeteringPoints:
type: integer
format: int64
activeMemberships:
type: integer
format: int64
pendingMemberships:
type: integer
format: int64
AdminStats:
type: object
properties:
totalCommunities:
type: integer
format: int64
pendingUsers:
type: integer
format: int64
totalActiveMemberships:
type: integer
format: int64
EligibleCommunityResponse:
type: object
properties:
id:
type: string
format: uuid
name:
type: string
type:
type: string
enum:
- EEG_LOKAL
- EEG_REGIONAL
- BEG
edaEcId:
type: string
MembershipResponse:
type: object
properties:
id:
type: string
format: uuid
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
firstName:
type: string
lastName:
type: string
atNumber:
type: string
communityName:
type: string
priorityLevel:
type: integer
format: int32
status:
type: string