Skip to content
Open
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
41 changes: 41 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
version: 2
updates:

# 1. NuGet Package Dependencies (.csproj files)
- package-ecosystem: "nuget"
directory: "/"
schedule:
interval: "weekly"
day: "tuesday"
open-pull-requests-limit: 10

# CRITICAL: Prevent major version bumps across all packages (Stability First)
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]

# Grouping Strategy to reduce PR noise (best practice)
groups:
test-dependencies:
# Group xUnit, Moq, FluentAssertions, etc.
patterns:
- "xunit*"
- "Moq"
- "FluentAssertions"
- "*Test*"
- "Testcontainers*"
- "coverlet*"

framework-dependencies:
# Group all core Microsoft Extensions and system packages
patterns:
- "Microsoft.Extensions.*"
- "System.*"
- "Microsoft.AspNetCore.*"
- "Microsoft.EntityFrameworkCore.*"

# 2. GitHub Actions (Keep workflows secure and updated)
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
48 changes: 48 additions & 0 deletions .github/workflows/ci-dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: CI Dependabot

on:
pull_request:
branches: [ main ]

jobs:
validate-deps:
if: github.actor == 'dependabot[bot]'
name: Validate Dependencies
runs-on: ubuntu-latest

services:
postgres:
image: public.ecr.aws/docker/library/postgres:15-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d test_db"
--health-interval 5s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Restore dependencies
run: dotnet restore

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

- name: Run Tests
env:
ConnectionStrings__TestDb: "Host=localhost;Port=5432;Database=test_db;Username=postgres;Password=postgres;Include Error Detail=true"
run: |
dotnet test Rgt.Space.Tests/Rgt.Space.Tests.csproj \
--configuration Release \
--no-build
76 changes: 76 additions & 0 deletions .github/workflows/ci-fast.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: CI Fast

on:
push:
branches-ignore:
- main # Main is handled by ci-quality.yml
pull_request:
types: [opened, synchronize, reopened]

jobs:
build-test:
runs-on: ubuntu-latest

services:
postgres:
image: public.ecr.aws/docker/library/postgres:15-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d test_db"
--health-interval 5s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Restore dependencies
run: dotnet restore

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

- name: Run Unit Tests
run: |
dotnet test Rgt.Space.Tests/Rgt.Space.Tests.csproj \
--configuration Release \
--filter "Category!=Integration" \
--collect:"XPlat Code Coverage"
timeout-minutes: 10

- name: Run Integration Tests
env:
ConnectionStrings__TestDb: "Host=localhost;Port=5432;Database=test_db;Username=postgres;Password=postgres;Include Error Detail=true"
run: |
dotnet test Rgt.Space.Tests/Rgt.Space.Tests.csproj \
--configuration Release \
--filter "Category=Integration" \
--collect:"XPlat Code Coverage"
timeout-minutes: 10

- name: Install ReportGenerator
run: dotnet tool install -g dotnet-reportgenerator-globaltool

- name: Generate coverage report
run: |
reportgenerator -reports:"**/coverage.cobertura.xml" -targetdir:"coverage-report" -reporttypes:"HtmlInline_AzurePipelines_Dark;MarkdownSummaryGithub"

- name: Publish Coverage Summary
run: |
cat coverage-report/SummaryGithub.md >> $GITHUB_STEP_SUMMARY

- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage-report
88 changes: 88 additions & 0 deletions .github/workflows/ci-quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: CI Quality Gate

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
sonar-analysis:
name: SonarCloud Analysis
runs-on: ubuntu-latest
if: github.actor != 'dependabot[bot]'
permissions:
pull-requests: write # Required for PR Decoration
contents: read

services:
postgres:
image: public.ecr.aws/docker/library/postgres:15-alpine
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test_db
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d test_db"
--health-interval 5s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'

- name: Install SonarScanner
run: dotnet tool install --global dotnet-sonarscanner

- name: SonarCloud Begin
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
dotnet sonarscanner begin \
/k:"triunai_microservices-template-net8" \
/o:"triunai" \
/d:sonar.token="${{ secrets.SONAR_TOKEN }}" \
/d:sonar.host.url="https://sonarcloud.io" \
/d:sonar.cs.opencover.reportsPaths="**/coverage.opencover.xml"

- name: Restore
run: dotnet restore

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

- name: Test (Collect Coverage)
env:
ConnectionStrings__TestDb: "Host=localhost;Port=5432;Database=test_db;Username=postgres;Password=postgres;Include Error Detail=true"
run: |
dotnet test Rgt.Space.Tests/Rgt.Space.Tests.csproj \
--configuration Release \
--no-build \
--collect:"XPlat Code Coverage" \
-- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover

- name: SonarCloud End
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: dotnet sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"

- name: Check Quality Gate Status & Display Summary
uses: sonarsource/sonarqube-quality-gate-action@master
timeout-minutes: 5
with:
scanMetadataReportFile: .sonarqube/out/.sonar/report-task.txt
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
3 changes: 2 additions & 1 deletion READMEs/SQL/PostgreSQL/Migrations/01-portal-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,5 @@ CREATE INDEX idx_users_email ON users (email);
CREATE INDEX idx_users_external_id ON users (sso_provider, external_id);
CREATE INDEX idx_user_roles_user ON user_roles (user_id);
CREATE INDEX idx_role_permissions_role ON role_permissions (role_id);
CREATE INDEX idx_proj_assignments_proj ON project_assignments (project_id);


