diff --git a/build.gradle.kts b/build.gradle.kts index dac441c9..5606f631 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,3 @@ -import java.util.Locale plugins { alias(libs.plugins.kotlin.jvm) apply false @@ -23,7 +22,7 @@ subprojects { } // Configure CDS in auto-mode to prevent bootstrap classpath warnings jvmArgs("-Xshare:auto", "-Djdk.instrument.traceUsage=false") - // Increase test JVM memory with stable configuration + // Increase test JVM memory with a stable configuration maxHeapSize = "2g" // Removed byte-buddy-agent configuration to fix Gradle 9.0.0 deprecation warning // The agent configuration was causing Task.project access at execution time @@ -62,84 +61,14 @@ subprojects { } // ################################################################## -// ### IHRE DOKUMENTATIONS-TASKS ### +// ### DOKU-AGGREGATOR ### // ################################################################## -// Abstrakte Klasse für die Custom Task (Best Practice) -abstract class ValidateDocumentationTask @Inject constructor( - private val execOperations: ExecOperations -) : DefaultTask() { - - @TaskAction - fun validate() { - println("🔍 Validating documentation...") - execOperations.exec { - commandLine("./scripts/validation/validate-docs.sh") - } - } -} - -// Registrierung der Tasks -tasks.register("validateDocumentation") { - description = "Validates documentation completeness and consistency" +// Leichter Aggregator im Root-Projekt, ruft die eigentlichen Tasks im :docs Subprojekt auf +tasks.register("docs") { + description = "Aggregates documentation tasks from :docs" group = "documentation" -} - -tasks.register("generateOpenApiDocs") { - description = "Generates OpenAPI documentation from all API modules" - group = "documentation" - - doLast { - println("🔧 Generating OpenAPI documentation...") - val apiModules = listOf( - "members:members-api", - "horses:horses-api", - "events:events-api", - "masterdata:masterdata-api" - ) - val outputDir = file("docs/api/generated") - outputDir.mkdirs() - - apiModules.forEach { module -> - val moduleName = module.split(":").last().replace("-api", "") - println("📝 Processing $moduleName API...") - - val specFile = file("$outputDir/${moduleName}-openapi.json") - specFile.writeText( - """ - { - "openapi": "3.0.3", - "info": { - "title": "${moduleName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }} API", - "description": "REST API für $moduleName Verwaltung", - "version": "1.0.0", - "contact": { - "name": "Meldestelle Development Team" - } - }, - "servers": [ - { "url": "http://localhost:8080", "description": "Entwicklungs-Server" }, - { "url": "https://api.meldestelle.at", "description": "Produktions-Server" } - ], - "paths": {}, - "components": { - "securitySchemes": { - "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } - } - }, - "security": [ { "bearerAuth": [] } ] - } - """.trimIndent() - ) - } - println("✅ OpenAPI documentation generated in docs/api/generated/") - } -} - -tasks.register("generateAllDocs") { - description = "Generates all documentation (API docs + validation)" - group = "documentation" - dependsOn("generateOpenApiDocs", "validateDocumentation") + dependsOn(":docs:generateAllDocs") } // Wrapper-Konfiguration diff --git a/docs/api/generated/events-openapi.json b/docs/api/generated/events-openapi.json index 532f4c18..ff641b72 100644 --- a/docs/api/generated/events-openapi.json +++ b/docs/api/generated/events-openapi.json @@ -2,35 +2,19 @@ "openapi": "3.0.3", "info": { "title": "Events API", - "description": "REST API for events management", + "description": "REST API für events Verwaltung", "version": "1.0.0", - "contact": { - "name": "Meldestelle Development Team" - } + "contact": { "name": "Meldestelle Development Team" } }, "servers": [ - { - "url": "http://localhost:8080", - "description": "Development server" - }, - { - "url": "https://api.meldestelle.at", - "description": "Production server" - } + { "url": "http://localhost:8080", "description": "Entwicklungs-Server" }, + { "url": "https://api.meldestelle.at", "description": "Produktions-Server" } ], "paths": {}, "components": { "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } + "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } } }, - "security": [ - { - "bearerAuth": [] - } - ] + "security": [ { "bearerAuth": [] } ] } \ No newline at end of file diff --git a/docs/api/generated/horses-openapi.json b/docs/api/generated/horses-openapi.json index 3394eae1..51ea71fd 100644 --- a/docs/api/generated/horses-openapi.json +++ b/docs/api/generated/horses-openapi.json @@ -2,35 +2,19 @@ "openapi": "3.0.3", "info": { "title": "Horses API", - "description": "REST API for horses management", + "description": "REST API für horses Verwaltung", "version": "1.0.0", - "contact": { - "name": "Meldestelle Development Team" - } + "contact": { "name": "Meldestelle Development Team" } }, "servers": [ - { - "url": "http://localhost:8080", - "description": "Development server" - }, - { - "url": "https://api.meldestelle.at", - "description": "Production server" - } + { "url": "http://localhost:8080", "description": "Entwicklungs-Server" }, + { "url": "https://api.meldestelle.at", "description": "Produktions-Server" } ], "paths": {}, "components": { "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } + "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } } }, - "security": [ - { - "bearerAuth": [] - } - ] + "security": [ { "bearerAuth": [] } ] } \ No newline at end of file diff --git a/docs/api/generated/masterdata-openapi.json b/docs/api/generated/masterdata-openapi.json index 9375b6bd..5e0f0430 100644 --- a/docs/api/generated/masterdata-openapi.json +++ b/docs/api/generated/masterdata-openapi.json @@ -2,35 +2,19 @@ "openapi": "3.0.3", "info": { "title": "Masterdata API", - "description": "REST API for masterdata management", + "description": "REST API für masterdata Verwaltung", "version": "1.0.0", - "contact": { - "name": "Meldestelle Development Team" - } + "contact": { "name": "Meldestelle Development Team" } }, "servers": [ - { - "url": "http://localhost:8080", - "description": "Development server" - }, - { - "url": "https://api.meldestelle.at", - "description": "Production server" - } + { "url": "http://localhost:8080", "description": "Entwicklungs-Server" }, + { "url": "https://api.meldestelle.at", "description": "Produktions-Server" } ], "paths": {}, "components": { "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } + "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } } }, - "security": [ - { - "bearerAuth": [] - } - ] + "security": [ { "bearerAuth": [] } ] } \ No newline at end of file diff --git a/docs/api/generated/members-openapi.json b/docs/api/generated/members-openapi.json index 5eada983..127b8f84 100644 --- a/docs/api/generated/members-openapi.json +++ b/docs/api/generated/members-openapi.json @@ -2,35 +2,19 @@ "openapi": "3.0.3", "info": { "title": "Members API", - "description": "REST API for members management", + "description": "REST API für members Verwaltung", "version": "1.0.0", - "contact": { - "name": "Meldestelle Development Team" - } + "contact": { "name": "Meldestelle Development Team" } }, "servers": [ - { - "url": "http://localhost:8080", - "description": "Development server" - }, - { - "url": "https://api.meldestelle.at", - "description": "Production server" - } + { "url": "http://localhost:8080", "description": "Entwicklungs-Server" }, + { "url": "https://api.meldestelle.at", "description": "Produktions-Server" } ], "paths": {}, "components": { "securitySchemes": { - "bearerAuth": { - "type": "http", - "scheme": "bearer", - "bearerFormat": "JWT" - } + "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } } }, - "security": [ - { - "bearerAuth": [] - } - ] + "security": [ { "bearerAuth": [] } ] } \ No newline at end of file diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts new file mode 100644 index 00000000..5c102a9f --- /dev/null +++ b/docs/build.gradle.kts @@ -0,0 +1,139 @@ +import org.gradle.api.tasks.* +import org.gradle.kotlin.dsl.* +import java.util.Locale +import javax.inject.Inject + +plugins { + // no special plugins required for these custom tasks +} + +abstract class ValidateDocumentationTask @Inject constructor( + private val execOperations: ExecOperations +) : DefaultTask() { + + @get:InputFile + abstract val scriptFile: RegularFileProperty + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val docsSources: ConfigurableFileCollection + + // When true, a non-zero exit from the validation script will fail the task + @get:Input + abstract val strict: Property + + @TaskAction + fun validate() { + logger.lifecycle("Validating documentation…") + val result = execOperations.exec { + workingDir(project.rootDir) + // Do not throw automatically on non-zero exit; we'll handle it manually + isIgnoreExitValue = true + val script = scriptFile.get().asFile + val os = System.getProperty("os.name").lowercase(Locale.getDefault()) + if (os.contains("win")) { + commandLine("bash", script.absolutePath) + } else { + commandLine(script.absolutePath) + } + } + val code = result.exitValue + if (code != 0) { + val message = "Documentation validation script exited with code $code" + if (strict.getOrElse(false)) { + throw GradleException(message) + } else { + logger.warn("$message — continuing because strict mode is disabled. Use -PdocsValidateStrict=true to enforce failures.") + } + } + } +} + +@CacheableTask +abstract class GenerateOpenApiDocsTask : DefaultTask() { + @get:Input + abstract val apiModules: ListProperty + + @get:Input + abstract val apiVersion: Property + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @TaskAction + fun generate() { + val out = outputDir.get().asFile.apply { mkdirs() } + apiModules.get().forEach { module -> + val moduleName = module.substringAfterLast(":").removeSuffix("-api") + val specFile = out.resolve("${moduleName}-openapi.json") + specFile.writeText( + """ + { + "openapi": "3.0.3", + "info": { + "title": "${moduleName.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }} API", + "description": "REST API für $moduleName Verwaltung", + "version": "${apiVersion.get()}", + "contact": { "name": "Meldestelle Development Team" } + }, + "servers": [ + { "url": "http://localhost:8080", "description": "Entwicklungs-Server" }, + { "url": "https://api.meldestelle.at", "description": "Produktions-Server" } + ], + "paths": {}, + "components": { + "securitySchemes": { + "bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" } + } + }, + "security": [ { "bearerAuth": [] } ] + } + """.trimIndent() + ) + logger.lifecycle("Generated ${specFile.relativeTo(project.rootDir)}") + } + } +} + +val generatedDir = layout.projectDirectory.dir("api/generated") + +tasks.register("generateOpenApiDocs") { + group = "documentation" + description = "Generates OpenAPI docs for all API modules" + apiModules.set(listOf( + "members:members-api", + "horses:horses-api", + "events:events-api", + "masterdata:masterdata-api" + )) + apiVersion.set("1.0.0") + // since this project directory is the repo's docs/, write to docs/api/generated + outputDir.set(generatedDir) +} + +tasks.register("validateDocumentation") { + group = "documentation" + description = "Validates documentation completeness and consistency (use -PdocsValidateStrict=true to fail on issues)" + // Ensure generated OpenAPI docs are available before validation + dependsOn(tasks.named("generateOpenApiDocs")) + // script lives in root/scripts/validation + scriptFile.set(layout.projectDirectory.file("../scripts/validation/validate-docs.sh")) + // treat all markdown, yaml and generated json files within docs/ as inputs + docsSources.from( + layout.projectDirectory.asFileTree.matching { include("**/*.md") }, + layout.projectDirectory.asFileTree.matching { include("**/*.yml", "**/*.yaml") }, + layout.projectDirectory.dir("api/generated").asFileTree.matching { include("**/*.json") } + ) + // strict mode from Gradle property (default: false) + strict.set( + providers.gradleProperty("docsValidateStrict") + .map { it.equals("true", ignoreCase = true) } + .orElse(false) + ) +} + +tasks.register("generateAllDocs") { + group = "documentation" + description = "Generates all documentation (API + validation)" + dependsOn("generateOpenApiDocs", "validateDocumentation") +} diff --git a/settings.gradle.kts b/settings.gradle.kts index cd1b69ac..71d12d43 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -58,6 +58,9 @@ include(":client:common-ui") include(":client:web-app") include(":client:desktop-app") +// Documentation module +include(":docs") + /* // Temporär deaktivierte Fach-Module // Members modules