Skip to content

Commit 3d58f7d

Browse files
committed
feat(core): implement daily automated scanning and enhance error handling
- Add Hangfire recurring job for daily repository scanning at midnight UTC - Enhance GitHub OAuth configuration validation with proper error handling - Fix nullable reference warnings across multiple entities - Simplify MainLayout by removing FluentThemeProvider wrapper - Update documentation for dual authentication strategy (GitHub App + OAuth) - Enhance Hangfire integration docs with recurring job configuration - Update cursor rules to reflect Fluent UI usage BREAKING CHANGE: Removed FluentThemeProvider from MainLayout.razor
1 parent 56b50c5 commit 3d58f7d

File tree

9 files changed

+151
-62
lines changed

9 files changed

+151
-62
lines changed

.cursor/rules/coding-practices.mdc

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ You are a senior Blazor and .NET developer and an expert in `C#`, `ASP.NET Core`
3535
- Use `C#`'s expressive syntax (e.g., null-conditional operators, string interpolation)
3636
- Use `var` for implicit typing when the type is obvious.
3737

38-
### Frontend Development (Blazor & MudBlazor)
39-
- Use `MudBlazor` components to build the UI for a consistent look and feel.
40-
- Adhere to a consistent styling and theming strategy using `MudBlazor`'s theme provider.
38+
### Frontend Development (Blazor & Fluent UI)
39+
- Use `Microsoft.FluentUI.AspNetCore.Components` to build the UI for a consistent look and feel.
40+
- Adhere to a consistent styling and theming strategy using Fluent UI's theme provider.
4141
- Manage application state carefully. For complex state, consider using a cascading parameter-based state container.
4242

4343
### Error Handling and Validation
@@ -119,4 +119,12 @@ You are a senior Blazor and .NET developer and an expert in `C#`, `ASP.NET Core`
119119
- Configure queues and workers appropriately for production environments.
120120
- Refer to `/docs/hangfire-integration.md` for more details.
121121

122+
### Action Service
123+
- Use `IActionService` to process automated actions for policy violations.
124+
- The service supports three action types: `create-issue`, `archive-repo`, and `log-only`.
125+
- All actions are logged to the `ActionLog` table with status tracking.
126+
- Duplicate issue prevention is built-in for `create-issue` actions.
127+
- Individual action failures don't block processing of other violations.
128+
- Refer to `/docs/action-service.md` for detailed usage examples and configuration.
129+
122130
Follow the official Microsoft documentation and `ASP.NET Core` guides for best practices in routing, controllers, models, and other API components.

.cursor/rules/setup.mdc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ alwaysApply: true
44
---
55
# Setup commands
66
To run this project locally:
7-
1. `cd 10xGitHubPolicies.App`
8-
2. `dotnet restore`
9-
3. `dotnet run`
7+
1. `cd 10xGitHubPolicies`
8+
2. `docker-compose up -d`
9+
3. `cd 10xGitHubPolicies.App`
10+
4. `dotnet restore`
11+
5. `dotnet ef database update`
12+
6. `dotnet dev-certs https --trust`
13+
7. `dotnet run --launch-profile https`
1014

11-
Alternatively, you can run from the root directory using `dotnet run --project 10xGitHubPolicies.App/10xGitHubPolicies.App.csproj`.
15+
Alternatively, you can run from the root directory using `dotnet run --project 10xGitHubPolicies.App/10xGitHubPolicies.App.csproj --launch-profile https`.
16+
17+
**Important**: Always use the HTTPS profile to ensure OAuth authentication works correctly.

10xGitHubPolicies.App/Data/Entities/PolicyViolation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ public class PolicyViolation
1818
public Policy Policy { get; set; } = null!;
1919

2020
[NotMapped]
21-
public string PolicyType { get; set; }
21+
public string PolicyType { get; set; } = string.Empty;
2222
}

10xGitHubPolicies.App/Program.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@
5959
})
6060
.AddGitHub(options =>
6161
{
62-
options.ClientId = builder.Configuration["GitHub:ClientId"];
63-
options.ClientSecret = builder.Configuration["GitHub:ClientSecret"];
62+
options.ClientId = builder.Configuration["GitHub:ClientId"] ?? throw new InvalidOperationException("GitHub:ClientId is not configured");
63+
options.ClientSecret = builder.Configuration["GitHub:ClientSecret"] ?? throw new InvalidOperationException("GitHub:ClientSecret is not configured");
6464
options.CallbackPath = "/signin-github";
6565
options.Scope.Add("read:org");
6666
options.SaveTokens = true; // Save access token for team verification
@@ -134,6 +134,16 @@
134134
Authorization = new[] { new HangfireAuthorizationFilter() }
135135
});
136136

