PilotCarrierClient.psm1

# Client for the Pilot supply chain carrier API: token, reads, and the two carrier writes.

function New-PilotSession {
  param(
    [Parameter(Mandatory)][string]$BaseUrl,
    [Parameter(Mandatory)][string]$TokenUrl,
    [Parameter(Mandatory)][string]$Scope,
    [Parameter(Mandatory)][pscredential]$Credential,
    [Parameter(Mandatory)][int]$CarrierId,
    [int]$TimeoutSec = 100
  )
  [pscustomobject]@{
    BaseUrl    = $BaseUrl.TrimEnd('/')
    TokenUrl   = $TokenUrl
    Scope      = $Scope
    Credential = $Credential
    CarrierId  = $CarrierId
    TimeoutSec = $TimeoutSec
    Token      = $null
    Expires    = [datetime]::MinValue
    Tokens     = 0
  }
}

function Get-PilotToken {
  param([Parameter(Mandatory)]$Session, [switch]$Force)
  if (-not $Force -and $Session.Token -and [datetime]::UtcNow -lt $Session.Expires) { return $Session.Token }
  $response = Invoke-RestMethod -Method Post -Uri $Session.TokenUrl -TimeoutSec $Session.TimeoutSec `
    -ContentType 'application/x-www-form-urlencoded' -Body @{
      client_id     = $Session.Credential.UserName
      client_secret = $Session.Credential.GetNetworkCredential().Password
      scope         = $Session.Scope
      grant_type    = 'client_credentials'
    }
  if (-not $response.access_token) { throw 'Pilot token response contained no access_token.' }
  # refresh a minute early so a token never expires mid-request
  $Session.Token = $response.access_token
  $Session.Expires = [datetime]::UtcNow.AddSeconds([int]$response.expires_in - 60)
  $Session.Tokens++
  $Session.Token
}

function Invoke-PilotRequest {
  param(
    [Parameter(Mandatory)]$Session,
    [Parameter(Mandatory)][string]$Path,
    [ValidateSet('GET', 'PUT')][string]$Method = 'GET',
    [hashtable]$Query,
    $Body
  )
  $uri = '{0}/{1}' -f $Session.BaseUrl, $Path.TrimStart('/')
  if ($Query -and $Query.Count) {
    $uri += '?' + (($Query.GetEnumerator() | Sort-Object Key | ForEach-Object {
      '{0}={1}' -f [uri]::EscapeDataString([string]$_.Key), [uri]::EscapeDataString([string]$_.Value)
    }) -join '&')
  }
  $request = @{
    Method     = $Method
    Uri        = $uri
    TimeoutSec = $Session.TimeoutSec
    Headers    = @{ Authorization = "Bearer $(Get-PilotToken $Session)"; Accept = 'application/json' }
  }
  if ($PSBoundParameters.ContainsKey('Body')) {
    # the writes take a JSON array under a form content type; that pairing is what the API accepts
    $request.Body = ConvertTo-Json -InputObject @($Body) -Depth 12 -Compress
    $request.ContentType = 'application/x-www-form-urlencoded'
  }
  Invoke-RestMethod @request
}

function Get-PilotData {
  param(
    [Parameter(Mandatory)]$Session,
    [Parameter(Mandatory)][string]$Path,
    [hashtable]$Query = @{}
  )
  $response = Invoke-PilotRequest -Session $Session -Path $Path -Query $Query
  if ($response.status -ne 'success') { throw "Pilot $Path read failed: $($response.status)" }
  if ($response.data -and $response.data.PSObject.Properties.Name -contains 'record') { return @($response.data.record) }
  @($response.data)
}

function Get-PilotOrder {
  param(
    [Parameter(Mandatory)]$Session,
    [Parameter(Mandatory)][datetime]$StartDate,
    [Parameter(Mandatory)][datetime]$EndDate
  )
  Get-PilotData $Session 'order' @{
    carrierId = $Session.CarrierId
    startDate = $StartDate.ToString('yyyy-MM-dd')
    endDate   = $EndDate.ToString('yyyy-MM-dd')
  }
}

function Get-PilotBol { param([Parameter(Mandatory)]$Session) Get-PilotData $Session 'bol' @{ carrierId = $Session.CarrierId } }
function Get-PilotLocation { param([Parameter(Mandatory)]$Session) Get-PilotData $Session 'location' @{ carrierId = $Session.CarrierId } }
function Get-PilotTerminal { param([Parameter(Mandatory)]$Session) Get-PilotData $Session 'terminal' @{ carrierId = $Session.CarrierId } }
function Get-PilotContract { param([Parameter(Mandatory)]$Session) Get-PilotData $Session 'contract' @{ carrierId = $Session.CarrierId } }

function Set-PilotBol {
  param([Parameter(Mandatory)]$Session, [Parameter(Mandatory)]$Payload)
  Invoke-PilotRequest -Session $Session -Path 'bol' -Method PUT -Body $Payload
}

function Set-PilotDocument {
  param([Parameter(Mandatory)]$Session, [Parameter(Mandatory)]$Payload)
  Invoke-PilotRequest -Session $Session -Path 'document' -Method PUT -Body $Payload
}

Export-ModuleMember -Function New-PilotSession, Get-PilotToken, Invoke-PilotRequest, Get-PilotData,
  Get-PilotOrder, Get-PilotBol, Get-PilotLocation, Get-PilotTerminal, Get-PilotContract,
  Set-PilotBol, Set-PilotDocument