Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 137 additions & 8 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
@@ -1,26 +1,155 @@
# This workflow will build a .NET project
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
# This workflow builds, tests, and releases the Resgrid solution.
# - PRs on any branch: build + test
# - Merged PRs into master: build + test + Docker Hub publish + GitHub Release

name: .NET

on:
pull_request:
branches: [ "master", "develop" ]
types: [opened, synchronize, reopened]
push:
branches: [ "master" ]

jobs:
build:
permissions:
contents: read

env:
DOTNET_VERSION: '9.0.x'

jobs:
# ───────────────────────────────────────────────
# Build & Test – runs on every PR and push to master
# ───────────────────────────────────────────────
build-and-test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Restore dependencies
run: dotnet restore

- name: Build
run: dotnet build --no-restore
run: dotnet build --no-restore --configuration Release

- name: Test
run: dotnet test --no-build --verbosity normal
run: dotnet test --no-build --configuration Release --verbosity normal

# ───────────────────────────────────────────────
# Docker Build & Push – only on merged PRs to master
# Uses a matrix to build each project's Dockerfile
# ───────────────────────────────────────────────
docker-build-and-push:
needs: build-and-test
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
include:
- image: resgridllc/resgridwebcore
dockerfile: Web/Resgrid.Web/Dockerfile
- image: resgridllc/resgridwebservices
dockerfile: Web/Resgrid.Web.Services/Dockerfile
- image: resgridllc/resgridwebevents
dockerfile: Web/Resgrid.Web.Eventing/Dockerfile
- image: resgridllc/resgridwebmcp
dockerfile: Web/Resgrid.Web.Mcp/Dockerfile
- image: resgridllc/resgridworkersconsole
dockerfile: Workers/Resgrid.Workers.Console/Dockerfile

steps:
- uses: actions/checkout@v4

- 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: Extract version
id: version
run: |
VERSION="4.${{ github.run_number }}.0"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ matrix.image }}
tags: |
type=raw,value=${{ steps.version.outputs.version }}
type=raw,value=latest
type=sha,prefix=

- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile }}
push: true
build-args: BUILD_VERSION=${{ steps.version.outputs.version }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

# ───────────────────────────────────────────────
# GitHub Release – created after all Docker images are pushed
# Uses the body of the merged PR as release notes
# ───────────────────────────────────────────────
github-release:
needs: docker-build-and-push
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest

permissions:
contents: write

steps:
- uses: actions/checkout@v4

- name: Extract version
id: version
run: |
VERSION="4.${{ github.run_number }}.0"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Get merged PR info
id: pr-info
uses: actions/github-script@v7
with:
script: |
const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: context.sha,
});
const pr = prs.data.find(p => p.merged_at);
const fs = require('fs');
if (pr) {
fs.writeFileSync('release_notes.md', pr.body || 'No release notes provided.');
} else {
fs.writeFileSync('release_notes.md', 'No release notes provided.');
}

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.version.outputs.version }}
name: Release v${{ steps.version.outputs.version }}
body_path: release_notes.md
draft: false
prerelease: false
Comment on lines +146 to +153
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

❓ Verification inconclusive

softprops/action-gh-release@v2 existing release tag behavior documentation


softprops/action-gh-release@v2 documents this “existing tag/release” behavior in its README / Marketplace listing:

  • If the tag already has a GitHub Release, the action updates that existing release (rather than failing/creating a second release), including uploading/updating the configured assets. [1][2]
  • If you do not explicitly set release fields (name, body, draft, prerelease, etc.) and a release already exists for the tag, the existing release keeps its original values for those fields (i.e., they’re retained, not overwritten). [1][2]
  • Related: asset replacement is controlled by overwrite_files (documented as defaulting to true). [1][2]

Sources:
[1] softprops/action-gh-release README (GitHub)
[2] “GH Release” action listing (GitHub Marketplace)


Minor: workflow re-runs may regenerate existing release tags.

If a workflow run is manually re-triggered, github.run_number remains the same, so the tag v4.${{ github.run_number }}.0 would already exist. softprops/action-gh-release@v2 will update the existing release in this case, which is safe but means re-runs are not idempotent — the release notes and other fields will be regenerated from the latest state.

🤖 Prompt for AI Agents
In @.github/workflows/dotnet.yml around lines 146 - 153, The Create GitHub
Release step (uses: softprops/action-gh-release@v2) is not idempotent because
tag_name (v${{ steps.version.outputs.version }}) can already exist on re-runs;
change the workflow to check for an existing tag before running this step and
only invoke the softprops action when the tag does not exist. Implement a prior
step (e.g., a small actions/github-script or curl/gh check) that queries the
repo tags for the computed tag (use the same expression as tag_name) and sets an
output/boolean like tag_exists, then conditionally run the Create GitHub Release
step using that output so existing tags are detected and the release creation is
skipped or updated intentionally.

