Frontend gaps fill work. Testing fixes work. Auditing in progress.

This commit is contained in:
StellaOps Bot
2025-12-30 01:22:58 +02:00
parent 1dc4bcbf10
commit 7a5210e2aa
928 changed files with 183942 additions and 3941 deletions

View File

@@ -0,0 +1,37 @@
using StellaOps.Integrations.Core;
using StellaOps.Plugin;
namespace StellaOps.Integrations.Contracts;
/// <summary>
/// Plugin contract for integration connectors.
/// Each provider (GitHub, Harbor, ECR, etc.) implements this interface.
/// </summary>
public interface IIntegrationConnectorPlugin : IAvailabilityPlugin
{
/// <summary>
/// Integration type this plugin handles.
/// </summary>
IntegrationType Type { get; }
/// <summary>
/// Specific provider implementation.
/// </summary>
IntegrationProvider Provider { get; }
/// <summary>
/// Tests connectivity and authentication to the integration endpoint.
/// </summary>
/// <param name="config">Configuration including resolved secrets.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Result indicating success or failure with details.</returns>
Task<TestConnectionResult> TestConnectionAsync(IntegrationConfig config, CancellationToken cancellationToken = default);
/// <summary>
/// Performs a health check on the integration endpoint.
/// </summary>
/// <param name="config">Configuration including resolved secrets.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Health check result with status and details.</returns>
Task<HealthCheckResult> CheckHealthAsync(IntegrationConfig config, CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,97 @@
using StellaOps.Integrations.Core;
namespace StellaOps.Integrations.Contracts;
/// <summary>
/// Request DTO for creating an integration.
/// </summary>
public sealed record CreateIntegrationRequest(
string Name,
string? Description,
IntegrationType Type,
IntegrationProvider Provider,
string Endpoint,
string? AuthRefUri,
string? OrganizationId,
IReadOnlyDictionary<string, object>? ExtendedConfig,
IReadOnlyList<string>? Tags);
/// <summary>
/// Request DTO for updating an integration.
/// </summary>
public sealed record UpdateIntegrationRequest(
string? Name,
string? Description,
string? Endpoint,
string? AuthRefUri,
string? OrganizationId,
IReadOnlyDictionary<string, object>? ExtendedConfig,
IReadOnlyList<string>? Tags,
IntegrationStatus? Status);
/// <summary>
/// Response DTO for integration details.
/// </summary>
public sealed record IntegrationResponse(
Guid Id,
string Name,
string? Description,
IntegrationType Type,
IntegrationProvider Provider,
IntegrationStatus Status,
string Endpoint,
bool HasAuth,
string? OrganizationId,
HealthStatus LastHealthStatus,
DateTimeOffset? LastHealthCheckAt,
DateTimeOffset CreatedAt,
DateTimeOffset UpdatedAt,
string? CreatedBy,
string? UpdatedBy,
IReadOnlyList<string> Tags);
/// <summary>
/// Response DTO for test-connection operation.
/// </summary>
public sealed record TestConnectionResponse(
Guid IntegrationId,
bool Success,
string? Message,
IReadOnlyDictionary<string, string>? Details,
TimeSpan Duration,
DateTimeOffset TestedAt);
/// <summary>
/// Response DTO for health check operation.
/// </summary>
public sealed record HealthCheckResponse(
Guid IntegrationId,
HealthStatus Status,
string? Message,
IReadOnlyDictionary<string, string>? Details,
DateTimeOffset CheckedAt,
TimeSpan Duration);
/// <summary>
/// Query parameters for listing integrations.
/// </summary>
public sealed record ListIntegrationsQuery(
IntegrationType? Type = null,
IntegrationProvider? Provider = null,
IntegrationStatus? Status = null,
string? Search = null,
IReadOnlyList<string>? Tags = null,
int Page = 1,
int PageSize = 20,
string SortBy = "name",
bool SortDescending = false);
/// <summary>
/// Paginated response for integration listings.
/// </summary>
public sealed record PagedIntegrationsResponse(
IReadOnlyList<IntegrationResponse> Items,
int TotalCount,
int Page,
int PageSize,
int TotalPages);

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<RootNamespace>StellaOps.Integrations.Contracts</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Integrations.Core\StellaOps.Integrations.Core.csproj" />
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Plugin\StellaOps.Plugin.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,100 @@
namespace StellaOps.Integrations.Core;
/// <summary>
/// Core domain entity representing a configured integration.
/// </summary>
public sealed class Integration
{
public required Guid Id { get; init; }
/// <summary>
/// Human-readable name for the integration.
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Optional description.
/// </summary>
public string? Description { get; set; }
/// <summary>
/// Classification of integration by functional purpose.
/// </summary>
public required IntegrationType Type { get; init; }
/// <summary>
/// Specific provider implementation.
/// </summary>
public required IntegrationProvider Provider { get; init; }
/// <summary>
/// Lifecycle status.
/// </summary>
public IntegrationStatus Status { get; set; } = IntegrationStatus.Pending;
/// <summary>
/// Base URL or endpoint for the integration.
/// </summary>
public required string Endpoint { get; set; }
/// <summary>
/// Reference to stored credentials (AuthRef URI, never raw secret).
/// Format: authref://{vault}/{path}#{key} or similar.
/// </summary>
public string? AuthRefUri { get; set; }
/// <summary>
/// Organization or tenant identifier within the provider.
/// </summary>
public string? OrganizationId { get; set; }
/// <summary>
/// Additional provider-specific configuration as JSON.
/// </summary>
public string? ConfigJson { get; set; }
/// <summary>
/// Last health check result.
/// </summary>
public HealthStatus LastHealthStatus { get; set; } = HealthStatus.Unknown;
/// <summary>
/// UTC timestamp of last health check.
/// </summary>
public DateTimeOffset? LastHealthCheckAt { get; set; }
/// <summary>
/// UTC timestamp when the integration was created.
/// </summary>
public DateTimeOffset CreatedAt { get; init; } = DateTimeOffset.UtcNow;
/// <summary>
/// UTC timestamp when the integration was last updated.
/// </summary>
public DateTimeOffset UpdatedAt { get; set; } = DateTimeOffset.UtcNow;
/// <summary>
/// User or system that created this integration.
/// </summary>
public string? CreatedBy { get; init; }
/// <summary>
/// User or system that last modified this integration.
/// </summary>
public string? UpdatedBy { get; set; }
/// <summary>
/// Tenant/workspace isolation identifier.
/// </summary>
public string? TenantId { get; init; }
/// <summary>
/// Tags for filtering and grouping.
/// </summary>
public List<string> Tags { get; set; } = [];
/// <summary>
/// Soft-delete marker.
/// </summary>
public bool IsDeleted { get; set; }
}

View File

@@ -0,0 +1,113 @@
namespace StellaOps.Integrations.Core;
/// <summary>
/// Classification of integration by functional purpose.
/// </summary>
public enum IntegrationType
{
/// <summary>Container registry (Harbor, ECR, GCR, ACR, Docker Hub, etc.).</summary>
Registry = 1,
/// <summary>Source code management (GitHub, GitLab, Bitbucket, Gitea, etc.).</summary>
Scm = 2,
/// <summary>CI/CD system (GitHub Actions, GitLab CI, Jenkins, etc.).</summary>
CiCd = 3,
/// <summary>Repository source for packages (npm, PyPI, Maven, NuGet, etc.).</summary>
RepoSource = 4,
/// <summary>Runtime host for telemetry (eBPF, ETW, dyld, etc.).</summary>
RuntimeHost = 5,
/// <summary>Advisory/vulnerability feed mirror.</summary>
FeedMirror = 6
}
/// <summary>
/// Specific provider implementation within an integration type.
/// </summary>
public enum IntegrationProvider
{
// Registry providers
Harbor = 100,
Ecr = 101,
Gcr = 102,
Acr = 103,
DockerHub = 104,
Quay = 105,
Artifactory = 106,
Nexus = 107,
GitHubContainerRegistry = 108,
GitLabContainerRegistry = 109,
// SCM providers
GitHubApp = 200,
GitLabServer = 201,
Bitbucket = 202,
Gitea = 203,
AzureDevOps = 204,
// CI/CD providers
GitHubActions = 300,
GitLabCi = 301,
Jenkins = 302,
CircleCi = 303,
AzurePipelines = 304,
ArgoWorkflows = 305,
Tekton = 306,
// Repo sources
NpmRegistry = 400,
PyPi = 401,
MavenCentral = 402,
NuGetOrg = 403,
CratesIo = 404,
GoProxy = 405,
// Runtime hosts
EbpfAgent = 500,
EtwAgent = 501,
DyldInterposer = 502,
// Feed mirrors
StellaOpsMirror = 600,
NvdMirror = 601,
OsvMirror = 602,
// Generic / testing
InMemory = 900,
Custom = 999
}
/// <summary>
/// Lifecycle status of an integration instance.
/// </summary>
public enum IntegrationStatus
{
/// <summary>Just created, not yet tested.</summary>
Pending = 0,
/// <summary>Connection test passed.</summary>
Active = 1,
/// <summary>Connection test failed.</summary>
Failed = 2,
/// <summary>Administratively disabled.</summary>
Disabled = 3,
/// <summary>Marked for deletion.</summary>
Archived = 4
}
/// <summary>
/// Health check result status.
/// </summary>
public enum HealthStatus
{
Unknown = 0,
Healthy = 1,
Degraded = 2,
Unhealthy = 3
}

View File

@@ -0,0 +1,74 @@
namespace StellaOps.Integrations.Core;
/// <summary>
/// Configuration passed to connector plugins for test-connection and health checks.
/// </summary>
public sealed record IntegrationConfig(
Guid IntegrationId,
IntegrationType Type,
IntegrationProvider Provider,
string Endpoint,
string? ResolvedSecret,
string? OrganizationId,
IReadOnlyDictionary<string, object>? ExtendedConfig);
/// <summary>
/// Result of a test-connection operation.
/// </summary>
public sealed record TestConnectionResult(
bool Success,
string? Message,
IReadOnlyDictionary<string, string>? Details,
TimeSpan Duration);
/// <summary>
/// Result of a health check operation.
/// </summary>
public sealed record HealthCheckResult(
HealthStatus Status,
string? Message,
IReadOnlyDictionary<string, string>? Details,
DateTimeOffset CheckedAt,
TimeSpan Duration);
/// <summary>
/// Integration lifecycle events for downstream consumers.
/// </summary>
public abstract record IntegrationEvent(Guid IntegrationId, DateTimeOffset OccurredAt);
public sealed record IntegrationCreatedEvent(
Guid IntegrationId,
string Name,
IntegrationType Type,
IntegrationProvider Provider,
string? CreatedBy,
DateTimeOffset OccurredAt) : IntegrationEvent(IntegrationId, OccurredAt);
public sealed record IntegrationUpdatedEvent(
Guid IntegrationId,
string Name,
string? UpdatedBy,
DateTimeOffset OccurredAt) : IntegrationEvent(IntegrationId, OccurredAt);
public sealed record IntegrationDeletedEvent(
Guid IntegrationId,
string? DeletedBy,
DateTimeOffset OccurredAt) : IntegrationEvent(IntegrationId, OccurredAt);
public sealed record IntegrationStatusChangedEvent(
Guid IntegrationId,
IntegrationStatus OldStatus,
IntegrationStatus NewStatus,
DateTimeOffset OccurredAt) : IntegrationEvent(IntegrationId, OccurredAt);
public sealed record IntegrationHealthChangedEvent(
Guid IntegrationId,
HealthStatus OldHealth,
HealthStatus NewHealth,
DateTimeOffset OccurredAt) : IntegrationEvent(IntegrationId, OccurredAt);
public sealed record IntegrationTestConnectionEvent(
Guid IntegrationId,
bool Success,
string? Message,
DateTimeOffset OccurredAt) : IntegrationEvent(IntegrationId, OccurredAt);

View File

@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<RootNamespace>StellaOps.Integrations.Core</RootNamespace>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,35 @@
using StellaOps.Integrations.Core;
namespace StellaOps.Integrations.Persistence;
/// <summary>
/// Repository contract for integration persistence.
/// </summary>
public interface IIntegrationRepository
{
Task<Integration?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Integration>> GetAllAsync(IntegrationQuery query, CancellationToken cancellationToken = default);
Task<int> CountAsync(IntegrationQuery query, CancellationToken cancellationToken = default);
Task<Integration> CreateAsync(Integration integration, CancellationToken cancellationToken = default);
Task<Integration> UpdateAsync(Integration integration, CancellationToken cancellationToken = default);
Task DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Integration>> GetByProviderAsync(IntegrationProvider provider, CancellationToken cancellationToken = default);
Task<IReadOnlyList<Integration>> GetActiveByTypeAsync(IntegrationType type, CancellationToken cancellationToken = default);
Task UpdateHealthStatusAsync(Guid id, HealthStatus status, DateTimeOffset checkedAt, CancellationToken cancellationToken = default);
}
/// <summary>
/// Query parameters for repository operations.
/// </summary>
public sealed record IntegrationQuery(
IntegrationType? Type = null,
IntegrationProvider? Provider = null,
IntegrationStatus? Status = null,
string? Search = null,
IReadOnlyList<string>? Tags = null,
string? TenantId = null,
bool IncludeDeleted = false,
int Skip = 0,
int Take = 20,
string SortBy = "name",
bool SortDescending = false);

View File

@@ -0,0 +1,83 @@
using Microsoft.EntityFrameworkCore;
using StellaOps.Integrations.Core;
namespace StellaOps.Integrations.Persistence;
/// <summary>
/// EF Core DbContext for Integration persistence.
/// </summary>
public sealed class IntegrationDbContext : DbContext
{
public IntegrationDbContext(DbContextOptions<IntegrationDbContext> options)
: base(options)
{
}
public DbSet<IntegrationEntity> Integrations => Set<IntegrationEntity>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<IntegrationEntity>(entity =>
{
entity.ToTable("integrations");
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Name).HasColumnName("name").HasMaxLength(256).IsRequired();
entity.Property(e => e.Description).HasColumnName("description").HasMaxLength(1024);
entity.Property(e => e.Type).HasColumnName("type").IsRequired();
entity.Property(e => e.Provider).HasColumnName("provider").IsRequired();
entity.Property(e => e.Status).HasColumnName("status").IsRequired();
entity.Property(e => e.Endpoint).HasColumnName("endpoint").HasMaxLength(2048).IsRequired();
entity.Property(e => e.AuthRefUri).HasColumnName("auth_ref_uri").HasMaxLength(1024);
entity.Property(e => e.OrganizationId).HasColumnName("organization_id").HasMaxLength(256);
entity.Property(e => e.ConfigJson).HasColumnName("config_json").HasColumnType("jsonb");
entity.Property(e => e.LastHealthStatus).HasColumnName("last_health_status");
entity.Property(e => e.LastHealthCheckAt).HasColumnName("last_health_check_at");
entity.Property(e => e.CreatedAt).HasColumnName("created_at").IsRequired();
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at").IsRequired();
entity.Property(e => e.CreatedBy).HasColumnName("created_by").HasMaxLength(256);
entity.Property(e => e.UpdatedBy).HasColumnName("updated_by").HasMaxLength(256);
entity.Property(e => e.TenantId).HasColumnName("tenant_id").HasMaxLength(128);
entity.Property(e => e.TagsJson).HasColumnName("tags").HasColumnType("jsonb");
entity.Property(e => e.IsDeleted).HasColumnName("is_deleted").IsRequired();
entity.HasIndex(e => e.Type);
entity.HasIndex(e => e.Provider);
entity.HasIndex(e => e.Status);
entity.HasIndex(e => e.TenantId);
entity.HasIndex(e => new { e.TenantId, e.Name }).IsUnique().HasFilter("is_deleted = false");
});
}
}
/// <summary>
/// EF Core entity for Integration.
/// </summary>
public sealed class IntegrationEntity
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public string? Description { get; set; }
public IntegrationType Type { get; set; }
public IntegrationProvider Provider { get; set; }
public IntegrationStatus Status { get; set; }
public string Endpoint { get; set; } = string.Empty;
public string? AuthRefUri { get; set; }
public string? OrganizationId { get; set; }
public string? ConfigJson { get; set; }
public HealthStatus LastHealthStatus { get; set; }
public DateTimeOffset? LastHealthCheckAt { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public DateTimeOffset UpdatedAt { get; set; }
public string? CreatedBy { get; set; }
public string? UpdatedBy { get; set; }
public string? TenantId { get; set; }
public string? TagsJson { get; set; }
public bool IsDeleted { get; set; }
}

