Private/Invoke/Invoke-AspireRequest.ps1

<#
.SYNOPSIS
    Generic function to invoke requests to the Aspire API.
.DESCRIPTION
    Handles GET, POST, PUT, DELETE requests with Bearer token authentication.
.PARAMETER Method
    HTTP method (GET, POST, PUT, DELETE).
.PARAMETER Path
    API path (e.g., /WorkTickets).
.PARAMETER QueryParams
    Optional hashtable of query parameters (e.g., @{ '$filter' = 'somefilter' }).
.PARAMETER Body
    Optional body for POST/PUT (as PSCustomObject or hashtable, auto-converted to JSON).
.PARAMETER ApiVersion
    Optional API version query param (default: 1.0 from spec).
.EXAMPLE
    Invoke-AspireRequest -Method GET -Path "/WorkTickets"
#>

function Invoke-AspireRequest {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
        [string]$Method,
        [Parameter(Mandatory = $true)]
        [string]$Path,
        [hashtable]$QueryParams = @{},
        [object]$Body = $null,
        [string]$ApiVersion = "1.0"
    )

    if (-not $script:AspireToken) {
        Write-Error "No authentication token. Call Connect-AspireAPI first."
        return
    }

    $uri = "$script:BaseUrl$Path"
    if ($QueryParams.Count -gt 0) {
        $queryString = ($QueryParams.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join '&'
        $uri += "?$queryString"
    }
    if ($ApiVersion) {
        $uri += ($QueryParams.Count -gt 0 ? "&" : "?") + "api-version=$ApiVersion"
    }

    $headers = @{
        'Authorization' = "Bearer $script:AspireToken"
        'Content-Type' = 'application/json'
    }

    $invokeParams = @{
        Uri = $uri
        Method = $Method
        Headers = $headers
    }

    if ($Body -and ($Method -eq 'POST' -or $Method -eq 'PUT')) {
        $invokeParams.Body = $Body | ConvertTo-Json -Depth 10
    }

    try {
        $response = Invoke-RestMethod @invokeParams
        return $response
    } catch {
        if ($_.Exception.Response -and $_.Exception.Response.StatusCode -eq 401 -and $script:AspireRefreshToken) {
            # Attempt to refresh the token
            $headers = @{
                'Content-Type' = 'application/json'
            }
            $authBody = @{
                RefreshToken = $script:AspireRefreshToken
            } | ConvertTo-Json
            $refreshUri = "$script:BaseUrl/Authorization/RefreshToken"
            try {
                $refreshResponse = Invoke-RestMethod -Uri $refreshUri -Method Post -Body $authBody -Headers $headers
                $script:AspireToken = $refreshResponse.Token
                $script:AspireRefreshToken = $refreshResponse.RefreshToken
                # Update headers with new token
                $invokeParams.Headers['Authorization'] = "Bearer $script:AspireToken"
                # Retry the request
                $response = Invoke-RestMethod @invokeParams
                return $response
            } catch {
                Write-Error "Token refresh failed: $($_.Exception.Message)"
            }
        } else {
            Write-Error "Request failed: $($_.Exception.Message)"
        }
    }
}