dutybeat.psm1
|
# DutyBeat public API — PowerShell client. # Works on Windows PowerShell 5.1 and PowerShell 7+ (Core). No external dependencies. $script:DefaultBaseUri = 'https://api.dutybeat.com' # Session state set by Connect-DutyBeat (per module load). $script:DutyBeatContext = @{ ApiKey = $null; BaseUri = $script:DefaultBaseUri } function Connect-DutyBeat { <# .SYNOPSIS Stores the API key (and optional base URL) for subsequent calls in this session. .EXAMPLE Connect-DutyBeat -ApiKey 'db_live_...' #> [CmdletBinding()] param( [string]$ApiKey, [string]$BaseUri = $script:DefaultBaseUri ) $key = if ($ApiKey) { $ApiKey } elseif ($env:DUTYBEAT_API_KEY) { $env:DUTYBEAT_API_KEY } else { $null } if (-not $key) { throw "Provide -ApiKey or set the DUTYBEAT_API_KEY environment variable." } $script:DutyBeatContext = @{ ApiKey = $key; BaseUri = $BaseUri.TrimEnd('/') } Write-Verbose "Connected to $($script:DutyBeatContext.BaseUri)" } function Invoke-DbApi { # Private transport: resolves the key/base URL, builds the URI, retries on 429/5xx honouring # Retry-After, and maps the API error envelope to a terminating error. Not exported. [CmdletBinding()] param( [string]$Method = 'GET', [Parameter(Mandatory)][string]$Path, [hashtable]$Query, [string]$ApiKey, [string]$BaseUri, [int]$MaxRetries = 2 ) $key = if ($ApiKey) { $ApiKey } elseif ($script:DutyBeatContext.ApiKey) { $script:DutyBeatContext.ApiKey } elseif ($env:DUTYBEAT_API_KEY) { $env:DUTYBEAT_API_KEY } else { $null } if (-not $key) { throw "No API key. Run Connect-DutyBeat -ApiKey '...' or set DUTYBEAT_API_KEY." } $base = if ($BaseUri) { $BaseUri.TrimEnd('/') } elseif ($script:DutyBeatContext.BaseUri) { $script:DutyBeatContext.BaseUri } else { $script:DefaultBaseUri } $uri = "$base$Path" if ($Query -and $Query.Count -gt 0) { $pairs = foreach ($k in $Query.Keys) { "$k=$([uri]::EscapeDataString([string]$Query[$k]))" } $uri += '?' + ($pairs -join '&') } $headers = @{ Authorization = "Bearer $key"; Accept = 'application/json' } for ($attempt = 0; ; $attempt++) { try { return Invoke-RestMethod -Method $Method -Uri $uri -Headers $headers -ErrorAction Stop } catch { $status = $null try { $status = [int]$_.Exception.Response.StatusCode } catch {} if (($status -in 429, 500, 502, 503, 504) -and $attempt -lt $MaxRetries) { $delay = [Math]::Min([Math]::Pow(2, $attempt), 8) try { $ra = $_.Exception.Response.Headers['Retry-After'] if ($ra) { $delay = [double]$ra } } catch {} Start-Sleep -Seconds $delay continue } $code = $null $message = $_.Exception.Message if ($_.ErrorDetails -and $_.ErrorDetails.Message) { try { $body = $_.ErrorDetails.Message | ConvertFrom-Json if ($body.error) { $code = $body.error.code; $message = $body.error.message } } catch {} } $prefix = 'DutyBeat API error' if ($status) { $prefix += " $status" } if ($code) { $prefix += " ($code)" } throw "${prefix}: $message" } } } function Get-DbUser { <# .SYNOPSIS Gets a user's full record (GET /api/v1/users/:id). .PARAMETER UserId The user's id within your company. .PARAMETER IncludeFolders Also return the employee's document folders (metadata only). .EXAMPLE Get-DbUser -UserId '9f1c...' -IncludeFolders #> [CmdletBinding()] param( [Parameter(Mandatory, Position = 0, ValueFromPipelineByPropertyName)][string]$UserId, [switch]$IncludeFolders, [string]$ApiKey, [string]$BaseUri ) process { $query = @{} if ($IncludeFolders) { $query['include_folders'] = 'true' } $response = Invoke-DbApi -Method 'GET' -Path "/api/v1/users/$UserId" -Query $query -ApiKey $ApiKey -BaseUri $BaseUri $response.data } } Export-ModuleMember -Function 'Connect-DutyBeat', 'Get-DbUser' |