up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
sdk-generator-smoke / sdk-smoke (push) Has been cancelled
SDK Publish & Sign / sdk-publish (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled
oas-ci / oas-validate (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,401 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Notify.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Quiet hours schedule configuration for suppressing notifications during specified periods.
|
||||
/// </summary>
|
||||
public sealed record NotifyQuietHoursSchedule
|
||||
{
|
||||
[JsonConstructor]
|
||||
public NotifyQuietHoursSchedule(
|
||||
string scheduleId,
|
||||
string tenantId,
|
||||
string name,
|
||||
string cronExpression,
|
||||
TimeSpan duration,
|
||||
string timeZone,
|
||||
string? channelId = null,
|
||||
bool enabled = true,
|
||||
string? description = null,
|
||||
ImmutableDictionary<string, string>? metadata = null,
|
||||
string? createdBy = null,
|
||||
DateTimeOffset? createdAt = null,
|
||||
string? updatedBy = null,
|
||||
DateTimeOffset? updatedAt = null)
|
||||
{
|
||||
ScheduleId = NotifyValidation.EnsureNotNullOrWhiteSpace(scheduleId, nameof(scheduleId));
|
||||
TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId));
|
||||
Name = NotifyValidation.EnsureNotNullOrWhiteSpace(name, nameof(name));
|
||||
CronExpression = NotifyValidation.EnsureNotNullOrWhiteSpace(cronExpression, nameof(cronExpression));
|
||||
Duration = duration > TimeSpan.Zero ? duration : TimeSpan.FromHours(8);
|
||||
TimeZone = NotifyValidation.EnsureNotNullOrWhiteSpace(timeZone, nameof(timeZone));
|
||||
ChannelId = NotifyValidation.TrimToNull(channelId);
|
||||
Enabled = enabled;
|
||||
Description = NotifyValidation.TrimToNull(description);
|
||||
Metadata = NotifyValidation.NormalizeStringDictionary(metadata);
|
||||
CreatedBy = NotifyValidation.TrimToNull(createdBy);
|
||||
CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow);
|
||||
UpdatedBy = NotifyValidation.TrimToNull(updatedBy);
|
||||
UpdatedAt = NotifyValidation.EnsureUtc(updatedAt ?? CreatedAt);
|
||||
}
|
||||
|
||||
public static NotifyQuietHoursSchedule Create(
|
||||
string scheduleId,
|
||||
string tenantId,
|
||||
string name,
|
||||
string cronExpression,
|
||||
TimeSpan duration,
|
||||
string timeZone,
|
||||
string? channelId = null,
|
||||
bool enabled = true,
|
||||
string? description = null,
|
||||
IEnumerable<KeyValuePair<string, string>>? metadata = null,
|
||||
string? createdBy = null,
|
||||
DateTimeOffset? createdAt = null,
|
||||
string? updatedBy = null,
|
||||
DateTimeOffset? updatedAt = null)
|
||||
{
|
||||
return new NotifyQuietHoursSchedule(
|
||||
scheduleId,
|
||||
tenantId,
|
||||
name,
|
||||
cronExpression,
|
||||
duration,
|
||||
timeZone,
|
||||
channelId,
|
||||
enabled,
|
||||
description,
|
||||
ToImmutableDictionary(metadata),
|
||||
createdBy,
|
||||
createdAt,
|
||||
updatedBy,
|
||||
updatedAt);
|
||||
}
|
||||
|
||||
public string ScheduleId { get; }
|
||||
|
||||
public string TenantId { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Cron expression defining when quiet hours start.
|
||||
/// </summary>
|
||||
public string CronExpression { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Duration of the quiet hours window.
|
||||
/// </summary>
|
||||
public TimeSpan Duration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// IANA time zone for evaluating the cron expression (e.g., "America/New_York").
|
||||
/// </summary>
|
||||
public string TimeZone { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional channel ID to scope quiet hours to a specific channel.
|
||||
/// If null, applies to all channels.
|
||||
/// </summary>
|
||||
public string? ChannelId { get; }
|
||||
|
||||
public bool Enabled { get; }
|
||||
|
||||
public string? Description { get; }
|
||||
|
||||
public ImmutableDictionary<string, string> Metadata { get; }
|
||||
|
||||
public string? CreatedBy { get; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; }
|
||||
|
||||
public string? UpdatedBy { get; }
|
||||
|
||||
public DateTimeOffset UpdatedAt { get; }
|
||||
|
||||
private static ImmutableDictionary<string, string>? ToImmutableDictionary(IEnumerable<KeyValuePair<string, string>>? pairs)
|
||||
{
|
||||
if (pairs is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal);
|
||||
foreach (var (key, value) in pairs)
|
||||
{
|
||||
builder[key] = value;
|
||||
}
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maintenance window for planned suppression of notifications.
|
||||
/// </summary>
|
||||
public sealed record NotifyMaintenanceWindow
|
||||
{
|
||||
[JsonConstructor]
|
||||
public NotifyMaintenanceWindow(
|
||||
string windowId,
|
||||
string tenantId,
|
||||
string name,
|
||||
DateTimeOffset startsAt,
|
||||
DateTimeOffset endsAt,
|
||||
bool suppressNotifications = true,
|
||||
string? reason = null,
|
||||
ImmutableArray<string> channelIds = default,
|
||||
ImmutableArray<string> ruleIds = default,
|
||||
ImmutableDictionary<string, string>? metadata = null,
|
||||
string? createdBy = null,
|
||||
DateTimeOffset? createdAt = null,
|
||||
string? updatedBy = null,
|
||||
DateTimeOffset? updatedAt = null)
|
||||
{
|
||||
WindowId = NotifyValidation.EnsureNotNullOrWhiteSpace(windowId, nameof(windowId));
|
||||
TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId));
|
||||
Name = NotifyValidation.EnsureNotNullOrWhiteSpace(name, nameof(name));
|
||||
StartsAt = NotifyValidation.EnsureUtc(startsAt);
|
||||
EndsAt = NotifyValidation.EnsureUtc(endsAt);
|
||||
SuppressNotifications = suppressNotifications;
|
||||
Reason = NotifyValidation.TrimToNull(reason);
|
||||
ChannelIds = NormalizeStringArray(channelIds);
|
||||
RuleIds = NormalizeStringArray(ruleIds);
|
||||
Metadata = NotifyValidation.NormalizeStringDictionary(metadata);
|
||||
CreatedBy = NotifyValidation.TrimToNull(createdBy);
|
||||
CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow);
|
||||
UpdatedBy = NotifyValidation.TrimToNull(updatedBy);
|
||||
UpdatedAt = NotifyValidation.EnsureUtc(updatedAt ?? CreatedAt);
|
||||
|
||||
if (EndsAt <= StartsAt)
|
||||
{
|
||||
throw new ArgumentException("EndsAt must be after StartsAt.", nameof(endsAt));
|
||||
}
|
||||
}
|
||||
|
||||
public static NotifyMaintenanceWindow Create(
|
||||
string windowId,
|
||||
string tenantId,
|
||||
string name,
|
||||
DateTimeOffset startsAt,
|
||||
DateTimeOffset endsAt,
|
||||
bool suppressNotifications = true,
|
||||
string? reason = null,
|
||||
IEnumerable<string>? channelIds = null,
|
||||
IEnumerable<string>? ruleIds = null,
|
||||
IEnumerable<KeyValuePair<string, string>>? metadata = null,
|
||||
string? createdBy = null,
|
||||
DateTimeOffset? createdAt = null,
|
||||
string? updatedBy = null,
|
||||
DateTimeOffset? updatedAt = null)
|
||||
{
|
||||
return new NotifyMaintenanceWindow(
|
||||
windowId,
|
||||
tenantId,
|
||||
name,
|
||||
startsAt,
|
||||
endsAt,
|
||||
suppressNotifications,
|
||||
reason,
|
||||
ToImmutableArray(channelIds),
|
||||
ToImmutableArray(ruleIds),
|
||||
ToImmutableDictionary(metadata),
|
||||
createdBy,
|
||||
createdAt,
|
||||
updatedBy,
|
||||
updatedAt);
|
||||
}
|
||||
|
||||
public string WindowId { get; }
|
||||
|
||||
public string TenantId { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public DateTimeOffset StartsAt { get; }
|
||||
|
||||
public DateTimeOffset EndsAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to suppress notifications during the maintenance window.
|
||||
/// </summary>
|
||||
public bool SuppressNotifications { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Reason for the maintenance window.
|
||||
/// </summary>
|
||||
public string? Reason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional list of channel IDs to scope the maintenance window.
|
||||
/// If empty, applies to all channels.
|
||||
/// </summary>
|
||||
public ImmutableArray<string> ChannelIds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional list of rule IDs to scope the maintenance window.
|
||||
/// If empty, applies to all rules.
|
||||
/// </summary>
|
||||
public ImmutableArray<string> RuleIds { get; }
|
||||
|
||||
public ImmutableDictionary<string, string> Metadata { get; }
|
||||
|
||||
public string? CreatedBy { get; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; }
|
||||
|
||||
public string? UpdatedBy { get; }
|
||||
|
||||
public DateTimeOffset UpdatedAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the maintenance window is active at the specified time.
|
||||
/// </summary>
|
||||
public bool IsActiveAt(DateTimeOffset timestamp)
|
||||
=> SuppressNotifications && timestamp >= StartsAt && timestamp < EndsAt;
|
||||
|
||||
private static ImmutableArray<string> NormalizeStringArray(ImmutableArray<string> values)
|
||||
{
|
||||
if (values.IsDefaultOrEmpty)
|
||||
{
|
||||
return ImmutableArray<string>.Empty;
|
||||
}
|
||||
|
||||
return values
|
||||
.Where(static v => !string.IsNullOrWhiteSpace(v))
|
||||
.Select(static v => v.Trim())
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToImmutableArray();
|
||||
}
|
||||
|
||||
private static ImmutableArray<string> ToImmutableArray(IEnumerable<string>? values)
|
||||
=> values is null ? ImmutableArray<string>.Empty : values.ToImmutableArray();
|
||||
|
||||
private static ImmutableDictionary<string, string>? ToImmutableDictionary(IEnumerable<KeyValuePair<string, string>>? pairs)
|
||||
{
|
||||
if (pairs is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal);
|
||||
foreach (var (key, value) in pairs)
|
||||
{
|
||||
builder[key] = value;
|
||||
}
|
||||
|
||||
return builder.ToImmutable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Operator override for quiet hours or throttle configuration.
|
||||
/// Allows an operator to temporarily bypass quiet hours or throttling.
|
||||
/// </summary>
|
||||
public sealed record NotifyOperatorOverride
|
||||
{
|
||||
[JsonConstructor]
|
||||
public NotifyOperatorOverride(
|
||||
string overrideId,
|
||||
string tenantId,
|
||||
NotifyOverrideType overrideType,
|
||||
DateTimeOffset expiresAt,
|
||||
string? channelId = null,
|
||||
string? ruleId = null,
|
||||
string? reason = null,
|
||||
string? createdBy = null,
|
||||
DateTimeOffset? createdAt = null)
|
||||
{
|
||||
OverrideId = NotifyValidation.EnsureNotNullOrWhiteSpace(overrideId, nameof(overrideId));
|
||||
TenantId = NotifyValidation.EnsureNotNullOrWhiteSpace(tenantId, nameof(tenantId));
|
||||
OverrideType = overrideType;
|
||||
ExpiresAt = NotifyValidation.EnsureUtc(expiresAt);
|
||||
ChannelId = NotifyValidation.TrimToNull(channelId);
|
||||
RuleId = NotifyValidation.TrimToNull(ruleId);
|
||||
Reason = NotifyValidation.TrimToNull(reason);
|
||||
CreatedBy = NotifyValidation.TrimToNull(createdBy);
|
||||
CreatedAt = NotifyValidation.EnsureUtc(createdAt ?? DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
public static NotifyOperatorOverride Create(
|
||||
string overrideId,
|
||||
string tenantId,
|
||||
NotifyOverrideType overrideType,
|
||||
DateTimeOffset expiresAt,
|
||||
string? channelId = null,
|
||||
string? ruleId = null,
|
||||
string? reason = null,
|
||||
string? createdBy = null,
|
||||
DateTimeOffset? createdAt = null)
|
||||
{
|
||||
return new NotifyOperatorOverride(
|
||||
overrideId,
|
||||
tenantId,
|
||||
overrideType,
|
||||
expiresAt,
|
||||
channelId,
|
||||
ruleId,
|
||||
reason,
|
||||
createdBy,
|
||||
createdAt);
|
||||
}
|
||||
|
||||
public string OverrideId { get; }
|
||||
|
||||
public string TenantId { get; }
|
||||
|
||||
public NotifyOverrideType OverrideType { get; }
|
||||
|
||||
public DateTimeOffset ExpiresAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional channel ID to scope the override.
|
||||
/// </summary>
|
||||
public string? ChannelId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional rule ID to scope the override.
|
||||
/// </summary>
|
||||
public string? RuleId { get; }
|
||||
|
||||
public string? Reason { get; }
|
||||
|
||||
public string? CreatedBy { get; }
|
||||
|
||||
public DateTimeOffset CreatedAt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the override is active at the specified time.
|
||||
/// </summary>
|
||||
public bool IsActiveAt(DateTimeOffset timestamp)
|
||||
=> timestamp < ExpiresAt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Type of operator override.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public enum NotifyOverrideType
|
||||
{
|
||||
/// <summary>
|
||||
/// Bypass quiet hours.
|
||||
/// </summary>
|
||||
BypassQuietHours,
|
||||
|
||||
/// <summary>
|
||||
/// Bypass throttling.
|
||||
/// </summary>
|
||||
BypassThrottle,
|
||||
|
||||
/// <summary>
|
||||
/// Bypass maintenance window.
|
||||
/// </summary>
|
||||
BypassMaintenance,
|
||||
|
||||
/// <summary>
|
||||
/// Force suppress notifications.
|
||||
/// </summary>
|
||||
ForceSuppression
|
||||
}
|
||||
Reference in New Issue
Block a user