Files
OPC/ControlPlane.Api/Program.cs
T
2026-04-25 22:36:53 -04:00

133 lines
5.3 KiB
C#

using ControlPlane.Api.Consumers;
using ControlPlane.Api.Endpoints;
using ControlPlane.Api.Services;
using ControlPlane.Core.Models;
using ControlPlane.Core.Services;
using MassTransit;
using Npgsql;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
builder.Services.AddOpenApi();
builder.Services.ConfigureHttpJsonOptions(o =>
o.SerializerOptions.Converters.Add(new System.Text.Json.Serialization.JsonStringEnumConverter()));
// In-memory job store - swap for EF Core post-MVP
builder.Services.AddSingleton<Dictionary<Guid, ProvisioningJob>>();
// Tenant registry - reads ClientAssets/{subdomain}.xml files
builder.Services.AddSingleton<TenantRegistryService>();
// SSE event bus - ProgressConsumer writes here, SSE endpoint reads
builder.Services.AddSingleton<SseEventBus>();
// Build + release pipeline services
builder.Services.AddSingleton<BuildHistoryService>();
builder.Services.AddSingleton<ImageBuildService>();
builder.Services.AddSingleton<ReleaseService>();
builder.Services.AddSingleton<ProjectBuildService>();
builder.Services.AddSingleton<PromotionService>();
// OPC persistence (raw Npgsql)
var opcConnStr = builder.Configuration.GetConnectionString("opcdb");
if (!string.IsNullOrWhiteSpace(opcConnStr))
// Replace 'localhost' with '127.0.0.1' to avoid Npgsql trying [::1] first on Windows
builder.Services.AddSingleton(NpgsqlDataSource.Create(opcConnStr.Replace("localhost", "127.0.0.1")));
else
builder.Services.AddSingleton(NpgsqlDataSource.Create("Host=127.0.0.1;Port=5433;Database=opcdb;Username=postgres;Password=controlplane-dev"));
builder.Services.AddScoped<OpcService>();
// Named HttpClient for OpenRouter AI assist proxy
builder.Services.AddHttpClient("openrouter");
// Gitea integration
builder.Services.AddHttpClient("gitea").ConfigurePrimaryHttpMessageHandler(() =>
new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator });
builder.Services.AddScoped<GiteaService>();
builder.Services.AddMassTransit(x =>
{
x.SetKebabCaseEndpointNameFormatter();
// Receives ProvisioningProgressEvent from Worker and pushes to SSE
x.AddConsumer<ProvisioningProgressConsumer>();
x.UsingRabbitMq((ctx, cfg) =>
{
var connStr = builder.Configuration.GetConnectionString("rabbitmq");
if (!string.IsNullOrWhiteSpace(connStr))
cfg.Host(new Uri(connStr));
cfg.ConfigureEndpoints(ctx);
});
});
var app = builder.Build();
app.MapDefaultEndpoints();
if (app.Environment.IsDevelopment())
app.MapOpenApi();
app.MapProvisioningEndpoints();
app.MapTenantLogEndpoints();
app.MapImageBuildEndpoints();
app.MapReleaseEndpoints();
app.MapProjectBuildEndpoints();
app.MapGitEndpoints();
app.MapPromotionEndpoints();
app.MapOpcEndpoints();
app.MapGiteaEndpoints();
app.MapInfraEndpoints();
// Ensure OPC tables exist (idempotent — IF NOT EXISTS)
var ds = app.Services.GetRequiredService<NpgsqlDataSource>();
await using (var cmd = ds.CreateCommand("""
CREATE TABLE IF NOT EXISTS opc (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
number VARCHAR(20) NOT NULL UNIQUE,
title VARCHAR(500) NOT NULL,
description TEXT NOT NULL DEFAULT '',
type VARCHAR(50) NOT NULL DEFAULT 'General',
status VARCHAR(50) NOT NULL DEFAULT 'New',
priority VARCHAR(20) NOT NULL DEFAULT 'Medium',
assignee VARCHAR(200) NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS opc_note (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
opc_id UUID NOT NULL REFERENCES opc(id) ON DELETE CASCADE,
author VARCHAR(200) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS opc_artifact (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
opc_id UUID NOT NULL REFERENCES opc(id) ON DELETE CASCADE,
artifact_type VARCHAR(50) NOT NULL,
title VARCHAR(500) NOT NULL DEFAULT '',
content TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS opc_pinned_commit (
opc_id UUID NOT NULL REFERENCES opc(id) ON DELETE CASCADE,
hash VARCHAR(40) NOT NULL,
short_hash VARCHAR(10) NOT NULL DEFAULT '',
subject VARCHAR(1000) NOT NULL DEFAULT '',
author VARCHAR(200) NOT NULL DEFAULT '',
pinned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
pinned_by VARCHAR(200) NOT NULL DEFAULT '',
PRIMARY KEY (opc_id, hash)
);
CREATE INDEX IF NOT EXISTS ix_opc_number ON opc(number);
CREATE INDEX IF NOT EXISTS ix_opc_note_opc_id ON opc_note(opc_id);
CREATE INDEX IF NOT EXISTS ix_opc_artifact_opc_id ON opc_artifact(opc_id);
CREATE INDEX IF NOT EXISTS ix_opc_artifact_type ON opc_artifact(opc_id, artifact_type);
CREATE INDEX IF NOT EXISTS ix_opc_pinned_commit_opc_id ON opc_pinned_commit(opc_id);
"""))
await cmd.ExecuteNonQueryAsync();
app.Run();