make_latest: true
fail_on_unmatched_files: false
14 changes: 9 additions & 5 deletions Core/Resgrid.Services/VoiceService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,14 @@ public VoiceService(IDepartmentVoiceRepository departmentVoiceRepository, IDepar
public async Task<bool> CanDepartmentUseVoiceAsync(int departmentId)
{
var addonPlans = await _subscriptionsService.GetAllAddonPlansByTypeAsync(PlanAddonTypes.PTT);
var addonPayment = await _subscriptionsService.GetCurrentPaymentAddonsForDepartmentAsync(departmentId, addonPlans.Select(x => x.PlanAddonId).ToList());

if (addonPayment != null && addonPayment.Count > 0)
return true;
if (addonPlans != null && addonPlans.Any())
{
var addonPayment = await _subscriptionsService.GetCurrentPaymentAddonsForDepartmentAsync(departmentId, addonPlans.Select(x => x.PlanAddonId).ToList());

if (addonPayment != null && addonPayment.Count > 0)
return true;
}

return false;
}
Expand All @@ -61,7 +65,7 @@ public async Task<DepartmentVoice> GetVoiceSettingsForDepartmentAsync(int depart
var voice = await GetOrCreateDepartmentVoiceRecordAsync(department);
var users = await _departmentsService.GetAllUsersForDepartmentAsync(departmentId, true, true);
var userProfiles = await _userProfileService.GetAllProfilesForDepartmentAsync(departmentId, true);

if (users != null && users.Any())
{
foreach (var user in users)
Expand Down Expand Up @@ -253,7 +257,7 @@ public async Task<DepartmentVoiceChannel> GetVoiceChannelByIdAsync(string voiceC
}
}
}

return await _departmentVoiceChannelRepository.SaveOrUpdateAsync(voiceChannel, cancellationToken, true);
}

Expand Down
33 changes: 30 additions & 3 deletions Resgrid.sln
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@


Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.32112.339
Expand Down Expand Up @@ -78,6 +78,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resgrid.Workers.Console", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resgrid.Web.Eventing", "Web\Resgrid.Web.Eventing\Resgrid.Web.Eventing.csproj", "{907CA744-0D45-4928-AC26-5724BA6A38EF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resgrid.Web.Mcp", "Web\Resgrid.Web.Mcp\Resgrid.Web.Mcp.csproj", "{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resgrid.Providers.Voip", "Providers\Resgrid.Providers.Voip\Resgrid.Providers.Voip.csproj", "{FFCBA7D4-853A-4D25-935B-F242851752EE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Resgrid.Repositories.NoSqlRepository", "Repositories\Resgrid.Repositories.NoSqlRepository\Resgrid.Repositories.NoSqlRepository.csproj", "{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E}"
Expand Down Expand Up @@ -706,8 +708,32 @@ Global
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Release|x86.Build.0 = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|Any CPU.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|Any CPU.Build.0 = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|x86.ActiveCfg = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|x86.Build.0 = Release|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|x86.ActiveCfg = Debug|Any CPU
{907CA744-0D45-4928-AC26-5724BA6A38EF}.Staging|x86.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|Any CPU.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|x86.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Azure|x86.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|Any CPU.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|Any CPU.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|x86.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Cloud|x86.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|x86.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Debug|x86.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|Any CPU.ActiveCfg = Docker|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|Any CPU.Build.0 = Docker|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|x86.ActiveCfg = Docker|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Docker|x86.Build.0 = Docker|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|Any CPU.Build.0 = Release|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|x86.ActiveCfg = Release|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Release|x86.Build.0 = Release|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|Any CPU.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|Any CPU.Build.0 = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|x86.ActiveCfg = Debug|Any CPU
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678}.Staging|x86.Build.0 = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|Any CPU.ActiveCfg = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|Any CPU.Build.0 = Debug|Any CPU
{FFCBA7D4-853A-4D25-935B-F242851752EE}.Azure|x86.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -882,6 +908,7 @@ Global
{C89A962F-75AC-4D0B-9B8A-B481F63810EC} = {DBB9862A-C008-4C3F-A9DB-320429E4A07F}
{477AAB02-9403-44BE-B912-3DA98660F307} = {DBB9862A-C008-4C3F-A9DB-320429E4A07F}
{907CA744-0D45-4928-AC26-5724BA6A38EF} = {53B024F9-E293-42F1-BA67-7F68C3F3C243}
{A8B9C1D2-E3F4-5678-9ABC-DEF012345678} = {53B024F9-E293-42F1-BA67-7F68C3F3C243}
{FFCBA7D4-853A-4D25-935B-F242851752EE} = {F06D475C-635C-4DE4-82BA-C49A90BA8FCD}
{086A3F5A-F7D3-4E38-B9CD-B422DD3A709E} = {206D5D48-99B0-4913-B1E2-4BA11D021740}
{E39D63BA-5CCA-43EC-A3F9-375FA87EFE2F} = {D43D1D6B-66A9-4A57-9EA3-8DECC92FA583}
Expand Down
6 changes: 6 additions & 0 deletions Web/Resgrid.Web.Mcp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
bin/
obj/
*.user
*.suo
.vs/

Loading
Loading