Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions src/RecurringThings.MongoDB/Documents/RecurringThingDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace RecurringThings.MongoDB.Documents;
using global::MongoDB.Bson;
using global::MongoDB.Bson.Serialization.Attributes;
using RecurringThings.Domain;
using RecurringThings.Options;

/// <summary>
/// MongoDB document model for storing recurrences, occurrences, exceptions, and overrides in a single collection.
Expand Down Expand Up @@ -123,6 +124,22 @@ public sealed class RecurringThingDocument
[BsonIgnoreIfNull]
public string? RRule { get; set; }

/// <summary>
/// Gets or sets the strategy for out-of-bounds monthly days.
/// </summary>
/// <remarks>
/// <para>
/// Present only on recurrences with monthly patterns that have out-of-bounds days.
/// </para>
/// <para>
/// Values: "skip" or "clamp". Null when not applicable (non-monthly patterns,
/// monthly patterns with day &lt;= 28, or patterns where no months are affected).
/// </para>
/// </remarks>
[BsonElement("monthDayBehavior")]
[BsonIgnoreIfNull]
public string? MonthDayBehavior { get; set; }

/// <summary>
/// Gets or sets the parent recurrence identifier.
/// </summary>
Expand Down Expand Up @@ -232,7 +249,8 @@ public static Recurrence ToRecurrence(RecurringThingDocument document)
RecurrenceEndTime = document.RecurrenceEndTime!.Value,
RRule = document.RRule!,
TimeZone = document.TimeZone!,
Extensions = document.Extensions
Extensions = document.Extensions,
MonthDayBehavior = ParseMonthDayBehavior(document.MonthDayBehavior)
};
}

Expand Down Expand Up @@ -352,7 +370,8 @@ public static RecurringThingDocument FromRecurrence(Recurrence recurrence)
RecurrenceEndTime = recurrence.RecurrenceEndTime,
RRule = recurrence.RRule,
TimeZone = recurrence.TimeZone,
Extensions = recurrence.Extensions
Extensions = recurrence.Extensions,
MonthDayBehavior = SerializeMonthDayBehavior(recurrence.MonthDayBehavior)
// EndTime is NOT set for recurrences
};
}
Expand Down Expand Up @@ -427,4 +446,34 @@ public static RecurringThingDocument FromOccurrenceOverride(OccurrenceOverride @
Extensions = @override.Extensions
};
}

/// <summary>
/// Parses a string value to <see cref="MonthDayOutOfBoundsStrategy"/>.
/// </summary>
/// <param name="value">The string value ("skip" or "clamp").</param>
/// <returns>The parsed strategy, or null if the value is null or empty.</returns>
private static MonthDayOutOfBoundsStrategy? ParseMonthDayBehavior(string? value)
{
return value switch
{
"skip" => MonthDayOutOfBoundsStrategy.Skip,
"clamp" => MonthDayOutOfBoundsStrategy.Clamp,
_ => null
};
}

/// <summary>
/// Serializes a <see cref="MonthDayOutOfBoundsStrategy"/> to a string value.
/// </summary>
/// <param name="strategy">The strategy to serialize.</param>
/// <returns>The string value ("skip" or "clamp"), or null if the strategy is null.</returns>
private static string? SerializeMonthDayBehavior(MonthDayOutOfBoundsStrategy? strategy)
{
return strategy switch
{
MonthDayOutOfBoundsStrategy.Skip => "skip",
MonthDayOutOfBoundsStrategy.Clamp => "clamp",
_ => null
};
}
}
9 changes: 5 additions & 4 deletions src/RecurringThings.MongoDB/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@
MongoDB persistence provider for [RecurringThings](../../README.md).

## Installation

```bash
dotnet add package RecurringThings
dotnet add package RecurringThings.MongoDB
```

## Configuration

```csharp
using RecurringThings.Configuration;
using RecurringThings.MongoDB.Configuration;
Expand All @@ -26,11 +24,14 @@ services.AddRecurringThings(builder =>
}));
```

