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'