OPC # 0001: Extract OPC into standalone repo

This commit is contained in:
amadzarak
2026-04-25 17:26:42 -04:00
commit 42383bdc03
170 changed files with 21365 additions and 0 deletions
+297
View File
@@ -0,0 +1,297 @@
using ControlPlane.Core.Models;
using Npgsql;
namespace ControlPlane.Api.Services;
public class OpcService(NpgsqlDataSource db)
{
// ── Helpers ──────────────────────────────────────────────────────────────
private static OpcRecord ReadOpc(NpgsqlDataReader r) => new(
r.GetGuid(0),
r.GetString(1),
r.GetString(2),
r.GetString(3),
r.GetString(4),
r.GetString(5),
r.GetString(6),
r.GetString(7),
r.GetDateTime(8),
r.GetDateTime(9)
);
private static OpcNote ReadNote(NpgsqlDataReader r) => new(
r.GetGuid(0),
r.GetGuid(1),
r.GetString(2),
r.GetString(3),
r.GetDateTime(4)
);
private static OpcArtifact ReadArtifact(NpgsqlDataReader r) => new(
r.GetGuid(0),
r.GetGuid(1),
r.GetString(2),
r.GetString(3),
r.GetString(4),
r.GetDateTime(5),
r.GetDateTime(6)
);
// ── Next OPC number ───────────────────────────────────────────────────────
public async Task<string> NextNumberAsync(CancellationToken ct = default)
{
await using var cmd = db.CreateCommand(
"SELECT number FROM opc ORDER BY CAST(TRIM(SUBSTRING(number FROM 7)) AS INTEGER) DESC LIMIT 1");
var last = await cmd.ExecuteScalarAsync(ct) as string;
if (last is null) return "OPC # 0001";
if (int.TryParse(last[6..], out var n))
return $"OPC # {n + 1:D4}";
return "OPC # 0001";
}
// ── OPC CRUD ──────────────────────────────────────────────────────────────
public async Task<List<OpcRecord>> ListAsync(
string? typeFilter = null, string? statusFilter = null,
CancellationToken ct = default)
{
var sql = """
SELECT id, number, title, description, type, status, priority, assignee,
created_at, updated_at
FROM opc
WHERE ($1::text IS NULL OR type = $1)
AND ($2::text IS NULL OR status = $2)
ORDER BY created_at DESC
""";
await using var cmd = db.CreateCommand(sql);
cmd.Parameters.AddWithValue(typeFilter ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue(statusFilter ?? (object)DBNull.Value);
await using var r = await cmd.ExecuteReaderAsync(ct);
var list = new List<OpcRecord>();
while (await r.ReadAsync(ct)) list.Add(ReadOpc(r));
return list;
}
public async Task<OpcRecord?> GetAsync(Guid id, CancellationToken ct = default)
{
await using var cmd = db.CreateCommand(
"SELECT id, number, title, description, type, status, priority, assignee, created_at, updated_at FROM opc WHERE id = $1");
cmd.Parameters.AddWithValue(id);
await using var r = await cmd.ExecuteReaderAsync(ct);
return await r.ReadAsync(ct) ? ReadOpc(r) : null;
}
public async Task<OpcRecord> CreateAsync(CreateOpcRequest req, CancellationToken ct = default)
{
var number = await NextNumberAsync(ct);
var sql = """
INSERT INTO opc (number, title, description, type, status, priority, assignee)
VALUES ($1, $2, $3, $4, 'New', $5, $6)
RETURNING id, number, title, description, type, status, priority, assignee,
created_at, updated_at
""";
await using var cmd = db.CreateCommand(sql);
cmd.Parameters.AddWithValue(number);
cmd.Parameters.AddWithValue(req.Title);
cmd.Parameters.AddWithValue(req.Description);
cmd.Parameters.AddWithValue(req.Type);
cmd.Parameters.AddWithValue(req.Priority);
cmd.Parameters.AddWithValue(req.Assignee);
await using var r = await cmd.ExecuteReaderAsync(ct);
await r.ReadAsync(ct);
return ReadOpc(r);
}
public async Task<OpcRecord?> UpdateAsync(Guid id, UpdateOpcRequest req, CancellationToken ct = default)
{
var sql = """
UPDATE opc SET
title = COALESCE($2, title),
description = COALESCE($3, description),
type = COALESCE($4, type),
status = COALESCE($5, status),
priority = COALESCE($6, priority),
assignee = COALESCE($7, assignee),
updated_at = NOW()
WHERE id = $1
RETURNING id, number, title, description, type, status, priority, assignee,
created_at, updated_at
""";
await using var cmd = db.CreateCommand(sql);
cmd.Parameters.AddWithValue(id);
cmd.Parameters.AddWithValue(req.Title ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue(req.Description ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue(req.Type ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue(req.Status ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue(req.Priority ?? (object)DBNull.Value);
cmd.Parameters.AddWithValue(req.Assignee ?? (object)DBNull.Value);
await using var r = await cmd.ExecuteReaderAsync(ct);
return await r.ReadAsync(ct) ? ReadOpc(r) : null;
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken ct = default)
{
await using var cmd = db.CreateCommand("DELETE FROM opc WHERE id = $1");
cmd.Parameters.AddWithValue(id);
return await cmd.ExecuteNonQueryAsync(ct) > 0;
}
// ── Notes ──────────────────────────────────────────────────────────────────
public async Task<List<OpcNote>> ListNotesAsync(Guid opcId, CancellationToken ct = default)
{
await using var cmd = db.CreateCommand(
"SELECT id, opc_id, author, content, created_at FROM opc_note WHERE opc_id = $1 ORDER BY created_at ASC");
cmd.Parameters.AddWithValue(opcId);
await using var r = await cmd.ExecuteReaderAsync(ct);
var list = new List<OpcNote>();
while (await r.ReadAsync(ct)) list.Add(ReadNote(r));
return list;
}
public async Task<OpcNote> AddNoteAsync(Guid opcId, AddNoteRequest req, CancellationToken ct = default)
{
var sql = """
INSERT INTO opc_note (opc_id, author, content)
VALUES ($1, $2, $3)
RETURNING id, opc_id, author, content, created_at
""";
await using var cmd = db.CreateCommand(sql);
cmd.Parameters.AddWithValue(opcId);
cmd.Parameters.AddWithValue(req.Author);
cmd.Parameters.AddWithValue(req.Content);
await using var r = await cmd.ExecuteReaderAsync(ct);
await r.ReadAsync(ct);
return ReadNote(r);
}
// ── Artifacts ─────────────────────────────────────────────────────────────
public async Task<List<OpcArtifact>> ListArtifactsAsync(Guid opcId, string? artifactType = null, CancellationToken ct = default)
{
var sql = """
SELECT id, opc_id, artifact_type, title, content, created_at, updated_at
FROM opc_artifact
WHERE opc_id = $1
AND ($2::text IS NULL OR artifact_type = $2)
ORDER BY created_at ASC
""";
await using var cmd = db.CreateCommand(sql);
cmd.Parameters.AddWithValue(opcId);
cmd.Parameters.AddWithValue(artifactType ?? (object)DBNull.Value);
await using var r = await cmd.ExecuteReaderAsync(ct);
var list = new List<OpcArtifact>();
while (await r.ReadAsync(ct)) list.Add(ReadArtifact(r));
return list;
}
public async Task<OpcArtifact> UpsertArtifactAsync(Guid opcId, UpsertArtifactRequest req, CancellationToken ct = default)
{
var sql = """
INSERT INTO opc_artifact (opc_id, artifact_type, title, content)
VALUES ($1, $2, $3, $4)
ON CONFLICT DO NOTHING
RETURNING id, opc_id, artifact_type, title, content, created_at, updated_at
""";
// Simple insert; for updates use artifact id endpoint
await using var cmd = db.CreateCommand(sql);
cmd.Parameters.AddWithValue(opcId);
cmd.Parameters.AddWithValue(req.ArtifactType);
cmd.Parameters.AddWithValue(req.Title);
cmd.Parameters.AddWithValue(req.Content);
await using var r = await cmd.ExecuteReaderAsync(ct);
await r.ReadAsync(ct);
return ReadArtifact(r);
}
public async Task<OpcArtifact?> UpdateArtifactAsync(Guid artifactId, UpsertArtifactRequest req, CancellationToken ct = default)
{
var sql = """
UPDATE opc_artifact SET
title = $2,
content = $3,
updated_at = NOW()
WHERE id = $1
RETURNING id, opc_id, artifact_type, title, content, created_at, updated_at
""";
await using var cmd = db.CreateCommand(sql);
cmd.Parameters.AddWithValue(artifactId);
cmd.Parameters.AddWithValue(req.Title);
cmd.Parameters.AddWithValue(req.Content);
await using var r = await cmd.ExecuteReaderAsync(ct);
return await r.ReadAsync(ct) ? ReadArtifact(r) : null;
}
public async Task<bool> DeleteArtifactAsync(Guid artifactId, CancellationToken ct = default)
{
await using var cmd = db.CreateCommand("DELETE FROM opc_artifact WHERE id = $1");
cmd.Parameters.AddWithValue(artifactId);
return await cmd.ExecuteNonQueryAsync(ct) > 0;
}
// ── Pinned commits ────────────────────────────────────────────────────────
private static OpcPinnedCommit ReadPinnedCommit(NpgsqlDataReader r) => new(
r.GetGuid(0),
r.GetString(1),
r.GetString(2),
r.GetString(3),
r.GetString(4),
r.GetDateTime(5),
r.GetString(6)
);
public async Task<List<OpcPinnedCommit>> ListPinnedCommitsAsync(Guid opcId, CancellationToken ct = default)
{
await using var cmd = db.CreateCommand(
"SELECT opc_id, hash, short_hash, subject, author, pinned_at, pinned_by FROM opc_pinned_commit WHERE opc_id = $1 ORDER BY pinned_at DESC");
cmd.Parameters.AddWithValue(opcId);
await using var r = await cmd.ExecuteReaderAsync(ct);
var list = new List<OpcPinnedCommit>();
while (await r.ReadAsync(ct)) list.Add(ReadPinnedCommit(r));
return list;
}
public async Task<OpcPinnedCommit?> PinCommitAsync(
Guid opcId, string hash, string shortHash, string subject, string author, string pinnedBy,
CancellationToken ct = default)
{
// Verify the OPC exists
await using var existsCmd = db.CreateCommand("SELECT 1 FROM opc WHERE id = $1");
existsCmd.Parameters.AddWithValue(opcId);
var exists = await existsCmd.ExecuteScalarAsync(ct);
if (exists is null) return null;
var sql = """
INSERT INTO opc_pinned_commit (opc_id, hash, short_hash, subject, author, pinned_by)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT (opc_id, hash) DO UPDATE SET
short_hash = EXCLUDED.short_hash,
subject = EXCLUDED.subject,
author = EXCLUDED.author,
pinned_by = EXCLUDED.pinned_by,
pinned_at = NOW()
RETURNING opc_id, hash, short_hash, subject, author, pinned_at, pinned_by
""";
await using var cmd = db.CreateCommand(sql);
cmd.Parameters.AddWithValue(opcId);
cmd.Parameters.AddWithValue(hash);
cmd.Parameters.AddWithValue(shortHash);
cmd.Parameters.AddWithValue(subject);
cmd.Parameters.AddWithValue(author);
cmd.Parameters.AddWithValue(pinnedBy);
await using var r = await cmd.ExecuteReaderAsync(ct);
return await r.ReadAsync(ct) ? ReadPinnedCommit(r) : null;
}
public async Task<bool> UnpinCommitAsync(Guid opcId, string hash, CancellationToken ct = default)
{
await using var cmd = db.CreateCommand(
"DELETE FROM opc_pinned_commit WHERE opc_id = $1 AND hash = $2");
cmd.Parameters.AddWithValue(opcId);
cmd.Parameters.AddWithValue(hash);
return await cmd.ExecuteNonQueryAsync(ct) > 0;
}
}