using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace StellaOps.Notify.Models;
///
/// Quiet hours schedule configuration for suppressing notifications during specified periods.
///
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? 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>? 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; }
///
/// Cron expression defining when quiet hours start.
///
public string CronExpression { get; }
///
/// Duration of the quiet hours window.
///
public TimeSpan Duration { get; }
///
/// IANA time zone for evaluating the cron expression (e.g., "America/New_York").
///
public string TimeZone { get; }
///
/// Optional channel ID to scope quiet hours to a specific channel.
/// If null, applies to all channels.
///
public string? ChannelId { get; }
public bool Enabled { get; }
public string? Description { get; }
public ImmutableDictionary Metadata { get; }
public string? CreatedBy { get; }
public DateTimeOffset CreatedAt { get; }
public string? UpdatedBy { get; }
public DateTimeOffset UpdatedAt { get; }
private static ImmutableDictionary? ToImmutableDictionary(IEnumerable>? pairs)
{
if (pairs is null)
{
return null;
}
var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal);
foreach (var (key, value) in pairs)
{
builder[key] = value;
}
return builder.ToImmutable();
}
}
///
/// Maintenance window for planned suppression of notifications.
///
public sealed record NotifyMaintenanceWindow
{
[JsonConstructor]
public NotifyMaintenanceWindow(
string windowId,
string tenantId,
string name,
DateTimeOffset startsAt,
DateTimeOffset endsAt,
bool suppressNotifications = true,
string? reason = null,
ImmutableArray channelIds = default,
ImmutableArray ruleIds = default,
ImmutableDictionary? 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? channelIds = null,
IEnumerable? ruleIds = null,
IEnumerable>? 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; }
///
/// Whether to suppress notifications during the maintenance window.
///
public bool SuppressNotifications { get; }
///
/// Reason for the maintenance window.
///
public string? Reason { get; }
///
/// Optional list of channel IDs to scope the maintenance window.
/// If empty, applies to all channels.
///
public ImmutableArray ChannelIds { get; }
///
/// Optional list of rule IDs to scope the maintenance window.
/// If empty, applies to all rules.
///
public ImmutableArray RuleIds { get; }
public ImmutableDictionary Metadata { get; }
public string? CreatedBy { get; }
public DateTimeOffset CreatedAt { get; }
public string? UpdatedBy { get; }
public DateTimeOffset UpdatedAt { get; }
///
/// Checks if the maintenance window is active at the specified time.
///
public bool IsActiveAt(DateTimeOffset timestamp)
=> SuppressNotifications && timestamp >= StartsAt && timestamp < EndsAt;
private static ImmutableArray NormalizeStringArray(ImmutableArray values)
{
if (values.IsDefaultOrEmpty)
{
return ImmutableArray.Empty;
}
return values
.Where(static v => !string.IsNullOrWhiteSpace(v))
.Select(static v => v.Trim())
.Distinct(StringComparer.Ordinal)
.ToImmutableArray();
}
private static ImmutableArray ToImmutableArray(IEnumerable? values)
=> values is null ? ImmutableArray.Empty : values.ToImmutableArray();
private static ImmutableDictionary? ToImmutableDictionary(IEnumerable>? pairs)
{
if (pairs is null)
{
return null;
}
var builder = ImmutableDictionary.CreateBuilder(StringComparer.Ordinal);
foreach (var (key, value) in pairs)
{
builder[key] = value;
}
return builder.ToImmutable();
}
}
///
/// Operator override for quiet hours or throttle configuration.
/// Allows an operator to temporarily bypass quiet hours or throttling.
///
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; }
///
/// Optional channel ID to scope the override.
///
public string? ChannelId { get; }
///
/// Optional rule ID to scope the override.
///
public string? RuleId { get; }
public string? Reason { get; }
public string? CreatedBy { get; }
public DateTimeOffset CreatedAt { get; }
///
/// Checks if the override is active at the specified time.
///
public bool IsActiveAt(DateTimeOffset timestamp)
=> timestamp < ExpiresAt;
}
///
/// Type of operator override.
///
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum NotifyOverrideType
{
///
/// Bypass quiet hours.
///
BypassQuietHours,
///
/// Bypass throttling.
///
BypassThrottle,
///
/// Bypass maintenance window.
///
BypassMaintenance,
///
/// Force suppress notifications.
///
ForceSuppression
}