# Legacy employee sync deployment notes

> [!WARNING]
> This earlier plan uses the Az.Accounts module. For the current implementation, follow the [current setup guide](./0.%20Readme.md) or the [Windows setup guide](./README.md).

## 0. Confirm the runtime version

PowerShell 7.6 is currently preview-only and **Windows-only** (Premium/Dedicated/Consumption). Flex Consumption on Linux currently runs **PowerShell 7.4**. Check the Function App's `FUNCTIONS_WORKER_RUNTIME_VERSION` app setting — if it's not `~7.4`, this code will still run fine (nothing here is 7.6-specific) but confirm before you deploy so there are no surprises at runtime resolution.

## 1. Vendor the Az.Accounts module

Flex Consumption does not support PowerShell managed dependencies, so `requirements.psd1` alone will not fetch the module.

Run this on a machine with PowerShell 7.4+ and internet access, from the function app root:

```powershell
Save-Module -Name Az.Accounts -Path ./Modules -Repository PSGallery
```

This creates `./Modules/Az.Accounts/<version>/...`. That folder must be included in the deployment package (zip deploy) alongside `host.json`, `profile.ps1`, `requirements.psd1`, and `HttpTrigger/`. Final layout:

```
/
  host.json
  profile.ps1
  requirements.psd1
  HttpTrigger/
    function.json
    run.ps1
  Modules/
    Az.Accounts/
      <version>/...
```

## 2. Enable a system-assigned managed identity on the Function App

Portal: Function App → Identity → System assigned → On.

## 3. Grant that identity two RBAC roles

|Resource|Role|Why|
|---|---|---|
|`kv-cia-employee-sync`|`Key Vault Secrets User`|Lets the Key Vault reference app setting resolve `ciaETLsecret`|
|Storage Account|`Storage Table Data Contributor`|Lets the function read/write the Employees table and read the OU mapping table, via Entra ID auth (no account keys)|

## 4. Create and seed the organisationUnit mapping table

Create a second table in the same storage account — `OrganisationUnitMappings` by default (override with the `OuMappingTableName` app setting if you want a different name). Every row uses a fixed `PartitionKey` so the function's lookup is always a single-partition query:

|PartitionKey|RowKey|MappedGuid|
|---|---|---|
|`Mapping`|`GRO`|`<guid>`|
|`Mapping`|`ABC`|`<guid>`|

`RowKey` is the 3-character prefix (uppercase — the function uppercases the incoming value before comparing, so casing in the source data doesn't matter). Add a row per prefix; any `organisationunit` value that doesn't match one gets logged as a warning and left unmapped rather than written with a blank/null id, so unmapped records are easy to find in the logs without failing the whole batch.

Seed it however's convenient — Storage Explorer's table editor is the quickest for a handful of rows, or `Add-AzTableRow`-style REST calls if you want it scripted.

## 5. App settings to configure on the Function App

|Setting|Value|
|---|---|
|`CiaEtlSecret`|`@Microsoft.KeyVault(SecretUri=https://kv-cia-employee-sync.vault.azure.net/secrets/ciaETLsecret/)`|
|`TableStorageAccountName`|Your storage account name, e.g. `mystorageacct`|
|`EmployeeTableName`|`Employees` (or your preferred table name — must already exist)|
|`OuMappingTableName`|`OrganisationUnitMappings` (optional — this is the default if unset)|

Neither table is auto-created — create both once via the portal, Storage Explorer, or `New-AzStorageTable`.

## 6. Testing the bearer token

Auth is now a straightforward comparison — the ETL sends `Authorization: Bearer <secret>`, the Function compares it (constant-time) against `ciaETLsecret` from Key Vault. To sanity check before wiring up the real ETL step:

```powershell
$uri    = 'https://<functionapp>.azurewebsites.net/api/employees/sync'
$secret = 'the-actual-secret-value'   # from Key Vault, for local testing only
$body   = Get-Content ./sample-payload.xml -Raw

# Should succeed (200) with a summary of upserted/skipped/failed counts
Invoke-RestMethod -Method Post -Uri $uri -Headers @{ Authorization = "Bearer $secret" } -ContentType 'application/xml' -Body $body

# Should fail with 401 — confirms the check actually rejects bad tokens, not just accepts everything
try {
    Invoke-RestMethod -Method Post -Uri $uri -Headers @{ Authorization = 'Bearer wrong-secret' } -ContentType 'application/xml' -Body $body
} catch {
    $_.Exception.Response.StatusCode
}
```

If the first call doesn't come back 200, `$_.ErrorDetails.Message` in a `catch` block will show the JSON error body (`Missing or malformed Authorization header`, `Invalid token`, `Could not load organisationUnit mapping table`, etc.) rather than a generic HTTP error.

## 7. Endpoint

`POST https://<functionapp>.azurewebsites.net/api/employees/sync` (no function key required — the bearer token check is the actual gate; consider still restricting via `authLevel` + a function key as defense-in-depth if you want belt-and-suspenders).