## Sharding Considerations

All queries issued by this provider filter by `Organization` first, making it an ideal shard key for MongoDB sharded clusters. Architects and DBAs should note this when designing their sharding strategy.

## Integration Tests

Set the environment variable before running integration tests:

```bash
export MONGODB_CONNECTION_STRING="mongodb://localhost:27017"
dotnet test --filter 'Category=Integration'
```
```
37 changes: 35 additions & 2 deletions src/RecurringThings.PostgreSQL/Data/Entities/EntityMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace RecurringThings.PostgreSQL.Data.Entities;

using System;
using RecurringThings.Domain;
using RecurringThings.Options;

/// <summary>
/// Maps between domain entities and EF Core entities.
Expand All @@ -28,7 +29,8 @@ public static RecurrenceEntity ToEntity(Recurrence recurrence)
RecurrenceEndTime = recurrence.RecurrenceEndTime,
RRule = recurrence.RRule,
TimeZone = recurrence.TimeZone,
Extensions = recurrence.Extensions
Extensions = recurrence.Extensions,
MonthDayBehavior = SerializeMonthDayBehavior(recurrence.MonthDayBehavior)
};
}

Expand All @@ -52,7 +54,8 @@ public static Recurrence ToDomain(RecurrenceEntity entity)
RecurrenceEndTime = DateTime.SpecifyKind(entity.RecurrenceEndTime, DateTimeKind.Utc),
RRule = entity.RRule,
TimeZone = entity.TimeZone,
Extensions = entity.Extensions
Extensions = entity.Extensions,
MonthDayBehavior = ParseMonthDayBehavior(entity.MonthDayBehavior)
};
}

Expand Down Expand Up @@ -197,4 +200,34 @@ public static OccurrenceOverride ToDomain(OccurrenceOverrideEntity entity)

return @override;
}

/// <summary>
/// Parses a string value to <see cref="MonthDayOutOfBoundsStrategy"/>.
/// </summary>
/// <param name="value">The string value ("skip" or "clamp").</param>
/// <returns>The parsed strategy, or null if the value is null or empty.</returns>
private static MonthDayOutOfBoundsStrategy? ParseMonthDayBehavior(string? value)
{
return value switch
{
"skip" => MonthDayOutOfBoundsStrategy.Skip,
"clamp" => MonthDayOutOfBoundsStrategy.Clamp,
_ => null
};
}

/// <summary>
/// Serializes a <see cref="MonthDayOutOfBoundsStrategy"/> to a string value.
/// </summary>
/// <param name="strategy">The strategy to serialize.</param>
/// <returns>The string value ("skip" or "clamp"), or null if the strategy is null.</returns>
private static string? SerializeMonthDayBehavior(MonthDayOutOfBoundsStrategy? strategy)
{
return strategy switch
{
MonthDayOutOfBoundsStrategy.Skip => "skip",
MonthDayOutOfBoundsStrategy.Clamp => "clamp",
_ => null
};
}
}
16 changes: 16 additions & 0 deletions src/RecurringThings.PostgreSQL/Data/Entities/RecurrenceEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,22 @@ internal sealed class RecurrenceEntity
[Column("extensions", TypeName = "jsonb")]
public Dictionary<string, string>? Extensions { get; set; }

/// <summary>
/// Gets or sets the strategy for out-of-bounds monthly days.
/// </summary>
/// <remarks>
/// <para>
/// Null when not applicable (non-monthly patterns, monthly patterns with day &lt;= 28,
/// or patterns where no months are affected).
/// </para>
/// <para>
/// Values: "skip" or "clamp".
/// </para>
/// </remarks>
[MaxLength(10)]
[Column("month_day_behavior")]
public string? MonthDayBehavior { get; set; }

/// <summary>
/// Gets or sets the navigation property for exceptions.
/// </summary>
Expand Down
Loading