initial backend: Dokumente, Historie, API und Persistenz (D76)

- Liquibase-Schema (`document`, `document_history`) + Rollback-scripts
- Spring Boot with JPA-repositories, entities and services
- REST-API (`/documents`, `/documents/{id}`, `/documents/{id}/history`)
- OpenAPI-specifikation for CRUD-Operationen and history
- config files (`application.yaml`, `Liquibase`, H2 im PostgreSQL-Modus)
- preps for future live-editing/delta-updates
- Exceptions for conflikt- and not-found cases (409/404)
- keeping document hostory even after `delete` for RESTORE functionality
This commit is contained in:
mhoennig
2026-08-26 12:56:27 +02:00
parent 4e0ce51820
commit 0446e3d81e
31 changed files with 2060 additions and 7 deletions
@@ -0,0 +1,11 @@
package com.example.editor
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class EditorBackendApplication
fun main(args: Array<String>) {
runApplication<EditorBackendApplication>(*args)
}
@@ -0,0 +1,88 @@
package com.example.editor.api
import com.example.editor.generated.api.DocumentsApi
import com.example.editor.generated.model.Document as ApiDocument
import com.example.editor.generated.model.DocumentCreateRequest
import com.example.editor.generated.model.DocumentHistoryEntry as ApiHistoryEntry
import com.example.editor.generated.model.DocumentUpdateRequest
import com.example.editor.generated.model.RestoreRequest
import com.example.editor.domain.Document
import com.example.editor.domain.DocumentHistoryEntry
import com.example.editor.service.DocumentService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.util.UUID
/**
* Implementiert das aus der OpenAPI-Spezifikation generierte Interface.
* Aendert sich die Spezifikation, schlaegt hier der Compile fehl so bleibt
* die Implementierung immer synchron zum Vertrag (API First).
*/
@RestController
@RequestMapping("/api/v1")
class DocumentsController(
private val service: DocumentService,
) : DocumentsApi {
override fun listDocuments(): ResponseEntity<List<ApiDocument>> =
ResponseEntity.ok(service.findAll().map { it.toApi() })
override fun createDocument(documentCreateRequest: DocumentCreateRequest): ResponseEntity<ApiDocument> {
val created = service.create(
title = documentCreateRequest.title,
content = documentCreateRequest.content,
)
return ResponseEntity.status(HttpStatus.CREATED).body(created.toApi())
}
override fun getDocument(documentId: UUID): ResponseEntity<ApiDocument> =
ResponseEntity.ok(service.findById(documentId).toApi())
override fun updateDocument(
documentId: UUID,
documentUpdateRequest: DocumentUpdateRequest,
): ResponseEntity<ApiDocument> {
val updated = service.update(
id = documentId,
title = documentUpdateRequest.title,
content = documentUpdateRequest.content,
)
return ResponseEntity.ok(updated.toApi())
}
override fun deleteDocument(documentId: UUID): ResponseEntity<Unit> {
service.delete(documentId)
return ResponseEntity.noContent().build()
}
override fun getDocumentHistory(documentId: UUID): ResponseEntity<List<ApiHistoryEntry>> =
ResponseEntity.ok(service.history(documentId).map { it.toApi() })
override fun restoreDocument(
documentId: UUID,
restoreRequest: RestoreRequest?,
): ResponseEntity<ApiDocument> {
val restored = service.restore(documentId, restoreRequest?.version)
return ResponseEntity.ok(restored.toApi())
}
private fun Document.toApi(): ApiDocument = ApiDocument(
id = id,
title = title,
content = content,
version = version,
createdAt = createdAt,
updatedAt = updatedAt,
)
private fun DocumentHistoryEntry.toApi(): ApiHistoryEntry = ApiHistoryEntry(
documentId = documentId,
version = version,
title = title,
content = content,
changeType = ApiHistoryEntry.ChangeType.valueOf(changeType.name),
timestamp = timestamp,
)
}
@@ -0,0 +1,28 @@
package com.example.editor.api
import com.example.editor.service.DocumentConflictException
import com.example.editor.service.DocumentNotFoundException
import org.springframework.http.HttpStatus
import org.springframework.http.ProblemDetail
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
/**
* Zentrale Fehlerbehandlung im Problem-Details-Format (RFC 9457),
* passend zum ProblemDetail-Schema der OpenAPI-Spezifikation.
*/
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(DocumentNotFoundException::class)
fun handleNotFound(ex: DocumentNotFoundException): ProblemDetail =
ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.message ?: "Nicht gefunden").apply {
title = "Dokument nicht gefunden"
}
@ExceptionHandler(DocumentConflictException::class)
fun handleConflict(ex: DocumentConflictException): ProblemDetail =
ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, ex.message ?: "Konflikt").apply {
title = "Konflikt"
}
}
@@ -0,0 +1,21 @@
package com.example.editor.domain
import java.time.OffsetDateTime
import java.util.UUID
/**
* Internes Domänenmodell (bewusst getrennt vom generierten API-Modell).
*
* [version] wird bei jeder Änderung inkrementiert und dient später als Basis
* für Optimistic Locking und Live-Editing-Konflikterkennung.
* [content] ist ein opaker String bei clientseitiger Verschlüsselung wird
* hier später Ciphertext gespeichert, ohne dass sich das Modell ändert.
*/
data class Document(
val id: UUID,
val title: String,
val content: String,
val version: Long,
val createdAt: OffsetDateTime,
val updatedAt: OffsetDateTime,
)
@@ -0,0 +1,20 @@
package com.example.editor.domain
import java.time.OffsetDateTime
import java.util.UUID
enum class ChangeType { CREATED, UPDATED, DELETED, RESTORED }
/**
* Ein Eintrag der Dokumenthistorie. Die Historie wird getrennt vom Dokument
* gespeichert und überlebt daher ein DELETE Grundlage für die
* Wiederherstellung und später auch für Audit/Live-Editing-Replays.
*/
data class DocumentHistoryEntry(
val documentId: UUID,
val version: Long,
val title: String,
val content: String,
val changeType: ChangeType,
val timestamp: OffsetDateTime,
)
@@ -0,0 +1,51 @@
package com.example.editor.persistence
import com.example.editor.domain.Document
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.time.OffsetDateTime
import java.util.UUID
@Entity
@Table(name = "document")
class DocumentEntity(
@Id
val id: UUID,
@Column(nullable = false)
var title: String,
@Column(nullable = false, columnDefinition = "text")
var content: String,
@Column(nullable = false)
var version: Long,
@Column(name = "created_at", nullable = false)
var createdAt: OffsetDateTime,
@Column(name = "updated_at", nullable = false)
var updatedAt: OffsetDateTime,
) {
fun toDomain() = Document(
id = id,
title = title,
content = content,
version = version,
createdAt = createdAt,
updatedAt = updatedAt,
)
companion object {
fun fromDomain(document: Document) = DocumentEntity(
id = document.id,
title = document.title,
content = document.content,
version = document.version,
createdAt = document.createdAt,
updatedAt = document.updatedAt,
)
}
}
@@ -0,0 +1,61 @@
package com.example.editor.persistence
import com.example.editor.domain.ChangeType
import com.example.editor.domain.DocumentHistoryEntry
import jakarta.persistence.Column
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.Table
import java.time.OffsetDateTime
import java.util.UUID
@Entity
@Table(name = "document_history")
class DocumentHistoryEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
@Column(name = "document_id", nullable = false)
val documentId: UUID,
@Column(nullable = false)
val version: Long,
@Column(nullable = false)
val title: String,
@Column(nullable = false, columnDefinition = "text")
val content: String,
@Enumerated(EnumType.STRING)
@Column(name = "change_type", nullable = false, length = 16)
val changeType: ChangeType,
@Column(name = "change_time", nullable = false)
val changeTime: OffsetDateTime,
) {
fun toDomain() = DocumentHistoryEntry(
documentId = documentId,
version = version,
title = title,
content = content,
changeType = changeType,
timestamp = changeTime,
)
companion object {
fun fromDomain(entry: DocumentHistoryEntry) = DocumentHistoryEntity(
documentId = entry.documentId,
version = entry.version,
title = entry.title,
content = entry.content,
changeType = entry.changeType,
changeTime = entry.timestamp,
)
}
}
@@ -0,0 +1,21 @@
package com.example.editor.persistence
import com.example.editor.domain.DocumentHistoryEntry
import com.example.editor.repository.DocumentHistoryRepository
import org.springframework.stereotype.Repository
import java.util.UUID
@Repository
class JpaDocumentHistoryRepository(
private val jpa: DocumentHistoryJpaRepository,
) : DocumentHistoryRepository {
override fun append(entry: DocumentHistoryEntry) {
jpa.save(DocumentHistoryEntity.fromDomain(entry))
}
override fun findByDocumentId(documentId: UUID): List<DocumentHistoryEntry> =
jpa.findByDocumentIdOrderByIdAsc(documentId).map { it.toDomain() }
override fun clear() = jpa.deleteAll()
}
@@ -0,0 +1,35 @@
package com.example.editor.persistence
import com.example.editor.domain.Document
import com.example.editor.repository.DocumentRepository
import org.springframework.stereotype.Repository
import java.util.UUID
/**
* JPA-Adapter fuer das fachliche Repository-Interface.
* Ersetzt die fruehere In-Memory-Implementierung.
*/
@Repository
class JpaDocumentRepository(
private val jpa: DocumentJpaRepository,
) : DocumentRepository {
override fun findAll(): List<Document> =
jpa.findAll().map { it.toDomain() }.sortedBy { it.createdAt }
override fun findById(id: UUID): Document? =
jpa.findById(id).map { it.toDomain() }.orElse(null)
override fun save(document: Document): Document {
jpa.save(DocumentEntity.fromDomain(document))
return document
}
override fun deleteById(id: UUID): Boolean {
if (!jpa.existsById(id)) return false
jpa.deleteById(id)
return true
}
override fun clear() = jpa.deleteAll()
}
@@ -0,0 +1,10 @@
package com.example.editor.persistence
import org.springframework.data.jpa.repository.JpaRepository
import java.util.UUID
interface DocumentJpaRepository : JpaRepository<DocumentEntity, UUID>
interface DocumentHistoryJpaRepository : JpaRepository<DocumentHistoryEntity, Long> {
fun findByDocumentIdOrderByIdAsc(documentId: UUID): List<DocumentHistoryEntity>
}
@@ -0,0 +1,13 @@
package com.example.editor.repository
import com.example.editor.domain.DocumentHistoryEntry
import java.util.UUID
interface DocumentHistoryRepository {
fun append(entry: DocumentHistoryEntry)
/** Alle Einträge zu einem Dokument, älteste zuerst. */
fun findByDocumentId(documentId: UUID): List<DocumentHistoryEntry>
fun clear()
}
@@ -0,0 +1,17 @@
package com.example.editor.repository
import com.example.editor.domain.Document
import java.util.UUID
/**
* Abstraktion über die Persistenz. Die In-Memory-Implementierung ist ein
* Platzhalter und kann später durch JPA/R2DBC ersetzt werden, ohne dass
* Service oder Controller angepasst werden müssen.
*/
interface DocumentRepository {
fun findAll(): List<Document>
fun findById(id: UUID): Document?
fun save(document: Document): Document
fun deleteById(id: UUID): Boolean
fun clear()
}
@@ -0,0 +1,11 @@
package com.example.editor.service
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import java.time.Clock
@Configuration
class ClockConfiguration {
@Bean
fun clock(): Clock = Clock.systemUTC()
}
@@ -0,0 +1,3 @@
package com.example.editor.service
class DocumentConflictException(message: String) : RuntimeException(message)
@@ -0,0 +1,6 @@
package com.example.editor.service
import java.util.UUID
class DocumentNotFoundException(id: UUID) :
RuntimeException("Dokument mit ID $id wurde nicht gefunden")
@@ -0,0 +1,131 @@
package com.example.editor.service
import com.example.editor.domain.ChangeType
import com.example.editor.domain.Document
import com.example.editor.domain.DocumentHistoryEntry
import com.example.editor.repository.DocumentHistoryRepository
import com.example.editor.repository.DocumentRepository
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.Clock
import java.time.OffsetDateTime
import java.util.UUID
@Service
@Transactional
class DocumentService(
private val repository: DocumentRepository,
private val historyRepository: DocumentHistoryRepository,
private val clock: Clock,
) {
fun findAll(): List<Document> = repository.findAll()
fun findById(id: UUID): Document =
repository.findById(id) ?: throw DocumentNotFoundException(id)
fun create(title: String, content: String): Document {
val now = OffsetDateTime.now(clock)
val document = Document(
id = UUID.randomUUID(),
title = title,
content = content,
version = 1,
createdAt = now,
updatedAt = now,
)
repository.save(document)
recordHistory(document, ChangeType.CREATED)
return document
}
fun update(id: UUID, title: String, content: String): Document {
val existing = findById(id)
val updated = existing.copy(
title = title,
content = content,
version = existing.version + 1,
updatedAt = OffsetDateTime.now(clock),
)
repository.save(updated)
recordHistory(updated, ChangeType.UPDATED)
return updated
}
fun delete(id: UUID) {
val existing = findById(id)
repository.deleteById(id)
// Tombstone-Eintrag: konserviert den letzten Stand und überlebt das DELETE.
recordHistory(
existing.copy(
version = existing.version + 1,
updatedAt = OffsetDateTime.now(clock),
),
ChangeType.DELETED,
)
}
/**
* Historie eines Dokuments funktioniert auch für bereits gelöschte
* Dokumente. 404 nur, wenn die UUID gänzlich unbekannt ist.
*/
fun history(id: UUID): List<DocumentHistoryEntry> {
val entries = historyRepository.findByDocumentId(id)
if (entries.isEmpty()) throw DocumentNotFoundException(id)
return entries
}
/**
* Stellt ein Dokument unter derselben UUID wieder her.
*
* - Ohne [targetVersion]: letzter inhaltlicher Stand vor dem Löschen.
* Existiert das Dokument noch, gibt es einen Konflikt (409).
* - Mit [targetVersion]: Inhalt dieser Version wird als neue Version
* übernommen funktioniert auch als Rollback für existierende Dokumente.
*/
fun restore(id: UUID, targetVersion: Long? = null): Document {
val entries = historyRepository.findByDocumentId(id)
if (entries.isEmpty()) throw DocumentNotFoundException(id)
val existing = repository.findById(id)
if (existing != null && targetVersion == null) {
throw DocumentConflictException(
"Dokument $id existiert noch; zum Rollback bitte eine Zielversion angeben"
)
}
val snapshot = if (targetVersion != null) {
entries.lastOrNull { it.version == targetVersion && it.changeType != ChangeType.DELETED }
?: throw DocumentNotFoundException(id)
} else {
entries.last { it.changeType != ChangeType.DELETED }
}
val now = OffsetDateTime.now(clock)
val lastVersion = maxOf(entries.maxOf { it.version }, existing?.version ?: 0)
val restored = Document(
id = id,
title = snapshot.title,
content = snapshot.content,
version = lastVersion + 1,
createdAt = existing?.createdAt ?: entries.first().timestamp,
updatedAt = now,
)
repository.save(restored)
recordHistory(restored, ChangeType.RESTORED)
return restored
}
private fun recordHistory(document: Document, changeType: ChangeType) {
historyRepository.append(
DocumentHistoryEntry(
documentId = document.id,
version = document.version,
title = document.title,
content = document.content,
changeType = changeType,
timestamp = document.updatedAt,
)
)
}
}
@@ -0,0 +1,22 @@
spring:
application:
name: editor-backend
datasource:
# H2 im File-Modus mit PostgreSQL-Kompatibilitaet.
# Spaeterer Umstieg auf echtes PostgreSQL = im Wesentlichen nur diese URL aendern.
url: jdbc:h2:file:./data/editor;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DEFAULT_NULL_ORDERING=HIGH
username: sa
password: ""
driver-class-name: org.h2.Driver
jpa:
hibernate:
ddl-auto: validate # Schema kommt ausschliesslich von Liquibase
open-in-view: false
liquibase:
change-log: classpath:db/changelog/db.changelog-master.sql
server:
port: 8080
@@ -0,0 +1,28 @@
--liquibase formatted sql
--changeset editor:001-create-document
CREATE TABLE document (
id UUID PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
version BIGINT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
);
--rollback DROP TABLE document;
--changeset editor:002-create-document-history
CREATE TABLE document_history (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
document_id UUID NOT NULL,
version BIGINT NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
change_type VARCHAR(16) NOT NULL,
change_time TIMESTAMP WITH TIME ZONE NOT NULL
);
--rollback DROP TABLE document_history;
--changeset editor:003-index-document-history
CREATE INDEX idx_document_history_document_id ON document_history (document_id);
--rollback DROP INDEX idx_document_history_document_id;
+308
View File
@@ -0,0 +1,308 @@
openapi: 3.0.3
info:
title: Editor Backend API
description: |
CRUD-Grundgeruest fuer Dokumente.
Vorbereitete Erweiterungen (noch nicht aktiv):
- Autorisierung: securitySchemes.bearerAuth ist definiert, wird aber noch
auf keine Operation angewendet.
- Live-Editing: Das Feld `version` dient spaeter der Konflikterkennung
(Optimistic Locking) und als Basis fuer Delta-Updates via WebSocket.
- Clientseitige Verschluesselung: `content` ist ein opaker String. Der
Server interpretiert den Inhalt nicht, sodass spaeter Ciphertext
transportiert werden kann, ohne die API zu aendern.
version: 0.1.0
servers:
- url: /api/v1
tags:
- name: Documents
description: Verwaltung von Dokumenten
paths:
/documents:
get:
tags: [Documents]
operationId: listDocuments
summary: Alle Dokumente auflisten
responses:
"200":
description: Liste aller Dokumente
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Document"
post:
tags: [Documents]
operationId: createDocument
summary: Neues Dokument anlegen
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentCreateRequest"
responses:
"201":
description: Dokument wurde angelegt
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"400":
$ref: "#/components/responses/BadRequest"
/documents/{documentId}:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [Documents]
operationId: getDocument
summary: Einzelnes Dokument abrufen
responses:
"200":
description: Das angeforderte Dokument
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"404":
$ref: "#/components/responses/NotFound"
put:
tags: [Documents]
operationId: updateDocument
summary: Dokument vollstaendig aktualisieren
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/DocumentUpdateRequest"
responses:
"200":
description: Aktualisiertes Dokument
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"400":
$ref: "#/components/responses/BadRequest"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: Versionskonflikt (fuer spaeteres Optimistic Locking reserviert)
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
delete:
tags: [Documents]
operationId: deleteDocument
summary: Dokument loeschen
responses:
"204":
description: Dokument wurde geloescht
"404":
$ref: "#/components/responses/NotFound"
/documents/{documentId}/history:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
get:
tags: [Documents]
operationId: getDocumentHistory
summary: Historie eines Dokuments abrufen
description: >
Liefert alle Versionen eines Dokuments in chronologischer Reihenfolge.
Die Historie bleibt auch nach dem Loeschen des Dokuments erhalten.
responses:
"200":
description: Historie des Dokuments (aelteste zuerst)
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/DocumentHistoryEntry"
"404":
$ref: "#/components/responses/NotFound"
/documents/{documentId}/restore:
parameters:
- name: documentId
in: path
required: true
schema:
type: string
format: uuid
post:
tags: [Documents]
operationId: restoreDocument
summary: Geloeschtes Dokument aus der Historie wiederherstellen
description: >
Stellt ein geloeschtes Dokument unter derselben UUID wieder her.
Ohne Request-Body wird der letzte Stand vor dem Loeschen
wiederhergestellt; optional kann eine bestimmte Version angegeben
werden (auch als Rollback fuer ein noch existierendes Dokument).
requestBody:
required: false
content:
application/json:
schema:
$ref: "#/components/schemas/RestoreRequest"
responses:
"200":
description: Wiederhergestelltes Dokument
content:
application/json:
schema:
$ref: "#/components/schemas/Document"
"404":
$ref: "#/components/responses/NotFound"
"409":
description: >
Dokument existiert noch und es wurde keine Zielversion angegeben.
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
components:
responses:
NotFound:
description: Ressource nicht gefunden
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
BadRequest:
description: Ungueltige Anfrage
content:
application/problem+json:
schema:
$ref: "#/components/schemas/ProblemDetail"
schemas:
Document:
type: object
required: [id, title, content, version, createdAt, updatedAt]
properties:
id:
type: string
format: uuid
readOnly: true
title:
type: string
maxLength: 255
content:
type: string
description: >
Opaker Inhalt. Bei clientseitiger Verschluesselung enthaelt dieses
Feld spaeter den Ciphertext.
version:
type: integer
format: int64
description: Wird bei jeder Aenderung inkrementiert (Basis fuer Live-Editing/Konflikterkennung).
createdAt:
type: string
format: date-time
readOnly: true
updatedAt:
type: string
format: date-time
readOnly: true
DocumentCreateRequest:
type: object
required: [title, content]
properties:
title:
type: string
minLength: 1
maxLength: 255
content:
type: string
DocumentUpdateRequest:
type: object
required: [title, content]
properties:
title:
type: string
minLength: 1
maxLength: 255
content:
type: string
expectedVersion:
type: integer
format: int64
description: Optional; wird spaeter fuer Optimistic Locking ausgewertet.
DocumentHistoryEntry:
type: object
required: [documentId, version, title, content, changeType, timestamp]
properties:
documentId:
type: string
format: uuid
version:
type: integer
format: int64
title:
type: string
content:
type: string
changeType:
type: string
enum: [CREATED, UPDATED, DELETED, RESTORED]
timestamp:
type: string
format: date-time
RestoreRequest:
type: object
properties:
version:
type: integer
format: int64
description: >
Optionale Zielversion. Ohne Angabe wird der letzte inhaltliche
Stand vor dem Loeschen wiederhergestellt.
ProblemDetail:
type: object
description: Fehlerformat nach RFC 9457 (Problem Details)
properties:
type:
type: string
title:
type: string
status:
type: integer
detail:
type: string
instance:
type: string
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: >
Noch nicht aktiv. Wird bei Einfuehrung der Autorisierung auf die
Operationen angewendet (security: - bearerAuth: []).