diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 132aec20..6e307d49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,9 +77,9 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} detailed_summary: true - name: Publish - if: ${{ startsWith(github.ref, 'refs/heads/master') || startsWith(github.ref, 'refs/heads/release') }}" + if: ${{ startsWith(github.ref, 'refs/heads/master') || startsWith(github.ref, 'refs/heads/release') }} uses: gradle/gradle-build-action@v2.4.2 with: arguments: >- --info -PsigningKeyId=DF8285F0 - :alfresco:${{ matrix.alfresco_version }}:publish \ No newline at end of file + :alfresco:${{ matrix.alfresco_version }}:publish diff --git a/.github/workflows/website-publish.yml b/.github/workflows/website-publish.yml new file mode 100644 index 00000000..4f6c3f7d --- /dev/null +++ b/.github/workflows/website-publish.yml @@ -0,0 +1,51 @@ +name: 'Publish website' +on: + workflow_dispatch: +env: + BRANCH_NAME: ${{ github.ref_name }} + ALFRESCO_NEXUS_USERNAME: ${{ secrets.ALFRESCO_NEXUS_USERNAME }} + ALFRESCO_NEXUS_PASSWORD: ${{ secrets.ALFRESCO_NEXUS_PASSWORD }} +jobs: + alfred-api-docs: + if: ${{ startsWith(github.ref, 'refs/heads/master') || startsWith(github.ref, 'refs/heads/release') }} + runs-on: ubuntu-latest + steps: + - name: Check out + uses: actions/checkout@v4 + - name: Login to Docker + uses: docker/login-action@v2 + with: + registry: private.docker.xenit.eu + username: ${{ secrets.CLOUDSMITH_USER }} + password: ${{ secrets.CLOUDSMITH_APIKEY }} + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + - name: Build website + run: ./gradlew :docs:buildWebsiteScript --info + - name: Upload website artifact + if: success() + uses: actions/upload-artifact@v3 + with: + name: website-alfred-api.tar.gz + path: /home/runner/work/alfred-api/docs/build/website-*.tar.gz + retention-days: 2 + - name: Write ssh key to file + env: + RAW_KEY: ${{ secrets.A2_HOSTING_SSH_KEY }} + run: | + mkdir -p /home/runner/.ssh/ + echo "$RAW_KEY" > /home/runner/.ssh/a2hostingKey + chmod 600 /home/runner/.ssh/a2hostingKey + - name: Deploy tar to webhost + env: + GRADLE_OPTS: >- + -Dorg.gradle.project.webHostAddress=nl1-ts102.a2hosting.com + -Dorg.gradle.project.webHostPort=7822 + -Dorg.gradle.project.webHostUser=xeniteu + -Dorg.gradle.project.webHostSshKey=/home/runner/.ssh/a2hostingKey + run: ./gradlew :docs:deployWebsiteToWebHost --info \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index e3563e0c..beb1f46a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Alfred API - Changelog +## 5.0.4 (Unreleased) + +### Fixed +* [ALFREDAPI-552](https://xenitsupport.jira.com/browse/ALFREDAPI-552) Make swagger spec Open Api v2 compliant + ## 5.0.3 (2024-06-17) ### Fixed diff --git a/build.gradle b/build.gradle index 2adcd545..6d1cca5a 100644 --- a/build.gradle +++ b/build.gradle @@ -4,8 +4,8 @@ plugins { id 'eu.xenit.de' version '3.1.0' apply false id 'eu.xenit.amp' version '1.1.0' apply false id 'eu.xenit.alfresco' version '1.1.0' apply false - id 'eu.xenit.docker-alfresco' version '5.3.1' apply false - id 'eu.xenit.docker-compose' version '5.3.1' apply false + id 'eu.xenit.docker-alfresco' version '5.5.0' apply false + id 'eu.xenit.docker-compose' version '5.5.0' apply false id 'eu.xenit.alfresco-remote-testrunner' version '2.0.1' apply false } diff --git a/docs/README.md b/docs/README.md index f230edc1..fd01a5f7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,7 @@ # Documentation Alfred API +**Process is automated through GHA. Trigger the appropriate workflow.** + ## Generate website This directory generates the product documentation website at https://docs.xenit.eu/alfred-api/ diff --git a/docs/build-website.sh b/docs/build-website.sh index f08cfb5e..68e1ec78 100755 --- a/docs/build-website.sh +++ b/docs/build-website.sh @@ -62,7 +62,7 @@ build_javadoc() { local alfredapidir=".." pushd "$alfredapidir" - ./gradlew clean :apix-interface:javadoc + ./gradlew :apix-interface:javadoc popd local outputdir="build/website/$productName/" diff --git a/docs/build.gradle b/docs/build.gradle new file mode 100644 index 00000000..a727b0ce --- /dev/null +++ b/docs/build.gradle @@ -0,0 +1,54 @@ +import java.text.SimpleDateFormat + +plugins { + id "base" + id "org.hidetake.ssh" version "2.11.2" +} + +def date = new Date() +def format = new SimpleDateFormat("yyyy-MM-dd") + +tasks.register("buildWebsiteScript", Exec) { + setGroup "documentation" + commandLine 'bash', './build-website.sh' +} +assemble.dependsOn buildWebsiteScript + +remotes { + a2hosting { + host = project.findProperty("webHostAddress") + port = project.hasProperty("webHostPort") ? (project.getProperty("webHostPort") as Integer) : 22 + user = project.findProperty("webHostUser") + def keyFileLocation = project.hasProperty("webHostSshKey") ? project.getProperty("webHostSshKey") : "No Key" + identity = file(keyFileLocation) + } +} + +ssh.settings { + knownHosts = allowAnyHosts +} + +tasks.register("deployWebsiteToWebHost") { + setGroup "publishing" + dependsOn buildWebsiteScript + doLast { + ssh.run { + session(remotes.a2hosting) { + def remoteHomeDir = execute 'pwd' + def websiteDir = "${remoteHomeDir}/docs.xenit.eu" + def today = format.format(date) + def websiteTar = "website-alfred-api_${today}.tar.gz" + def backupDir = "alfred-api_${today}_back" + // Upload + put from: file("${layout.buildDirectory.get()}/${websiteTar}"), into: "${websiteDir}/" + // Backup old website component to tar + execute "mv ${websiteDir}/alfred-api ${remoteHomeDir}/${backupDir}" + execute "tar vczf alfred-api_${today}_back.tar.gz -C ${remoteHomeDir} ${backupDir}" + // Deploy new website component from tar + execute "tar vxzf ${websiteDir}/${websiteTar} -C ${websiteDir}" + // Cleanup uncompressed backup + remove "${remoteHomeDir}/${backupDir}" + } + } + } +} \ No newline at end of file diff --git a/docs/swagger-ui/swagger.json b/docs/swagger-ui/swagger.json index e22e46ab..9fefa92d 100644 --- a/docs/swagger-ui/swagger.json +++ b/docs/swagger-ui/swagger.json @@ -1,9 +1,9 @@ { "swagger": "2.0", "info": { - "description": "This is the swagger specification for Api-X REST API\n\nExamples can be found at: https://docs.xenit.eu/alfred-api", - "version": "5.0.0", - "title": "Api-X REST API", + "description": "This is the swagger specification for Alfred REST API\n\nExamples can be found at: https://docs.xenit.eu/alfred-api", + "version": "5.0.3", + "title": "Alfred REST Api", "contact": { "name": "XeniT", "url": "http://www.xenit.eu", @@ -288,10 +288,10 @@ { "name": "fields", "in": "query", - "description": "Comma separated field names to include.", + "description": "Comma separated field names to include. Example: \"content,nodeRef\"", "required": true, "type": "string", - "default": "content,nodeRef", + "default": "nodeRef", "enum": [ "content", "nodeRef", @@ -503,7 +503,7 @@ ], "summary": "The Swagger Spec for Alfred API", "description": "", - "operationId": "execute", + "operationId": "v1.getDocumentation", "parameters": [], "responses": { "200": { @@ -536,7 +536,7 @@ ], "summary": "Creates or copies a node", "description": "Example of POST body:\n\n```\nPOST /apix/v1/nodes\n{\n\"parent\" : \"workspace://SpacesStore/d5dac928-e581-4507-9be7-9a2416adc318\", \n\"name\" : \"mydocument.txt\", \n\"type\" : \"{http://www.alfresco.org/model/content/1.0}content\", \n\"properties\" : {\n \"{namespace}property1\": [\n \"string\"\n ],\n \"{namespace}property2\": [\n \"string\"\n ],\n \"{namespace}property3\": [\n \"string\"\n ]\n}, \n\"aspectsToAdd\" : [\n \"{namespace}aspect1\"\n], \n\"aspectsToRemove\" : [\n \"{namespace}aspect1\"\n], \n\"copyFrom\" : \"workspace://SpacesStore/f0d15919-3841-4170-807f-b81d2ebdeb80\", \n}\n```\n\"aspectsToRemove\" is only relevant when copying a node.\n", - "operationId": "createNode", + "operationId": "v1.createNode", "parameters": [ { "in": "body", @@ -1431,6 +1431,7 @@ "type": "file" } ], + "consumes": ["multipart/form-data"], "responses": { "200": { "description": "Success" @@ -1658,7 +1659,7 @@ ], "summary": "Retrieve current user's permissions for a node", "description": "Returns a key-value map of permissions keys to a value of 'DENY' or 'ALLOW'. Possible keys are: Read, Write, Delete, CreateChildren, ReadPermissions, ChangePermissions, or custom permissions", - "operationId": "getPermissions", + "operationId": "v1.getPermissions", "parameters": [ { "name": "space", @@ -1828,7 +1829,7 @@ ], "summary": "Returns person information", "description": "", - "operationId": "getPerson", + "operationId": "v1.getPerson", "parameters": [ { "name": "space", @@ -1866,7 +1867,7 @@ ], "summary": "Return the definition of a property", "description": "", - "operationId": "getPropertyDefinition", + "operationId": "v1.getPropertyDefinition", "parameters": [ { "name": "qname", @@ -1898,7 +1899,7 @@ ], "summary": "Performs a search for nodes", "description": "# Request components\n\n## query\nObject containing subcomponents that build the requested query.\nInfo about the Search query syntax can be found here: https://docs.xenit.eu/alfred-api/user/rest-api\n### special search terms:\n- type: searches for nodes of that type (for example: \"type\" : \"cm:content\")\n- aspect: searches for nodes with that aspect (for example: \"aspect\" : \"cm:titled\")\n- noderef: searches the node with that noderef (for example: \"noderef\" : \"workspace://SpacesStore/f0d15919-3841-4170-807f-b81d2ebdeb80\")\n- parent: searches the nodes with that parent (for example: \"parent\" : \"workspace://SpacesStore/f0d15919-3841-4170-807f-b81d2ebdeb80\")\n- path: searches the nodes with that path (for example: \"path\" : \"/\")\n- category: searches the nodes with that category (for example: \"category\" : \"workspace://SpacesStore/f0d15919-3841-4170-807f-b81d2ebdeb80\")\n- text: searches the nodes with content containing that text (for example: \"text\" : \"this text\")\n- all: searches the nodes with content, cm:name, cm:creator, cm:modifier or cm:author containing the value (for example: \"all\" : \"search term\")\n- isunset: searches the nodes with where the value of the property is not set (for example: \"isunset\" : \"cm:author\")\n- isnull: searches the nodes with where the value of the property is null (for example: \"isnull\" : \"cm:author\")\n- isnotnull: searches the nodes with where the value of the property is not null (for example: \"isnotnull\" : \"cm:author\")\n- exists: searches the nodes that have the property (for example: \"exists\" : \"cm:author\")\n\n## paging\n`Optional`\n\nOptions to skip over results starting from the top of the result and to limit the total number of results.\n\n## facets\n`Optional`\n\nOptions to enable, limit the total amount, minimum number of hits and customize input of facets.\n\nNote: facets with 0 hits are not returned in the result\n\n## orderBy\n`Optional`\n\nOptions to select the property to order by and the direction of sorting.\n\n## consistency\n`Optional`\n\nOption to request specific consistency\n\n## locale\n`Optional`\n\nOptions to request specific locale and encoding options\n\n## workspace\n`Optional`\n\nOptions to change the target alfresco workspace\n\n## highlight\n`5.2 and up`\n\n`Optional`\n\nOptions to change the highlight configuration.\nMinimal requirement is the `fields` array, which takes object containing at least the `field` property. Each list element specifies a property on which higlighting needs to be applied. When no fields are specified, the call defaults to `cm:content` as field.\nFull documentation can be found on the alfresco [documentation](https://docs.alfresco.com/5.2/concepts/search-api-highlight.html) page.\n\n# Examples\n\nSearch for the first 10 nodes in the `cm:content` namespace:\n```json\n{\n \"query\": {\"type\":\"{http://www.alfresco.org/model/content/1.0}content\"},\n \"paging\": {\n \"limit\": 10,\n \"skip\": 0\n },\n \"facets\": {\n \"enabled\": false\n }\n}\n```\n\nSearch for all nodes with the term 'budget' in the `cm:content` property (fulltext), and show two highlighted hits with delimiter \\\\:\n```json\n{\n \"query\": {\n \"property\": {\n \"exact\": false,\n \"name\": \"cm:content\",\n \"value\": \"budget\"\n }\n },\n \"highlight\":{\n \"prefix\":\"\",\n \"postfix\":\"\",\n \"snippetCount\":2,\n\t\t\"fields\":[{\"field\":\"cm:content\"}]\n }\n}\n```", - "operationId": "execute", + "operationId": "v1.search", "parameters": [ { "in": "body", @@ -2658,7 +2659,7 @@ ], "summary": "Creates or copies a node", "description": "", - "operationId": "createNode", + "operationId": "v2.createNode", "parameters": [ { "in": "body", @@ -2746,7 +2747,7 @@ ], "summary": "Retrieve current user's permissions for a node", "description": "Returns a key-value map of permissions keys to a value of 'DENY' or 'ALLOW'. Possible keys are: Read, Write, Delete, CreateChildren, ReadPermissions, ChangePermissions, or custom permissions", - "operationId": "getPermissions", + "operationId": "v2.getPermissions", "parameters": [ { "name": "space", @@ -2862,7 +2863,7 @@ ], "summary": "Returns person information", "description": "", - "operationId": "getPerson", + "operationId": "v2.getPerson", "parameters": [ { "name": "space", @@ -3160,12 +3161,10 @@ "type": "object", "properties": { "comment": { - "type": "string", - "readOnly": true + "type": "string" }, "majorVersion": { "type": "boolean", - "readOnly": true, "default": false } } @@ -3233,7 +3232,11 @@ }, "type": { "description": "Defaults to cm:content", - "$ref": "#/definitions/QName" + "allOf": [ + { + "$ref": "#/definitions/QName" + } + ] } } }, @@ -3668,20 +3671,16 @@ ], "properties": { "parent": { - "type": "string", - "readOnly": true + "type": "string" }, "name": { - "type": "string", - "readOnly": true + "type": "string" }, "type": { - "type": "string", - "readOnly": true + "type": "string" }, "properties": { "type": "object", - "readOnly": true, "additionalProperties": { "type": "array", "items": { @@ -3691,21 +3690,18 @@ }, "aspectsToAdd": { "type": "array", - "readOnly": true, "items": { "$ref": "#/definitions/QName" } }, "aspectsToRemove": { "type": "array", - "readOnly": true, "items": { "$ref": "#/definitions/QName" } }, "copyFrom": { - "type": "string", - "readOnly": true + "type": "string" } } }, @@ -3763,8 +3759,7 @@ ], "properties": { "parent": { - "type": "string", - "readOnly": true + "type": "string" } } }, @@ -3775,13 +3770,15 @@ ], "properties": { "original": { - "readOnly": true, "$ref": "#/definitions/NodeRef" }, "destinationFolder": { "description": "Optional, if not specified uses the original node's folder. If current user does not have permissions for this folder, the working copy is created in the user's home folder", - "readOnly": true, - "$ref": "#/definitions/NodeRef" + "allOf": [ + { + "$ref": "#/definitions/NodeRef" + } + ] } } }, @@ -3804,7 +3801,6 @@ "properties": { "users": { "type": "array", - "readOnly": true, "items": { "type": "string" } @@ -4172,7 +4168,6 @@ "properties": { "subgroups": { "type": "array", - "readOnly": true, "items": { "type": "string" } diff --git a/settings.gradle b/settings.gradle index 4a170df9..00c6e31d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ include ':alfresco' include ':apix-docker' include ':apix-integrationtests' include ':apix-integrationtests:model-amp' +include ':docs' for (String version : ['7.0', '7.1', '7.2', '7.3', '7.4']) { def shortVersion = version.replaceAll('\\.', '')