137+
// Configure recurring jobs
138+
RecurringJob.AddOrUpdate<IScanningService>(
139+
"daily-scan",
140+
service => service.PerformScanAsync(),
141+
"0 0 * * *", // Daily at midnight UTC
142+
new RecurringJobOptions
143+
{
144+
TimeZone = TimeZoneInfo.Utc
145+
});
146+
137147
// Add OAuth challenge endpoint
138148
app.MapGet("/challenge", async (HttpContext context) =>
139149
{

10xGitHubPolicies.App/Services/Configuration/ConfigurationService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public ConfigurationService(
3737

3838
public async Task<AppConfig> GetConfigAsync(bool forceRefresh = false)
3939
{
40-
if (!forceRefresh && _cache.TryGetValue(AppConfigCacheKey, out AppConfig cachedConfig))
40+
if (!forceRefresh && _cache.TryGetValue(AppConfigCacheKey, out AppConfig? cachedConfig) && cachedConfig != null)
4141
{
4242
_logger.LogInformation("Configuration found in cache.");
4343
return cachedConfig;
@@ -47,7 +47,7 @@ public async Task<AppConfig> GetConfigAsync(bool forceRefresh = false)
4747
try
4848
{
4949
// Double-check locking
50-
if (!forceRefresh && _cache.TryGetValue(AppConfigCacheKey, out AppConfig configAfterLock))
50+
if (!forceRefresh && _cache.TryGetValue(AppConfigCacheKey, out AppConfig? configAfterLock) && configAfterLock != null)
5151
{
5252
_logger.LogInformation("Configuration found in cache after acquiring lock.");
5353
return configAfterLock;

10xGitHubPolicies.App/Shared/MainLayout.razor

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,40 @@
11
@inherits LayoutComponentBase
2+
@using Microsoft.FluentUI.AspNetCore.Components
23

3-
<FluentThemeProvider>
4-
<FluentLayout>
5-
<FluentHeader>
6-
<div style="display: flex; align-items: center; padding: 0 24px;">
7-
<FluentIcon Icon="@(Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size28.ShieldCheckmark)" style="font-size: 28px;" />
8-
<span style="margin-left: 12px; font-size: var(--type-ramp-plus-2-font-size);">10x GitHub Policy Enforcer</span>
9-
</div>
10-
<FluentSpacer />
11-
12-
<AuthorizeView>
13-
<Authorized>
14-
<FluentStack Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
15-
<FluentIcon Icon="@(Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.Person)" />
16-
<span style="margin: 0 8px;">@context.User.Identity?.Name</span>
17-
<FluentButton Appearance="Appearance.Neutral" OnClick="Logout" Style="margin-left: 8px;">
18-
<FluentIcon Icon="@(Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.Person)" />
19-
</FluentButton>
20-
</FluentStack>
21-
</Authorized>
22-
<NotAuthorized>
23-
<FluentButton Appearance="Appearance.Accent" OnClick="Login">
4+
<FluentLayout>
5+
<FluentHeader>
6+
<div style="display: flex; align-items: center; padding: 0 24px;">
7+
<FluentIcon Icon="@(Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size28.ShieldCheckmark)" style="font-size: 28px;" />
8+
<span style="margin-left: 12px; font-size: var(--type-ramp-plus-2-font-size);">10x GitHub Policy Enforcer</span>
9+
</div>
10+
<FluentSpacer />
11+
12+
<AuthorizeView>
13+
<Authorized>
14+
<FluentStack Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
15+
<FluentIcon Icon="@(Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.Person)" />
16+
<span style="margin: 0 8px;">@context.User.Identity?.Name</span>
17+
<FluentButton Appearance="Appearance.Neutral" OnClick="Logout" Style="margin-left: 8px;">
2418
<FluentIcon Icon="@(Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.Person)" />
25-
Login
2619
</FluentButton>
27-
</NotAuthorized>
28-
</AuthorizeView>
29-
30-
<FluentThemeSwitcher />
31-
</FluentHeader>
32-
<FluentStack Orientation="Orientation.Vertical" Style="width: 100%;">
33-
<FluentBodyContent>
34-
<div class="content" style="padding: 24px;">
35-
@Body
36-
</div>
37-
</FluentBodyContent>
38-
</FluentStack>
39-
</FluentLayout>
40-
</FluentThemeProvider>
20+
</FluentStack>
21+
</Authorized>
22+
<NotAuthorized>
23+
<FluentButton Appearance="Appearance.Accent" OnClick="Login">
24+
<FluentIcon Icon="@(Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.Person)" />
25+
Login
26+
</FluentButton>
27+
</NotAuthorized>
28+
</AuthorizeView>
29+
</FluentHeader>
30+
<FluentStack Orientation="Orientation.Vertical" Style="width: 100%;">
31+
<FluentBodyContent>
32+
<div class="content" style="padding: 24px;">
33+
@Body
34+
</div>
35+
</FluentBodyContent>
36+
</FluentStack>
37+
</FluentLayout>
4138

4239
@code {
4340
[Inject] private NavigationManager Navigation { get; set; } = default!;

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,28 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## 1.1
6+
7+
### Added
8+
- **Daily Automated Scanning**: Implemented recurring job configuration for automatic daily repository scanning
9+
- Added `RecurringJob.AddOrUpdate()` configuration in `Program.cs` for daily scans at midnight UTC
10+
- Scans run automatically without manual intervention using Hangfire's recurring job system
11+
- Uses `IScanningService.PerformScanAsync()` for consistent scanning logic
12+
- **Enhanced Error Handling**: Improved configuration validation and error handling
13+
- Added null checks and proper exception handling for GitHub OAuth configuration
14+
- Enhanced error messages for missing configuration values
15+
16+
### Changed
17+
- **UI Simplification**: Removed `FluentThemeProvider` wrapper from `MainLayout.razor` for cleaner component structure
18+
- **Code Quality**: Fixed nullable reference warnings across multiple files
19+
- `PolicyViolation.cs`: Added default value for `PolicyType` property
20+
- `ConfigurationService.cs`: Enhanced null checking for cached configuration
21+
- **Documentation**: Updated Hangfire integration documentation to include recurring job configuration
22+
23+
### Fixed
24+
- **Nullable Reference Warnings**: Resolved compiler warnings for nullable reference types
25+
- **Configuration Caching**: Improved thread safety and null handling in configuration service
26+
527
## 1.0
628

729
### Added

README.md

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ The 10x GitHub Policy Enforcer is a GitHub App with an accompanying web UI desig
2929

3030
It uses a flexible policy evaluation engine to scan repositories for compliance with a centrally managed configuration file. When violations are found, it can automatically perform actions like creating issues in the repository or archiving it. The web dashboard provides a clear overview of your organization's compliance posture.
3131

32+
The application uses a dual-authentication strategy:
33+
- **GitHub App**: For backend services to perform automated scans and actions
34+
- **GitHub OAuth App**: For user authentication to the web dashboard
35+
3236
---
3337

3438
## Features
@@ -132,7 +136,10 @@ The application will be available at:
132136
The application is configured via `appsettings.json` and user secrets for sensitive data.
133137

134138
### GitHub App Settings
135-
You need to configure the GitHub App settings. During development, it's required to use the .NET Secret Manager to keep secrets out of source control.
139+
The application uses a dual-authentication strategy requiring both a GitHub App (for backend services) and a GitHub OAuth App (for user authentication).
140+
141+
#### GitHub App (Backend Services)
142+
The GitHub App is used by backend services to perform automated scans and actions against the GitHub API.
136143

137144
1. Initialize user secrets for the project (if you haven't already):
138145
```sh
@@ -157,7 +164,27 @@ You need to configure the GitHub App settings. During development, it's required
157164
```
158165
Replace `your-organization-name` with your GitHub organization's slug.
159166
160-
### GitHub OAuth App Settings
167+
#### GitHub App Setup
168+
To create a GitHub App for backend services:
169+
170+
1. Go to [GitHub Developer Settings](https://github.com/settings/apps)
171+
2. Click "New GitHub App"
172+
3. Configure the following:
173+
- **GitHub App name**: 10x GitHub Policy Enforcer
174+
- **Homepage URL**: `https://localhost:7040/` (for local development)
175+
- **Webhook URL**: Leave empty for local development
176+
- **Repository permissions**:
177+
- **Administration**: Read & write (to archive repositories)
178+
- **Contents**: Read-only (to check for file presence)
179+
- **Issues**: Read & write (to create and check for duplicate issues)
180+
- **Metadata**: Read-only (to list repositories)
181+
- **Organization permissions**: None required
182+
4. After creating the app:
183+
- Note the **App ID** (found on the app's general page)
184+
- Generate a **Private Key** (download the `.pem` file)
185+
- Install the app on your organization and note the **Installation ID**
186+
187+
#### GitHub OAuth App (User Authentication)
161188
For user authentication, you need to create a GitHub OAuth App:
162189

163190
1. Go to [GitHub Developer Settings](https://github.com/settings/developers)
@@ -227,7 +254,7 @@ Detailed documentation for specific features and integrations:
227254

228255
### In Scope (MVP)
229256
*`[done]` Configuration managed via a single `config.yaml` file in the `.github` repository.
230-
* `[todo]` Daily and on-demand scanning of all active repositories.
257+
* `[done]` Daily and on-demand scanning of all active repositories.
231258
*`[done]` Core policies:
232259
*`[done]` Verify presence of `AGENTS.md`.
233260
*`[done]` Verify presence of `catalog-info.yaml`.
@@ -250,21 +277,13 @@ Detailed documentation for specific features and integrations:
250277
* Repository-level exceptions or overrides in the UI.
251278

252279
### Ideas
253-
* Secure /hangfire
254280
* Logs - production
255281
* Advanced policy types (e.g., checking file content)
256282
* Action - PR Blocking
257283
* Action - Log only
258284
* Action - Slack notification
259285
* Exception policies
260-
* Getting team ownership and
261-
262-
263-
---
264-
265-
## Project Status
266-
267-
This project is currently **in development**. The immediate focus is on delivering the Minimum Viable Product (MVP) features outlined in the project scope.
286+
* Getting team ownership
268287

269288
---
270289

docs/hangfire-integration.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ The dashboard is accessible at the `/hangfire` endpoint of the application (e.g.
4343

4444
## Usage in the Application
4545

46-
Hangfire is primarily used in two places:
46+
Hangfire is used in three main scenarios:
4747

4848
1. **On-Demand Repository Scanning**: When a user clicks the "Scan Now" button on the dashboard.
49-
2. **Processing Actions for Violations**: After a scan is completed, a job is enqueued to process the configured actions for any violations found using the `ActionService`.
49+
2. **Daily Automated Scanning**: A recurring job that automatically scans all repositories daily at midnight UTC.
50+
3. **Processing Actions for Violations**: After a scan is completed, a job is enqueued to process the configured actions for any violations found using the `ActionService`.
5051

5152
### Enqueuing a Scan
5253

@@ -68,6 +69,32 @@ private async Task StartScan()
6869

6970
By using `_backgroundJobClient.Enqueue()`, the `PerformScanAsync` method is executed on a background thread by a Hangfire worker. This immediately returns control to the UI, which can then display a "Scanning..." status to the user.
7071

72+
### Daily Automated Scanning
73+
74+
The application is configured with a recurring job that automatically scans all repositories daily:
75+
76+
```csharp
77+
// Program.cs
78+
79+
// Configure recurring jobs
80+
RecurringJob.AddOrUpdate<IScanningService>(
81+
"daily-scan",
82+
service => service.PerformScanAsync(),
83+
"0 0 * * *", // Daily at midnight UTC
84+
new RecurringJobOptions
85+
{
86+
TimeZone = TimeZoneInfo.Utc
87+
});
88+
```
89+
90+
This configuration:
91+
- **Job Name**: `"daily-scan"` - unique identifier for the recurring job
92+
- **Cron Expression**: `"0 0 * * *"` - runs daily at midnight UTC
93+
- **Timezone**: UTC to ensure consistent execution times
94+
- **Service**: Uses `IScanningService.PerformScanAsync()` for the actual scanning logic
95+
96+
The recurring job ensures that all repositories are automatically scanned for policy compliance without manual intervention, providing continuous monitoring of organizational compliance.
97+
7198
### Enqueuing Actions Post-Scan
7299

73100
In the `ScanningService`, after a scan is successfully completed and violations have been saved, a job is enqueued for the `IActionService` to process the results.

0 commit comments

Comments
 (0)