Add implementation plan: deploy artifacts, remove config DB dependency

This commit is contained in:
Joseph Doherty
2026-03-17 13:35:54 -04:00
parent 75ccd4b1c0
commit 54c03a3139
2 changed files with 198 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
# Deploy Artifacts to Sites — Remove Config DB Dependency
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.
**Goal:** Make site servers fully self-contained by reading external system, notification, and data connection configs from local SQLite instead of the central config DB.
**Architecture:** The artifact deployment pipeline already exists (`DeployArtifactsCommand``DeploymentManagerActor``SiteStorageService`). The gap is that runtime services still query the config DB. We create site-local repository implementations backed by SQLite, wire them into DI, add a UI button to trigger deployment, and extend artifacts to include data connections.
**Tech Stack:** C#, SQLite (Microsoft.Data.Sqlite), Blazor Server, Akka.NET messaging
---
### Task 1: Create SiteExternalSystemRepository
**Files:**
- Create: `src/ScadaLink.SiteRuntime/Repositories/SiteExternalSystemRepository.cs`
**Step 1:** Create `SiteExternalSystemRepository` implementing `IExternalSystemRepository`. Read-only methods query `SiteStorageService` SQLite tables. Write methods throw `NotSupportedException` (site is read-only for these configs).
```csharp
// Key methods to implement:
// GetAllExternalSystemsAsync → query external_systems table, deserialize method_definitions JSON
// GetMethodsByExternalSystemIdAsync → parse method_definitions from external_systems row
// GetAllDatabaseConnectionsAsync → query database_connections table
// All Add/Update/Delete methods → throw NotSupportedException("Managed via artifact deployment")
```
The `external_systems` table stores `method_definitions` as JSON. Parse it into `List<ExternalSystemMethod>` on read. The `ExternalSystemDefinition` entity needs Id, Name, EndpointUrl, AuthType, AuthConfiguration. Map the `name` column to a synthetic Id (hash or sequential).
**Step 2:** Build and verify no compile errors.
**Step 3:** Commit: `feat: add SiteExternalSystemRepository backed by SQLite`
---
### Task 2: Create SiteNotificationRepository
**Files:**
- Create: `src/ScadaLink.SiteRuntime/Repositories/SiteNotificationRepository.cs`
**Step 1:** Create `SiteNotificationRepository` implementing `INotificationRepository`. Read-only methods query `SiteStorageService` SQLite tables.
```csharp
// Key methods:
// GetListByNameAsync → query notification_lists by name
// GetRecipientsByListIdAsync → parse recipient_emails JSON from notification_lists row
// GetAllSmtpConfigurationsAsync → needs new smtp_configurations SQLite table
// All Add/Update/Delete → throw NotSupportedException
```
**Step 2:** Add `smtp_configurations` table to `SiteStorageService.InitializeAsync()`:
```sql
CREATE TABLE IF NOT EXISTS smtp_configurations (
name TEXT PRIMARY KEY,
server TEXT NOT NULL,
port INTEGER NOT NULL,
auth_mode TEXT NOT NULL,
from_address TEXT NOT NULL,
username TEXT,
password TEXT,
oauth_config TEXT,
updated_at TEXT NOT NULL
);
```
**Step 3:** Add `StoreSmtpConfigurationAsync` method to `SiteStorageService`.
**Step 4:** Build and verify.
**Step 5:** Commit: `feat: add SiteNotificationRepository and SMTP storage`
---
### Task 3: Add data connections to DeployArtifactsCommand
**Files:**
- Modify: `src/ScadaLink.Commons/Messages/Artifacts/DeployArtifactsCommand.cs`
- Modify: `src/ScadaLink.SiteRuntime/Actors/DeploymentManagerActor.cs` (HandleDeployArtifacts)
- Modify: `src/ScadaLink.SiteRuntime/Persistence/SiteStorageService.cs`
**Step 1:** Add `DataConnectionArtifact` record and include it in `DeployArtifactsCommand`:
```csharp
public record DataConnectionArtifact(
string Name, string Protocol, string? ConfigurationJson);
// Add to DeployArtifactsCommand:
IReadOnlyList<DataConnectionArtifact>? DataConnections
```
**Step 2:** Add `data_connection_definitions` table to `SiteStorageService.InitializeAsync()`:
```sql
CREATE TABLE IF NOT EXISTS data_connection_definitions (
name TEXT PRIMARY KEY,
protocol TEXT NOT NULL,
configuration TEXT,
updated_at TEXT NOT NULL
);
```
**Step 3:** Add `StoreDataConnectionDefinitionAsync` and `GetAllDataConnectionDefinitionsAsync` to `SiteStorageService`.
**Step 4:** Update `DeploymentManagerActor.HandleDeployArtifacts` to persist data connections.
**Step 5:** Update `HandleDeployArtifacts` SMTP config storage (add to the existing handler alongside external systems and db connections).
**Step 6:** Build and verify.
**Step 7:** Commit: `feat: include data connections and SMTP in artifact deployment`
---
### Task 4: Wire site-local repositories into DI
**Files:**
- Modify: `src/ScadaLink.Host/Program.cs` (Site role, lines ~155-160)
- Modify: `src/ScadaLink.Host/appsettings.Site.json`
- Modify: `src/ScadaLink.SiteRuntime/ServiceCollectionExtensions.cs`
**Step 1:** In `ServiceCollectionExtensions.AddSiteRuntime()`, register the site-local repositories:
```csharp
services.AddScoped<IExternalSystemRepository, SiteExternalSystemRepository>();
services.AddScoped<INotificationRepository, SiteNotificationRepository>();
```
**Step 2:** Remove `AddConfigurationDatabase` call from the Site role in `Program.cs`. Remove the `ConfigurationDb` connection string check.
**Step 3:** Remove `ConfigurationDb` from `appsettings.Site.json`.
**Step 4:** Build and verify — site should start without SQL Server.
**Step 5:** Commit: `feat: wire site-local repos, remove config DB dependency from Site`
---
### Task 5: Add Deploy Artifacts button to Sites admin page
**Files:**
- Modify: `src/ScadaLink.CentralUI/Components/Pages/Admin/Sites.razor`
**Step 1:** Inject `ArtifactDeploymentService` and add a "Deploy Artifacts" button in each site's action column (next to Edit/Delete).
**Step 2:** Add handler method that:
- Collects all external systems, db connections, notification lists, SMTP configs, shared scripts, and data connections from config DB repositories
- Builds a `DeployArtifactsCommand`
- Calls `ArtifactDeploymentService.DeployToAllSitesAsync` (or per-site variant)
- Shows success/failure toast
**Step 3:** Add a "Deploy Artifacts to All Sites" button in the page header for bulk deployment.
**Step 4:** Build and verify UI renders.
**Step 5:** Commit: `feat: add Deploy Artifacts button to Sites admin page`
---
### Task 6: Update ArtifactDeploymentService to include all artifact types
**Files:**
- Modify: `src/ScadaLink.DeploymentManager/ArtifactDeploymentService.cs`
**Step 1:** Inject `ISiteRepository` (for data connections) and `INotificationRepository` (for SMTP configs). Update the command-building logic to include:
- Data connections from `ISiteRepository.GetAllDataConnectionsAsync()`
- SMTP configurations from `INotificationRepository.GetAllSmtpConfigurationsAsync()`
**Step 2:** Build and verify.
**Step 3:** Commit: `feat: include data connections and SMTP in artifact collection`
---
### Task 7: End-to-end test
**Step 1:** Start Central and Site servers.
**Step 2:** Login as admin, navigate to Sites page, click "Deploy Artifacts" on Test Plant A.
**Step 3:** Verify site logs show artifact receipt and SQLite storage.
**Step 4:** Restart Site without config DB connection string — verify it starts and ExternalSystemClient/NotificationDeliveryService work from local data.
**Step 5:** Commit any fixes.

View File

@@ -0,0 +1,13 @@
{
"planPath": "docs/plans/2026-03-17-deploy-artifacts-remove-configdb.md",
"tasks": [
{"id": 4, "subject": "Task 1: Create SiteExternalSystemRepository", "status": "pending"},
{"id": 5, "subject": "Task 2: Create SiteNotificationRepository", "status": "pending"},
{"id": 6, "subject": "Task 3: Add data connections to DeployArtifactsCommand", "status": "pending"},
{"id": 7, "subject": "Task 4: Wire site-local repositories into DI", "status": "pending", "blockedBy": [4, 5, 6]},
{"id": 8, "subject": "Task 5: Add Deploy Artifacts button to Sites admin page", "status": "pending", "blockedBy": [9]},
{"id": 9, "subject": "Task 6: Update ArtifactDeploymentService to include all artifact types", "status": "pending"},
{"id": 10, "subject": "Task 7: End-to-end test", "status": "pending", "blockedBy": [7, 8, 9]}
],
"lastUpdated": "2026-03-17T16:40:00Z"
}