name: OpenAPI
on:
  push:
    branches:
      - master
    tags:
      - 'v*'
  pull_request_target:

permissions: {}

jobs:
  openapi-head:
    name: OpenAPI - HEAD
    runs-on: ubuntu-latest
    permissions: read-all
    steps:
      - name: Checkout repository
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          repository: ${{ github.event.pull_request.head.repo.full_name }}
      - name: Setup .NET
        uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
        with:
          dotnet-version: '9.0.x'
      - name: Generate openapi.json
        run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
      - name: Upload openapi.json
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: openapi-head
          retention-days: 14
          if-no-files-found: error
          path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net9.0/openapi.json

  openapi-base:
    name: OpenAPI - BASE
    if: ${{ github.base_ref != '' }}
    runs-on: ubuntu-latest
    permissions: read-all
    steps:
      - name: Checkout repository
        uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          fetch-depth: 0
      - name: Checkout common ancestor
        env:
          HEAD_REF: ${{ github.head_ref }}
        run: |
          git remote add upstream https://github.com/${{ github.event.pull_request.base.repo.full_name }}
          git -c protocol.version=2 fetch --prune --progress --no-recurse-submodules upstream +refs/heads/*:refs/remotes/upstream/* +refs/tags/*:refs/tags/*
          ANCESTOR_REF=$(git merge-base upstream/${{ github.base_ref }} origin/$HEAD_REF)
          git checkout --progress --force $ANCESTOR_REF
      - name: Setup .NET
        uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
        with:
          dotnet-version: '9.0.x'
      - name: Generate openapi.json
        run: dotnet test tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj -c Release --filter "Jellyfin.Server.Integration.Tests.OpenApiSpecTests"
      - name: Upload openapi.json
        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
        with:
          name: openapi-base
          retention-days: 14
          if-no-files-found: error
          path: tests/Jellyfin.Server.Integration.Tests/bin/Release/net9.0/openapi.json

  openapi-diff:
    permissions:
      pull-requests: write  #  to create or update comment (peter-evans/create-or-update-comment)

    name: OpenAPI - Difference
    if: ${{ github.event_name == 'pull_request_target' }}
    runs-on: ubuntu-latest
    needs:
      - openapi-head
      - openapi-base
    steps:
      - name: Download openapi-head
        uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
        with:
          name: openapi-head
          path: openapi-head
      - name: Download openapi-base
        uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
        with:
          name: openapi-base
          path: openapi-base
      - name: Workaround openapi-diff issue
        run: |
          sed -i 's/"allOf"/"oneOf"/g' openapi-head/openapi.json
          sed -i 's/"allOf"/"oneOf"/g' openapi-base/openapi.json
      - name: Calculate OpenAPI difference
        uses: docker://openapitools/openapi-diff
        continue-on-error: true
        with:
          args: --fail-on-changed --markdown openapi-changes.md openapi-base/openapi.json openapi-head/openapi.json
      - id: read-diff
        name: Read openapi-diff output
        run: |
          # Read and fix markdown
          body=$(cat openapi-changes.md)
          # Write to workflow summary
          echo "$body" >> $GITHUB_STEP_SUMMARY
          # Set ApiChanged var
          if [ "$body" != '' ]; then
            echo "ApiChanged=1" >> "$GITHUB_OUTPUT"
          else
            echo "ApiChanged=0" >> "$GITHUB_OUTPUT"
          fi
          # Add header/footer for diff comment
          echo '<!--openapi-diff-workflow-comment-->' > openapi-changes-reply.md
          echo "<details>" >> openapi-changes-reply.md
          echo "<summary>Changes in OpenAPI specification found. Expand to see details.</summary>" >> openapi-changes-reply.md
          echo "" >> openapi-changes-reply.md
          echo "$body" >> openapi-changes-reply.md
          echo "" >> openapi-changes-reply.md
          echo "</details>" >> openapi-changes-reply.md
      - name: Find difference comment
        uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0
        id: find-comment
        with:
          issue-number: ${{ github.event.pull_request.number }}
          direction: last
          body-includes: openapi-diff-workflow-comment
      - name: Reply or edit difference comment (changed)
        uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
        if: ${{ steps.read-diff.outputs.ApiChanged == '1' }}
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-id: ${{ steps.find-comment.outputs.comment-id }}
          edit-mode: replace
          body-path: openapi-changes-reply.md
      - name: Edit difference comment (unchanged)
        uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0
        if: ${{ steps.read-diff.outputs.ApiChanged == '0' && steps.find-comment.outputs.comment-id != '' }}
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-id: ${{ steps.find-comment.outputs.comment-id }}
          edit-mode: replace
          body: |
            <!--openapi-diff-workflow-comment-->

            No changes to OpenAPI specification found. See history of this comment for previous changes.

  publish-unstable:
    name: OpenAPI - Publish Unstable Spec
    if: ${{ github.event_name != 'pull_request_target' && !startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
    runs-on: ubuntu-latest
    needs:
      - openapi-head
    steps:
      - name: Set unstable dated version
        id: version
        run: |-
          echo "JELLYFIN_VERSION=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV
      - name: Download openapi-head
        uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
        with:
          name: openapi-head
          path: openapi-head
      - name: Upload openapi.json (unstable) to repository server
        uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
        with:
          host: "${{ secrets.REPO_HOST }}"
          username: "${{ secrets.REPO_USER }}"
          key: "${{ secrets.REPO_KEY }}"
          source: openapi-head/openapi.json
          strip_components: 1
          target: "/srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
      - name: Move openapi.json (unstable) into place
        uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
        with:
          host: "${{ secrets.REPO_HOST }}"
          username: "${{ secrets.REPO_USER }}"
          key: "${{ secrets.REPO_KEY }}"
          debug: false
          script_stop: false
          script: |
            if ! test -d /run/workflows; then
                sudo mkdir -p /run/workflows
                sudo chown ${{ secrets.REPO_USER }} /run/workflows
            fi
            (
            flock -x -w 300 200 || exit 1
            TGT_DIR="/srv/repository/main/openapi"
            LAST_SPEC="$( ls -lt ${TGT_DIR}/unstable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
            # If new and previous spec don't differ (diff retcode 0), remove incoming and finish
            if diff /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/unstable/${LAST_SPEC} &>/dev/null; then
                rm -r /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
                exit 0
            fi
            # Move new spec into place
            sudo mv /srv/incoming/openapi/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
            # Delete previous jellyfin-openapi-unstable_previous.json
            sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
            # Move current jellyfin-openapi-unstable.json symlink to jellyfin-openapi-unstable_previous.json
            sudo mv ${TGT_DIR}/jellyfin-openapi-unstable.json ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
            # Create new jellyfin-openapi-unstable.json symlink
            sudo ln -s unstable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-unstable.json
            # Check that the previous openapi unstable spec link is correct
            if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-unstable_previous.json )" != "unstable/${LAST_SPEC}" ]]; then
                sudo rm ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
                sudo ln -s unstable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-unstable_previous.json
            fi
            ) 200>/run/workflows/openapi-unstable.lock

  publish-stable:
    name: OpenAPI - Publish Stable Spec
    if: ${{ startsWith(github.ref, 'refs/tags/v') && contains(github.repository_owner, 'jellyfin') }}
    runs-on: ubuntu-latest
    needs:
      - openapi-head
    steps:
      - name: Set version number
        id: version
        run: |-
          echo "JELLYFIN_VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV
      - name: Download openapi-head
        uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
        with:
          name: openapi-head
          path: openapi-head
      - name: Upload openapi.json (stable) to repository server
        uses: appleboy/scp-action@ff85246acaad7bdce478db94a363cd2bf7c90345 # v1.0.0
        with:
          host: "${{ secrets.REPO_HOST }}"
          username: "${{ secrets.REPO_USER }}"
          key: "${{ secrets.REPO_KEY }}"
          source: openapi-head/openapi.json
          strip_components: 1
          target: "/srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}"
      - name: Move openapi.json (stable) into place
        uses: appleboy/ssh-action@2ead5e36573f08b82fbfce1504f1a4b05a647c6f # v1.2.2
        with:
          host: "${{ secrets.REPO_HOST }}"
          username: "${{ secrets.REPO_USER }}"
          key: "${{ secrets.REPO_KEY }}"
          debug: false
          script_stop: false
          script: |
            if ! test -d /run/workflows; then
                sudo mkdir -p /run/workflows
                sudo chown ${{ secrets.REPO_USER }} /run/workflows
            fi
            (
            flock -x -w 300 200 || exit 1
            TGT_DIR="/srv/repository/main/openapi"
            LAST_SPEC="$( ls -lt ${TGT_DIR}/stable/ | grep 'jellyfin-openapi' | head -1 | awk '{ print $NF }' )"
            # If new and previous spec don't differ (diff retcode 0), remove incoming and finish
            if diff /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/${LAST_SPEC} &>/dev/null; then
                rm -r /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}
                exit 0
            fi
            # Move new spec into place
            sudo mv /srv/incoming/openapi/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}/openapi.json ${TGT_DIR}/stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json
            # Delete previous jellyfin-openapi-stable_previous.json
            sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
            # Move current jellyfin-openapi-stable.json symlink to jellyfin-openapi-stable_previous.json
            sudo mv ${TGT_DIR}/jellyfin-openapi-stable.json ${TGT_DIR}/jellyfin-openapi-stable_previous.json
            # Create new jellyfin-openapi-stable.json symlink
            sudo ln -s stable/jellyfin-openapi-${{ env.JELLYFIN_VERSION }}.json ${TGT_DIR}/jellyfin-openapi-stable.json
            # Check that the previous openapi stable spec link is correct
            if [[ "$( readlink ${TGT_DIR}/jellyfin-openapi-stable_previous.json )" != "stable/${LAST_SPEC}" ]]; then
                sudo rm ${TGT_DIR}/jellyfin-openapi-stable_previous.json
                sudo ln -s stable/${LAST_SPEC} ${TGT_DIR}/jellyfin-openapi-stable_previous.json
            fi
            ) 200>/run/workflows/openapi-stable.lock
