273 lines
10 KiB
C#
273 lines
10 KiB
C#
using ZB.MOM.WW.ScadaBridge.Commons.Entities.ExternalSystems;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.Transport.Serialization;
|
|
|
|
/// <summary>
|
|
/// In-memory aggregate of all bundle-eligible Commons entities. Matches the
|
|
/// shape of <see cref="BundleContentDto"/> but uses the real persistence-
|
|
/// ignorant POCO types — what <see cref="EntitySerializer"/> consumes/produces
|
|
/// on the application side of the bundle boundary.
|
|
/// </summary>
|
|
// ApiKeys is intentionally absent: inbound API keys are not transported between
|
|
// environments (re-arch C4). The aggregate only carries API methods.
|
|
public sealed record EntityAggregate(
|
|
IReadOnlyList<TemplateFolder> TemplateFolders,
|
|
IReadOnlyList<Template> Templates,
|
|
IReadOnlyList<SharedScript> SharedScripts,
|
|
IReadOnlyList<ExternalSystemDefinition> ExternalSystems,
|
|
IReadOnlyList<ExternalSystemMethod> ExternalSystemMethods,
|
|
IReadOnlyList<DatabaseConnectionDefinition> DatabaseConnections,
|
|
IReadOnlyList<NotificationList> NotificationLists,
|
|
IReadOnlyList<SmtpConfiguration> SmtpConfigurations,
|
|
IReadOnlyList<ApiMethod> ApiMethods)
|
|
{
|
|
// M8: site/instance-scoped entities. Init-only with empty-array defaults so
|
|
// existing positional `new EntityAggregate(...)` callers keep compiling and
|
|
// never see a null collection; new callers opt in via object-initializer.
|
|
public IReadOnlyList<Site> Sites { get; init; } = Array.Empty<Site>();
|
|
public IReadOnlyList<DataConnection> DataConnections { get; init; } = Array.Empty<DataConnection>();
|
|
public IReadOnlyList<Instance> Instances { get; init; } = Array.Empty<Instance>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Top-level serializable bundle payload. Lists are sequenced in dependency
|
|
/// order so importers can apply them inline. Lists are never null on the wire
|
|
/// — empty arrays are preferred over nulls so JSON consumers can rely on each
|
|
/// property being present — except <see cref="ApiKeys"/>, which is intentionally
|
|
/// null-defaulted (see below).
|
|
/// <para>
|
|
/// <see cref="ApiKeys"/> is a <b>legacy, read-only</b> field retained purely for
|
|
/// backward-compatible deserialization of bundles produced before the
|
|
/// inbound-API-key re-architecture (C4). New exports never populate it (it stays
|
|
/// <c>null</c> and is dropped from the JSON by the serializer's
|
|
/// <c>WhenWritingNull</c> policy); the importer counts any keys present in an old
|
|
/// bundle, ignores them, and surfaces a note — keys are re-created per environment.
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed record BundleContentDto(
|
|
IReadOnlyList<TemplateFolderDto> TemplateFolders,
|
|
IReadOnlyList<TemplateDto> Templates,
|
|
IReadOnlyList<SharedScriptDto> SharedScripts,
|
|
IReadOnlyList<ExternalSystemDto> ExternalSystems,
|
|
IReadOnlyList<DatabaseConnectionDto> DatabaseConnections,
|
|
IReadOnlyList<NotificationListDto> NotificationLists,
|
|
IReadOnlyList<SmtpConfigDto> SmtpConfigs,
|
|
IReadOnlyList<ApiMethodDto> ApiMethods,
|
|
IReadOnlyList<ApiKeyDto>? ApiKeys = null)
|
|
{
|
|
// M8: site/instance-scoped payloads. Modeled as init-only properties with
|
|
// empty-array defaults (rather than additional positional ctor params) for
|
|
// two reasons:
|
|
// 1. Forward-compat: deserializing an OLDER bundle whose content blob has
|
|
// no sites/dataConnections/instances fields leaves each property at its
|
|
// Array.Empty<>() initializer — so consumers always see an empty list,
|
|
// never null. (Contrast ApiKeys, which is a nullable legacy field.)
|
|
// 2. Source-compat: every existing `new BundleContentDto(...)` positional
|
|
// caller keeps compiling unchanged; new producers opt in via an
|
|
// object-initializer (`new BundleContentDto(...) { Sites = ... }`).
|
|
// These are written by the serializer (WhenWritingNull does not apply — they
|
|
// are non-null), so new bundles always carry the three arrays explicitly.
|
|
public IReadOnlyList<SiteDto> Sites { get; init; } = Array.Empty<SiteDto>();
|
|
public IReadOnlyList<DataConnectionDto> DataConnections { get; init; } = Array.Empty<DataConnectionDto>();
|
|
public IReadOnlyList<InstanceDto> Instances { get; init; } = Array.Empty<InstanceDto>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Carved-off secret values for an entity. The outer DTO carries all non-
|
|
/// sensitive fields; secrets land here so a future "share without secrets"
|
|
/// export mode can drop this block without touching anything else.
|
|
/// </summary>
|
|
public sealed record SecretsBlock(IReadOnlyDictionary<string, string> Values);
|
|
|
|
public sealed record TemplateFolderDto(
|
|
string Name,
|
|
string? ParentName,
|
|
int SortOrder);
|
|
|
|
public sealed record TemplateDto(
|
|
string Name,
|
|
string? FolderName,
|
|
string? BaseTemplateName,
|
|
string? Description,
|
|
IReadOnlyList<TemplateAttributeDto> Attributes,
|
|
IReadOnlyList<TemplateAlarmDto> Alarms,
|
|
IReadOnlyList<TemplateScriptDto> Scripts,
|
|
IReadOnlyList<TemplateCompositionDto> Compositions);
|
|
|
|
public sealed record TemplateAttributeDto(
|
|
string Name,
|
|
string? Value,
|
|
DataType DataType,
|
|
bool IsLocked,
|
|
string? Description,
|
|
string? DataSourceReference,
|
|
DataType? ElementDataType = null);
|
|
|
|
public sealed record TemplateAlarmDto(
|
|
string Name,
|
|
string? Description,
|
|
int PriorityLevel,
|
|
AlarmTriggerType TriggerType,
|
|
string? TriggerConfiguration,
|
|
bool IsLocked,
|
|
string? OnTriggerScriptName);
|
|
|
|
public sealed record TemplateScriptDto(
|
|
string Name,
|
|
string Code,
|
|
string? TriggerType,
|
|
string? TriggerConfiguration,
|
|
string? ParameterDefinitions,
|
|
string? ReturnDefinition,
|
|
bool IsLocked,
|
|
TimeSpan? MinTimeBetweenRuns,
|
|
// M2.5 (#9): per-script execution timeout (seconds). Additive trailing field;
|
|
// null on bundles written before this field existed.
|
|
int? ExecutionTimeoutSeconds = null);
|
|
|
|
public sealed record TemplateCompositionDto(
|
|
string InstanceName,
|
|
string ComposedTemplateName);
|
|
|
|
public sealed record SharedScriptDto(
|
|
string Name,
|
|
string Code,
|
|
string? ParameterDefinitions,
|
|
string? ReturnDefinition);
|
|
|
|
public sealed record ExternalSystemDto(
|
|
string Name,
|
|
string BaseUrl,
|
|
string AuthType,
|
|
int MaxRetries,
|
|
TimeSpan RetryDelay,
|
|
IReadOnlyList<ExternalSystemMethodDto> Methods,
|
|
SecretsBlock? Secrets);
|
|
|
|
public sealed record ExternalSystemMethodDto(
|
|
string Name,
|
|
string HttpMethod,
|
|
string Path,
|
|
string? ParameterDefinitions,
|
|
string? ReturnDefinition);
|
|
|
|
public sealed record DatabaseConnectionDto(
|
|
string Name,
|
|
int MaxRetries,
|
|
TimeSpan RetryDelay,
|
|
SecretsBlock? Secrets);
|
|
|
|
public sealed record NotificationListDto(
|
|
string Name,
|
|
NotificationType Type,
|
|
IReadOnlyList<NotificationRecipientDto> Recipients);
|
|
|
|
public sealed record NotificationRecipientDto(
|
|
string Name,
|
|
string EmailAddress);
|
|
|
|
public sealed record SmtpConfigDto(
|
|
string Host,
|
|
int Port,
|
|
string AuthType,
|
|
string FromAddress,
|
|
string? TlsMode,
|
|
int ConnectionTimeoutSeconds,
|
|
int MaxConcurrentConnections,
|
|
int MaxRetries,
|
|
TimeSpan RetryDelay,
|
|
SecretsBlock? Secrets);
|
|
|
|
// Legacy DTO: only deserialized from pre-C4 bundles so the importer can count and
|
|
// ignore the keys they contain. New exports never emit an ApiKeys array.
|
|
public sealed record ApiKeyDto(
|
|
string Name,
|
|
string KeyHash,
|
|
bool IsEnabled,
|
|
SecretsBlock? Secrets);
|
|
|
|
// ApprovedApiKeyIds is intentionally absent: it linked methods to keys that are no
|
|
// longer transported (re-arch C4). Scopes are re-granted per environment. The field
|
|
// in any old bundle is simply ignored on read.
|
|
public sealed record ApiMethodDto(
|
|
string Name,
|
|
string Script,
|
|
string? ParameterDefinitions,
|
|
string? ReturnDefinition,
|
|
int TimeoutSeconds);
|
|
|
|
// --- M8: site/instance-scoped transport DTOs --------------------------------
|
|
// These travel alongside the original central-config DTOs above. Sites and
|
|
// instances are referenced by their stable string identities (SiteIdentifier,
|
|
// UniqueName, TemplateName) rather than database ids so a bundle resolves
|
|
// correctly against the *target* environment's own surrogate keys.
|
|
|
|
/// <summary>
|
|
/// A site definition. Addresses are carried verbatim; the importer decides
|
|
/// whether to keep or rewrite them for the target environment.
|
|
/// </summary>
|
|
public sealed record SiteDto(
|
|
string SiteIdentifier,
|
|
string Name,
|
|
string? Description,
|
|
string? NodeAAddress,
|
|
string? NodeBAddress,
|
|
string? GrpcNodeAAddress,
|
|
string? GrpcNodeBAddress);
|
|
|
|
/// <summary>
|
|
/// A site-scoped protocol connection (the <c>Sites.DataConnection</c> entity —
|
|
/// NOT the External-System <see cref="DatabaseConnectionDto"/>). The protocol-
|
|
/// specific Primary/Backup configuration JSON rides inside <see cref="Secrets"/>
|
|
/// so a "share without secrets" export can drop it as a unit.
|
|
/// </summary>
|
|
public sealed record DataConnectionDto(
|
|
string SiteIdentifier,
|
|
string Name,
|
|
string Protocol,
|
|
int FailoverRetryCount,
|
|
SecretsBlock? Secrets);
|
|
|
|
public sealed record InstanceAttributeOverrideDto(
|
|
string AttributeName,
|
|
string? OverrideValue,
|
|
DataType? ElementDataType);
|
|
|
|
public sealed record InstanceAlarmOverrideDto(
|
|
string AlarmCanonicalName,
|
|
string? TriggerConfigurationOverride,
|
|
int? PriorityLevelOverride);
|
|
|
|
public sealed record InstanceNativeAlarmSourceOverrideDto(
|
|
string SourceCanonicalName,
|
|
string? ConnectionNameOverride,
|
|
string? SourceReferenceOverride,
|
|
string? ConditionFilterOverride);
|
|
|
|
public sealed record InstanceConnectionBindingDto(
|
|
string AttributeName,
|
|
string ConnectionName,
|
|
string? DataSourceReferenceOverride);
|
|
|
|
/// <summary>
|
|
/// A deployable instance with all of its per-instance overrides and bindings.
|
|
/// References its template and site by name/identifier (not id).
|
|
/// </summary>
|
|
public sealed record InstanceDto(
|
|
string UniqueName,
|
|
string TemplateName,
|
|
string SiteIdentifier,
|
|
string? AreaName,
|
|
InstanceState State,
|
|
IReadOnlyList<InstanceAttributeOverrideDto> AttributeOverrides,
|
|
IReadOnlyList<InstanceAlarmOverrideDto> AlarmOverrides,
|
|
IReadOnlyList<InstanceNativeAlarmSourceOverrideDto> NativeAlarmSourceOverrides,
|
|
IReadOnlyList<InstanceConnectionBindingDto> ConnectionBindings);
|