81 changes: 81 additions & 0 deletions READMEs/Tasks/TASK-003-Fix-AuditLogger-Shutdown.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# 🐛 Fix AuditLogger Shutdown Hang

## Context
The `AuditLogger` component has a critical bug in its shutdown/flush mechanism. The `BackgroundWriter` loop condition (`!ct.IsCancellationRequested`) depends on `_shutdownCts`, but this token source is never cancelled during `StopAsync` or `FlushAsync`. This causes the background writer to loop indefinitely (or spin tightly) after the channel is completed, preventing the application from shutting down gracefully.

## 🛑 Problem Analysis
1. **Shutdown Signal**: `StopAsync` completes the channel writer (`_channel.Writer.Complete()`), preventing new writes.
2. **Loop Condition**: `BackgroundWriter` loop checks `!ct.IsCancellationRequested` (derived from `_shutdownCts`).
3. **Broken Logic**:
* When the channel is empty and closed, `WaitToReadAsync` returns `false` immediately.
* The loop continues because `ct` is not cancelled.
* This creates a tight loop or ineffective polling until the process is forced to exit.

## 🛠️ Implementation Plan

### 1. Modify `StopAsync`
Explicitly cancel the `_shutdownCts` to break the `BackgroundWriter` loop.

```csharp
public async Task StopAsync(CancellationToken cancellationToken)
{
if (_writerTask == null)
return;

_logger.LogInformation("Stopping audit logger and flushing pending entries...");

// 1. Signal shutdown to background writer immediately
await _shutdownCts.CancelAsync(); // or .Cancel() for synchronous

// 2. Close channel to stop accepting new entries
try
{
_channel.Writer.Complete();
}
catch (ChannelClosedException)
{
// Ignore if already closed
}

// 3. Wait for writer to finish processing all entries (handled in finally block)
await _writerTask;

_logger.LogInformation("Audit logger stopped. All entries flushed.");
}
```

### 2. Modify `FlushAsync`
Apply the same fix to `FlushAsync` since it acts as a terminal flushing operation in this implementation.

```csharp
public async Task FlushAsync(CancellationToken ct = default)
{
// 1. Signal shutdown
await _shutdownCts.CancelAsync();

try
{
_channel.Writer.Complete();
}
catch (ChannelClosedException)
{
// Ignore if already closed
}

// 2. Wait for writer to process all pending entries
if (_writerTask != null)
{
await _writerTask;
}
}
```

### 3. Verification Logic
* **Trigger**: `_shutdownCts.Cancel()` is called.
* **Effect**:
* If `BackgroundWriter` is waiting at `WaitToReadAsync`, it throws `OperationCanceledException`.
* Exception is caught in the `BackgroundWriter`.
* `finally` block executes.
* `finally` block drains the remaining items in the channel (which are still readable even if the writer is completed) and writes them to the DB.
* Task completes successfully.
* **Outcome**: Clean, immediate shutdown with zero data loss.
47 changes: 47 additions & 0 deletions READMEs/Tasks/TASK-004-CI-Excellence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 🚀 CI/CD Premium Experience Upgrade

## Context
The current CI workflow produces a "file explosion" of HTML reports that are difficult to consume. Developers have to download a ZIP artifact and hunt for an index file. Additionally, there is a risk of `FluentAssertions` upgrading to a paid license version.

## 🎯 Objectives
1. **Imrpove CI Reporting:** Switch to a summary-first approach using "Premium" report types.
2. **Safety Lock:** Pin `FluentAssertions` to safe V6/V7 versions to avoid licensing issues.
3. **Job Summary:** Inject a markdown summary directly into the GitHub Actions run summary so no download is needed.

## 🛠️ Implementation Plan

### 1. Update `ci.yml`
Modify the `Generate coverage report` step to use smarter report types.

```yaml
- name: Generate coverage report
run: |
reportgenerator \
-reports:"**/coverage.cobertura.xml" \
-targetdir:"coverage-report" \
-reporttypes:"HtmlInline_AzurePipelines_Dark;Summary;MarkdownSummary"
```

Then, add a step to publish the **Markdown Summary** to the GitHub Job Summary page.

```yaml
- name: Publish Coverage Summary
run: |
cat coverage-report/Summary.md >> $GITHUB_STEP_SUMMARY
```

### 2. Lock dependencies in `Rgt.Space.Tests.csproj`
Verify and lock `FluentAssertions`.

```xml
<!-- Safety Lock: Prevent accidental upgrade to V8 (Commercial License) -->
<PackageReference Include="FluentAssertions" Version="6.12.1" />
```
*Note: We will stick to 6.12.1 for now as it is stable and already installed.*

## 🧪 Verification
1. Commit changes.
2. Push to branch.
3. Observe GitHub Actions run:
* Verify `Summary.md` content appears in the "Job Summary" view.
* Verify "Download Artifact" contains a clean single-file HTML report (or at least the Summary.html).
Loading
Loading