-
Notifications
You must be signed in to change notification settings - Fork 9
Develop #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop #7
Changes from all commits
2d265c1
f6c6901
0420c02
d86a590
37b8f05
71e08c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| **/bin/ | ||
| **/obj/ | ||
| **/.vs/ | ||
| **/.idea/ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # Copilot instructions for Resgrid Relay | ||
|
|
||
| ## Build, test, and run | ||
|
|
||
| - Full solution build (Windows required for the audio projects and test project): | ||
|
|
||
| ```powershell | ||
| dotnet build "Resgrid Audio.sln" | ||
| ``` | ||
|
|
||
| - Full test suite: | ||
|
|
||
| ```powershell | ||
| dotnet test "Resgrid Audio.sln" | ||
| ``` | ||
|
|
||
| - Run a single NUnit test method: | ||
|
|
||
| ```powershell | ||
| dotnet test ".\Resgrid.Audio.Tests\Resgrid.Audio.Tests.csproj" --filter "FullyQualifiedName~Resgrid.Audio.Tests.SmtpDispatchAddressParserTests.TryParse_Should_Map_Department_Address_To_Department_Dispatch_Code" | ||
| ``` | ||
|
|
||
| - For SMTP-only work on a non-Windows agent, build the cross-platform console target directly: | ||
|
|
||
| ```powershell | ||
| dotnet build ".\Resgrid.Audio.Relay.Console\Resgrid.Audio.Relay.Console.csproj" -f net10.0 | ||
| ``` | ||
|
|
||
| - Console worker entrypoints from the repo root: | ||
|
|
||
| ```powershell | ||
| dotnet run --project .\Resgrid.Audio.Relay.Console\Resgrid.Audio.Relay.Console.csproj -- run | ||
| dotnet run --project .\Resgrid.Audio.Relay.Console\Resgrid.Audio.Relay.Console.csproj -- setup | ||
| dotnet run --project .\Resgrid.Audio.Relay.Console\Resgrid.Audio.Relay.Console.csproj -- devices | ||
| dotnet run --project .\Resgrid.Audio.Relay.Console\Resgrid.Audio.Relay.Console.csproj -- monitor 0 | ||
| ``` | ||
|
|
||
| - There is no dedicated lint, analyzer, or `dotnet format` command checked into this repository. | ||
|
|
||
| ## High-level architecture | ||
|
|
||
| - `Resgrid.Audio.Relay.Console\Program.cs` is the live worker entrypoint. It selects `smtp` or `audio` mode, loads `appsettings.json` plus `RELAY_` environment variables into `RelayHostOptions`, and resolves relative paths from `AppContext.BaseDirectory`. | ||
|
|
||
| - SMTP mode is the current cross-platform relay path. The main flow is: | ||
|
|
||
| `Program.RunAsync` -> `SmtpRelayRunner` -> `RelayMailboxFilter` -> `RelayMessageStore` -> `SmtpDispatchAddressParser` / `DispatchListBuilder` -> `CallsApi` | ||
|
|
||
| `RelayMessageStore` parses the MIME message, derives a stable message id, suppresses duplicates through `ProcessedMessageStore`, optionally saves the raw `.eml` under `data\messages\`, creates the call, then uploads attachments as separate call files. `SmtpTelemetry` wraps the path with Serilog logging and optional Sentry/Countly reporting. | ||
|
|
||
| - Resgrid authentication and API access live in `Providers\Resgrid.Providers.ApiClient\V4`. `ResgridV4ApiClient` handles OIDC discovery, refresh-token exchange, token-cache persistence, and extraction of `CurrentUserId` from the access token `sub` claim for file uploads. `CallsApi` and `HealthApi` are the thin entrypoints used by the worker and audio pipeline. | ||
|
|
||
| - Audio mode is Windows-only and lives in `Resgrid.Audio.Core`. `AudioEvaluator` detects watcher triggers from DTMF/pure tones, `AudioProcessor` manages active watcher lifecycles and captured audio, and `ComService` translates watcher metadata into v4 dispatch lists, creates the call, then uploads the generated MP3 as a separate call file. | ||
|
|
||
| - `Resgrid.Audio.Relay` is a lightweight WPF monitor for selecting an input device and watching live audio metrics. It uses `AudioRecorder` directly; the actual relay/dispatch behavior stays in the console app plus `Resgrid.Audio.Core`. | ||
|
|
||
| - `Resgrid.Audio.Tests` covers the current seams: SMTP routing and telemetry, v4 dispatch-list formatting, and parts of the audio path. It targets `net10.0-windows`. | ||
|
|
||
| ## Key conventions | ||
|
|
||
| - Prefer compiled code over legacy files that still exist in the tree. `Resgrid.Audio.Relay.Console.csproj` removes `Args\`, `Commands\`, `Data\`, `Models\`, and `ConsoleTable.cs` from compilation. `Providers\Resgrid.Providers.ApiClient.csproj` removes `V3\**\*.cs`. `Resgrid.Audio.Relay.csproj` removes `ViewModel\**\*.cs` and older resource scaffolding. Do not treat those files as the active implementation path unless you are intentionally reviving them. | ||
|
|
||
| - OIDC/v4 is the only live Resgrid integration. New API work should go through `ResgridV4ApiClient`, `CallsApi`, and the V4 models instead of the legacy username/password V3 code. | ||
|
|
||
| - Configuration is split by mode. Host-level settings live in `Resgrid.Audio.Relay.Console\appsettings.json` plus `RELAY_` environment variables. Audio watcher definitions live in `Resgrid.Audio.Relay.Console\settings.json`. Keep new file-based settings consistent with the existing `AppContext.BaseDirectory` path resolution. | ||
|
|
||
| - SMTP routing is domain-driven. Configured department domains become `DispatchCodeType.Department`; configured group domains become `DispatchCodeType.Group`. `DispatchListBuilder` emits pipe-delimited `PREFIX:CODE` tokens, with a configurable department prefix and `G` as the default group prefix. | ||
|
|
||
| - SMTP duplicate suppression is persisted in `data\processed-messages.json`. Raw message retention uses `data\messages\`. Preserve the rollback behavior that removes a duplicate-registration entry if call creation or attachment upload fails after registration. | ||
|
|
||
| - `Watcher.Type` is semantic: `1` means department dispatch and `2` means group dispatch. `Watcher.AdditionalCodes` is always treated as extra group dispatch codes. When `Config.Multiple` is `false`, simultaneous hits are merged into the first active watcher; when it is `true`, they create separate active watcher flows. | ||
|
|
||
| - The Windows audio path still depends on checked-in binary references from `References\` and `packages\NAudio.1.8.4\...`. Avoid cleanup refactors that replace those references without revalidating the audio pipeline. | ||
|
|
||
| - `.editorconfig` uses tabs for `.cs` and CRLF line endings. Tests use NUnit with FluentAssertions. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| name: Auto approve | ||
|
|
||
| on: | ||
| issue_comment: | ||
| types: | ||
| - created | ||
|
|
||
| jobs: | ||
| auto-approve: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| pull-requests: write | ||
| steps: | ||
| - uses: actions/github-script@v9.0.0 | ||
| name: Approve LGTM Review | ||
| if: github.event.issue.pull_request && github.event.comment.user.login == 'ucswift' && contains(github.event.comment.body, 'Approve') | ||
| with: | ||
| script: | | ||
| github.rest.pulls.createReview({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: context.issue.number, | ||
| event: 'APPROVE', | ||
| body: 'This PR is approved.' | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| # This workflow runs build and tests on every pull request and every direct push | ||
| # to master. It also publishes artifacts and creates a GitHub Release only after | ||
| # a pull request is successfully merged to master. | ||
| # | ||
| # Required Docker Hub secrets: | ||
| # - DOCKERHUB_USERNAME | ||
| # - DOCKERHUB_TOKEN | ||
|
|
||
| name: Build and publish | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - master | ||
| - main | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, closed] | ||
|
Comment on lines
+12
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Align This workflow subscribes to Also applies to: 28-31, 59-59, 124-124, 197-197 🤖 Prompt for AI Agents |
||
|
|
||
| env: | ||
| DOTNET_VERSION: "10.0.x" | ||
| RELEASE_VERSION: "2.0.${{ github.run_number }}" | ||
| DOCKER_IMAGE_NAME: "resgridrelay" | ||
| DOCKER_IMAGE: "resgridllc/resgridrelay" | ||
|
|
||
| jobs: | ||
| build-and-test: | ||
| name: Build and test | ||
| if: | | ||
| github.event_name == 'push' || | ||
| github.event.action != 'closed' || | ||
| (github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master') | ||
| runs-on: windows-latest | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.action == 'closed' && github.event.pull_request.merge_commit_sha || github.sha }} | ||
|
|
||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ env.DOTNET_VERSION }} | ||
|
|
||
| - name: Restore dependencies | ||
| run: dotnet restore "Resgrid Audio.sln" | ||
|
|
||
| - name: Build | ||
| run: dotnet build "Resgrid Audio.sln" --configuration Release --no-restore | ||
|
|
||
| - name: Test | ||
| run: dotnet test "Resgrid Audio.sln" --configuration Release --no-build --verbosity normal | ||
|
|
||
| publish-apps: | ||
| name: Publish app assets | ||
| needs: build-and-test | ||
| if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master' | ||
| runs-on: windows-latest | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.pull_request.merge_commit_sha || github.sha }} | ||
|
|
||
| - name: Setup .NET | ||
| uses: actions/setup-dotnet@v4 | ||
| with: | ||
| dotnet-version: ${{ env.DOTNET_VERSION }} | ||
|
|
||
| - name: Restore dependencies | ||
| run: dotnet restore "Resgrid Audio.sln" | ||
|
|
||
| - name: Publish application outputs | ||
| shell: pwsh | ||
| run: | | ||
| $publishRoot = Join-Path $env:RUNNER_TEMP "publish" | ||
| $consoleLinux = Join-Path $publishRoot "relay-console-net10.0" | ||
| $consoleWindows = Join-Path $publishRoot "relay-console-net10.0-windows" | ||
| $monitorWindows = Join-Path $publishRoot "relay-monitor-net10.0-windows" | ||
|
|
||
| dotnet publish ".\Resgrid.Audio.Relay.Console\Resgrid.Audio.Relay.Console.csproj" --configuration Release --framework net10.0 --no-restore --output $consoleLinux /p:UseAppHost=false | ||
| dotnet publish ".\Resgrid.Audio.Relay.Console\Resgrid.Audio.Relay.Console.csproj" --configuration Release --framework net10.0-windows --no-restore --output $consoleWindows | ||
| dotnet publish ".\Resgrid.Audio.Relay\Resgrid.Audio.Relay.csproj" --configuration Release --framework net10.0-windows --no-restore --output $monitorWindows | ||
|
|
||
| - name: Package release assets | ||
| shell: pwsh | ||
| run: | | ||
| $publishRoot = Join-Path $env:RUNNER_TEMP "publish" | ||
| $assetRoot = Join-Path $env:RUNNER_TEMP "release-assets" | ||
|
|
||
| New-Item -ItemType Directory -Path $assetRoot -Force | Out-Null | ||
|
|
||
| $assets = @( | ||
| @{ Source = Join-Path $publishRoot "relay-console-net10.0"; File = Join-Path $assetRoot "resgridrelay-console-net10.0-$env:RELEASE_VERSION.zip" }, | ||
| @{ Source = Join-Path $publishRoot "relay-console-net10.0-windows"; File = Join-Path $assetRoot "resgridrelay-console-net10.0-windows-$env:RELEASE_VERSION.zip" }, | ||
| @{ Source = Join-Path $publishRoot "relay-monitor-net10.0-windows"; File = Join-Path $assetRoot "resgridrelay-monitor-net10.0-windows-$env:RELEASE_VERSION.zip" } | ||
| ) | ||
|
|
||
| foreach ($asset in $assets) | ||
| { | ||
| if (Test-Path $asset.File) | ||
| { | ||
| Remove-Item $asset.File -Force | ||
| } | ||
|
|
||
| Compress-Archive -Path (Join-Path $asset.Source '*') -DestinationPath $asset.File | ||
| } | ||
|
|
||
| - name: Upload release assets | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: release-assets | ||
| path: ${{ runner.temp }}\release-assets\*.zip | ||
| if-no-files-found: error | ||
|
|
||
| docker-build-and-push: | ||
| name: Publish Docker image | ||
| needs: build-and-test | ||
| if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master' | ||
| runs-on: ubuntu-latest | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ github.event.pull_request.merge_commit_sha || github.sha }} | ||
|
|
||
| - name: Validate Docker Hub configuration | ||
| shell: bash | ||
| env: | ||
| DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| run: | | ||
| if [ -z "$DOCKERHUB_USERNAME" ]; then | ||
| echo "DOCKERHUB_USERNAME secret is required." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Login to Docker Hub | ||
| uses: docker/login-action@v3 | ||
| with: | ||
| username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
| password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
|
|
||
| - name: Docker meta | ||
| id: meta | ||
| uses: docker/metadata-action@v5 | ||
| with: | ||
| images: ${{ env.DOCKER_IMAGE }} | ||
| tags: | | ||
| type=raw,value=${{ env.RELEASE_VERSION }} | ||
| type=raw,value=latest | ||
| type=sha,prefix=sha- | ||
|
|
||
| - name: Build and push Docker image | ||
| uses: docker/build-push-action@v6 | ||
| with: | ||
| context: . | ||
| file: ./Dockerfile | ||
| push: true | ||
| tags: ${{ steps.meta.outputs.tags }} | ||
| labels: ${{ steps.meta.outputs.labels }} | ||
| cache-from: type=gha | ||
| cache-to: type=gha,mode=max | ||
|
|
||
| - name: Create Docker release metadata | ||
| shell: bash | ||
| run: | | ||
| mkdir -p "$RUNNER_TEMP/docker-release" | ||
| { | ||
| echo "Docker image:" | ||
| echo "${{ env.DOCKER_IMAGE }}" | ||
| echo | ||
| echo "Tags:" | ||
| printf '%s\n' "${{ steps.meta.outputs.tags }}" | ||
| } > "$RUNNER_TEMP/docker-release/docker-image-tags.txt" | ||
|
|
||
| - name: Upload Docker release metadata | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: docker-release-metadata | ||
| path: ${{ runner.temp }}/docker-release/docker-image-tags.txt | ||
| if-no-files-found: error | ||
|
|
||
| github-release: | ||
| name: Create GitHub release | ||
| needs: [publish-apps, docker-build-and-push] | ||
| if: github.event.action == 'closed' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'master' | ||
| runs-on: ubuntu-latest | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: read | ||
|
|
||
| steps: | ||
| - name: Download app release assets | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: release-assets | ||
| path: ${{ runner.temp }}/release-assets | ||
|
|
||
| - name: Download Docker release metadata | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: docker-release-metadata | ||
| path: ${{ runner.temp }}/docker-release | ||
|
|
||
| - name: Get merged PR info | ||
| id: pr-info | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const fs = require('fs'); | ||
| const body = (context.payload.pull_request.body || '') | ||
| .replace(/##\s*Summary by CodeRabbit[\s\S]*/i, '') | ||
| .trim() || 'No release notes provided.'; | ||
| const notes = [ | ||
| body, | ||
| '', | ||
| '## Docker image', | ||
| `- \`${{ env.DOCKER_IMAGE }}:${{ env.RELEASE_VERSION }}\``, | ||
| `- \`${{ env.DOCKER_IMAGE }}:latest\`` | ||
| ].join('\n'); | ||
| fs.writeFileSync('release_notes.md', notes); | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| tag_name: ${{ env.RELEASE_VERSION }} | ||
| name: ${{ env.RELEASE_VERSION }} | ||
| body_path: release_notes.md | ||
| target_commitish: ${{ github.event.pull_request.merge_commit_sha || github.sha }} | ||
| files: | | ||
| ${{ runner.temp }}/release-assets/*.zip | ||
| ${{ runner.temp }}/docker-release/docker-image-tags.txt | ||
| draft: false | ||
| prerelease: false | ||
| make_latest: true | ||
| fail_on_unmatched_files: true | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build | ||
| WORKDIR /src | ||
|
|
||
| COPY . . | ||
| RUN dotnet restore "Resgrid.Audio.Relay.Console/Resgrid.Audio.Relay.Console.csproj" -p:TargetFramework=net10.0 | ||
| RUN dotnet publish "Resgrid.Audio.Relay.Console/Resgrid.Audio.Relay.Console.csproj" -c Release -f net10.0 -o /app/publish /p:UseAppHost=false | ||
|
|
||
| FROM mcr.microsoft.com/dotnet/runtime:10.0 AS final | ||
| WORKDIR /app | ||
|
|
||
| COPY --from=build /app/publish . | ||
|
|
||
| ENV RELAY_Mode=smtp | ||
| ENV RELAY_Smtp__Port=2525 | ||
|
|
||
| EXPOSE 2525 | ||
|
|
||
| ENTRYPOINT ["dotnet", "Resgrid.Audio.Relay.Console.dll"] |
Uh oh!
There was an error while loading. Please reload this page.