View File

@@ -0,0 +1,229 @@
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using StellaOps.Integrations.Core;
namespace StellaOps.Integrations.Persistence;
/// <summary>
/// PostgreSQL implementation of integration repository.
/// </summary>
public sealed class PostgresIntegrationRepository : IIntegrationRepository
{
private readonly IntegrationDbContext _context;
public PostgresIntegrationRepository(IntegrationDbContext context)
{
_context = context;
}
public async Task<Integration?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
var entity = await _context.Integrations
.AsNoTracking()
.FirstOrDefaultAsync(e => e.Id == id && !e.IsDeleted, cancellationToken);
return entity is null ? null : MapToDomain(entity);
}
public async Task<IReadOnlyList<Integration>> GetAllAsync(IntegrationQuery query, CancellationToken cancellationToken = default)
{
var dbQuery = BuildQuery(query);
dbQuery = query.SortBy.ToLowerInvariant() switch
{
"name" => query.SortDescending ? dbQuery.OrderByDescending(e => e.Name) : dbQuery.OrderBy(e => e.Name),
"createdat" => query.SortDescending ? dbQuery.OrderByDescending(e => e.CreatedAt) : dbQuery.OrderBy(e => e.CreatedAt),
"updatedat" => query.SortDescending ? dbQuery.OrderByDescending(e => e.UpdatedAt) : dbQuery.OrderBy(e => e.UpdatedAt),
"status" => query.SortDescending ? dbQuery.OrderByDescending(e => e.Status) : dbQuery.OrderBy(e => e.Status),
_ => dbQuery.OrderBy(e => e.Name)
};
var entities = await dbQuery
.Skip(query.Skip)
.Take(query.Take)
.AsNoTracking()
.ToListAsync(cancellationToken);
return entities.Select(MapToDomain).ToList();
}
public async Task<int> CountAsync(IntegrationQuery query, CancellationToken cancellationToken = default)
{
return await BuildQuery(query).CountAsync(cancellationToken);
}
public async Task<Integration> CreateAsync(Integration integration, CancellationToken cancellationToken = default)
{
var entity = MapToEntity(integration);
_context.Integrations.Add(entity);
await _context.SaveChangesAsync(cancellationToken);
return MapToDomain(entity);
}
public async Task<Integration> UpdateAsync(Integration integration, CancellationToken cancellationToken = default)
{
var entity = await _context.Integrations
.FirstOrDefaultAsync(e => e.Id == integration.Id, cancellationToken)
?? throw new InvalidOperationException($"Integration {integration.Id} not found");
entity.Name = integration.Name;
entity.Description = integration.Description;
entity.Status = integration.Status;
entity.Endpoint = integration.Endpoint;
entity.AuthRefUri = integration.AuthRefUri;
entity.OrganizationId = integration.OrganizationId;
entity.ConfigJson = integration.ConfigJson;
entity.LastHealthStatus = integration.LastHealthStatus;
entity.LastHealthCheckAt = integration.LastHealthCheckAt;
entity.UpdatedAt = integration.UpdatedAt;
entity.UpdatedBy = integration.UpdatedBy;
entity.TagsJson = integration.Tags.Count > 0 ? JsonSerializer.Serialize(integration.Tags) : null;
entity.IsDeleted = integration.IsDeleted;
await _context.SaveChangesAsync(cancellationToken);
return MapToDomain(entity);
}
public async Task DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var entity = await _context.Integrations
.FirstOrDefaultAsync(e => e.Id == id, cancellationToken);
if (entity is not null)
{
entity.IsDeleted = true;
entity.Status = IntegrationStatus.Archived;
entity.UpdatedAt = DateTimeOffset.UtcNow;
await _context.SaveChangesAsync(cancellationToken);
}
}
public async Task<IReadOnlyList<Integration>> GetByProviderAsync(IntegrationProvider provider, CancellationToken cancellationToken = default)
{
var entities = await _context.Integrations
.Where(e => e.Provider == provider && !e.IsDeleted)
.AsNoTracking()
.ToListAsync(cancellationToken);
return entities.Select(MapToDomain).ToList();
}
public async Task<IReadOnlyList<Integration>> GetActiveByTypeAsync(IntegrationType type, CancellationToken cancellationToken = default)
{
var entities = await _context.Integrations
.Where(e => e.Type == type && e.Status == IntegrationStatus.Active && !e.IsDeleted)
.AsNoTracking()
.ToListAsync(cancellationToken);
return entities.Select(MapToDomain).ToList();
}
public async Task UpdateHealthStatusAsync(Guid id, HealthStatus status, DateTimeOffset checkedAt, CancellationToken cancellationToken = default)
{
var entity = await _context.Integrations
.FirstOrDefaultAsync(e => e.Id == id, cancellationToken);
if (entity is not null)
{
entity.LastHealthStatus = status;
entity.LastHealthCheckAt = checkedAt;
await _context.SaveChangesAsync(cancellationToken);
}
}
private IQueryable<IntegrationEntity> BuildQuery(IntegrationQuery query)
{
var dbQuery = _context.Integrations.AsQueryable();
if (!query.IncludeDeleted)
{
dbQuery = dbQuery.Where(e => !e.IsDeleted);
}
if (query.TenantId is not null)
{
dbQuery = dbQuery.Where(e => e.TenantId == query.TenantId);
}
if (query.Type.HasValue)
{
dbQuery = dbQuery.Where(e => e.Type == query.Type.Value);
}
if (query.Provider.HasValue)
{
dbQuery = dbQuery.Where(e => e.Provider == query.Provider.Value);
}
if (query.Status.HasValue)
{
dbQuery = dbQuery.Where(e => e.Status == query.Status.Value);
}
if (!string.IsNullOrWhiteSpace(query.Search))
{
var searchLower = query.Search.ToLowerInvariant();
dbQuery = dbQuery.Where(e =>
e.Name.ToLower().Contains(searchLower) ||
(e.Description != null && e.Description.ToLower().Contains(searchLower)));
}
return dbQuery;
}
private static Integration MapToDomain(IntegrationEntity entity)
{
var tags = string.IsNullOrEmpty(entity.TagsJson)
? new List<string>()
: JsonSerializer.Deserialize<List<string>>(entity.TagsJson) ?? new List<string>();
return new Integration
{
Id = entity.Id,
Name = entity.Name,
Description = entity.Description,
Type = entity.Type,
Provider = entity.Provider,
Status = entity.Status,
Endpoint = entity.Endpoint,
AuthRefUri = entity.AuthRefUri,
OrganizationId = entity.OrganizationId,
ConfigJson = entity.ConfigJson,
LastHealthStatus = entity.LastHealthStatus,
LastHealthCheckAt = entity.LastHealthCheckAt,
CreatedAt = entity.CreatedAt,
UpdatedAt = entity.UpdatedAt,
CreatedBy = entity.CreatedBy,
UpdatedBy = entity.UpdatedBy,
TenantId = entity.TenantId,
Tags = tags,
IsDeleted = entity.IsDeleted
};
}
private static IntegrationEntity MapToEntity(Integration integration)
{
return new IntegrationEntity
{
Id = integration.Id,
Name = integration.Name,
Description = integration.Description,
Type = integration.Type,
Provider = integration.Provider,
Status = integration.Status,
Endpoint = integration.Endpoint,
AuthRefUri = integration.AuthRefUri,
OrganizationId = integration.OrganizationId,
ConfigJson = integration.ConfigJson,
LastHealthStatus = integration.LastHealthStatus,
LastHealthCheckAt = integration.LastHealthCheckAt,
CreatedAt = integration.CreatedAt,
UpdatedAt = integration.UpdatedAt,
CreatedBy = integration.CreatedBy,
UpdatedBy = integration.UpdatedBy,
TenantId = integration.TenantId,
TagsJson = integration.Tags.Count > 0 ? JsonSerializer.Serialize(integration.Tags) : null,
IsDeleted = integration.IsDeleted
};
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<RootNamespace>StellaOps.Integrations.Persistence</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.Integrations.Core\StellaOps.Integrations.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" />
</ItemGroup>
</Project>