diff --git a/dotnet/AipsCore/AipsCore.csproj b/dotnet/AipsCore/AipsCore.csproj
index 0124700..d062ba1 100644
--- a/dotnet/AipsCore/AipsCore.csproj
+++ b/dotnet/AipsCore/AipsCore.csproj
@@ -17,6 +17,7 @@
+
diff --git a/dotnet/AipsCore/Domain/Common/Validation/Rules/DateInFutureRule.cs b/dotnet/AipsCore/Domain/Common/Validation/Rules/DateInFutureRule.cs
new file mode 100644
index 0000000..cf48254
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Common/Validation/Rules/DateInFutureRule.cs
@@ -0,0 +1,22 @@
+using AipsCore.Domain.Abstract.Rule;
+
+namespace AipsCore.Domain.Common.Validation.Rules;
+
+public class DateInFutureRule : AbstractRule
+{
+ private readonly DateTime _date;
+ private readonly DateTime _now;
+
+ public DateInFutureRule(DateTime date)
+ {
+ _date = date;
+ _now = DateTime.Now;
+ }
+
+ protected override string ErrorCode => "date_in_future";
+ protected override string ErrorMessage => "Date must be in the future";
+ public override bool Validate()
+ {
+ return _date > _now;
+ }
+}
\ No newline at end of file
diff --git a/dotnet/AipsCore/Domain/Common/Validation/Rules/DateInPastRule.cs b/dotnet/AipsCore/Domain/Common/Validation/Rules/DateInPastRule.cs
new file mode 100644
index 0000000..2120aa6
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Common/Validation/Rules/DateInPastRule.cs
@@ -0,0 +1,22 @@
+using AipsCore.Domain.Abstract.Rule;
+
+namespace AipsCore.Domain.Common.Validation.Rules;
+
+public class DateInPastRule : AbstractRule
+{
+ private readonly DateTime _date;
+ private readonly DateTime _now;
+
+ public DateInPastRule(DateTime date)
+ {
+ _date = date;
+ _now = DateTime.Now;
+ }
+
+ protected override string ErrorCode => "date_in_past";
+ protected override string ErrorMessage => "Date must be in the past";
+ public override bool Validate()
+ {
+ return _date < _now;
+ }
+}
\ No newline at end of file
diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/External/IWhiteboardMembershipRepository.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/External/IWhiteboardMembershipRepository.cs
new file mode 100644
index 0000000..2d70762
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/External/IWhiteboardMembershipRepository.cs
@@ -0,0 +1,9 @@
+using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
+
+namespace AipsCore.Domain.Models.WhiteboardMembership.External;
+
+public interface IWhiteboardMembershipRepository
+{
+ Task Get(WhiteboardMembershipId whiteboardMembershipId, CancellationToken cancellationToken = default);
+ Task Save(WhiteboardMembership whiteboardMembership, CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipCanJoin.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipCanJoin.cs
new file mode 100644
index 0000000..48591ad
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipCanJoin.cs
@@ -0,0 +1,20 @@
+using AipsCore.Domain.Abstract.Rule;
+using AipsCore.Domain.Abstract.ValueObject;
+
+namespace AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
+
+public record WhiteboardMembershipCanJoin : AbstractValueObject
+{
+ public bool CanJoinValue { get; init; }
+
+ public WhiteboardMembershipCanJoin(bool CanJoinValue)
+ {
+ this.CanJoinValue = CanJoinValue;
+ Validate();
+ }
+
+ protected override ICollection GetValidationRules()
+ {
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipEditingEnabled.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipEditingEnabled.cs
new file mode 100644
index 0000000..da674c1
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipEditingEnabled.cs
@@ -0,0 +1,20 @@
+using AipsCore.Domain.Abstract.Rule;
+using AipsCore.Domain.Abstract.ValueObject;
+
+namespace AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
+
+public record WhiteboardMembershipEditingEnabled : AbstractValueObject
+{
+ public bool EditingEnabledValue { get; init; }
+
+ public WhiteboardMembershipEditingEnabled(bool EditingEnabledValue)
+ {
+ this.EditingEnabledValue = EditingEnabledValue;
+ Validate();
+ }
+
+ protected override ICollection GetValidationRules()
+ {
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipId.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipId.cs
new file mode 100644
index 0000000..4f786e0
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipId.cs
@@ -0,0 +1,8 @@
+using AipsCore.Domain.Common.ValueObjects;
+
+namespace AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
+
+public record WhiteboardMembershipId(string IdValue) : DomainId(IdValue)
+{
+ public static WhiteboardMembershipId Any() => new(Guid.NewGuid().ToString());
+}
\ No newline at end of file
diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipIsBanned.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipIsBanned.cs
new file mode 100644
index 0000000..363e9ab
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipIsBanned.cs
@@ -0,0 +1,20 @@
+using AipsCore.Domain.Abstract.Rule;
+using AipsCore.Domain.Abstract.ValueObject;
+
+namespace AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
+
+public record WhiteboardMembershipIsBanned : AbstractValueObject
+{
+ public bool IsBannedValue { get; init; }
+
+ public WhiteboardMembershipIsBanned(bool IsBannedValue)
+ {
+ this.IsBannedValue = IsBannedValue;
+ Validate();
+ }
+
+ protected override ICollection GetValidationRules()
+ {
+ return [];
+ }
+}
\ No newline at end of file
diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipLastInteractedAt.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipLastInteractedAt.cs
new file mode 100644
index 0000000..b9b8952
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/ValueObjects/WhiteboardMembershipLastInteractedAt.cs
@@ -0,0 +1,23 @@
+using AipsCore.Domain.Abstract.Rule;
+using AipsCore.Domain.Abstract.ValueObject;
+using AipsCore.Domain.Common.Validation.Rules;
+
+namespace AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
+
+public record WhiteboardMembershipLastInteractedAt : AbstractValueObject
+{
+ public DateTime LastInteractedAtValue { get; init; }
+
+ public WhiteboardMembershipLastInteractedAt(DateTime LastInteractedAtValue)
+ {
+ this.LastInteractedAtValue = LastInteractedAtValue;
+ Validate();
+ }
+
+ protected override ICollection GetValidationRules()
+ {
+ return [
+ new DateInPastRule(LastInteractedAtValue)
+ ];
+ }
+}
\ No newline at end of file
diff --git a/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.cs b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.cs
new file mode 100644
index 0000000..6cd006f
--- /dev/null
+++ b/dotnet/AipsCore/Domain/Models/WhiteboardMembership/WhiteboardMembership.cs
@@ -0,0 +1,105 @@
+using AipsCore.Domain.Models.User.ValueObjects;
+using AipsCore.Domain.Models.Whiteboard.ValueObjects;
+using AipsCore.Domain.Models.WhiteboardMembership.ValueObjects;
+
+namespace AipsCore.Domain.Models.WhiteboardMembership;
+
+public class WhiteboardMembership
+{
+ public WhiteboardMembershipId Id { get; private set; }
+ public WhiteboardId WhiteboardId { get; private set; }
+ public UserId UserId { get; private set; }
+ public WhiteboardMembershipIsBanned IsBanned { get; private set; }
+ public WhiteboardMembershipEditingEnabled EditingEnabled { get; private set; }
+ public WhiteboardMembershipCanJoin CanJoin { get; private set; }
+ public WhiteboardMembershipLastInteractedAt LastInteractedAt { get; private set; }
+
+ public WhiteboardMembership(
+ WhiteboardMembershipId id,
+ Whiteboard.Whiteboard owner,
+ User.User user,
+ WhiteboardMembershipIsBanned isBanned,
+ WhiteboardMembershipEditingEnabled editingEnabled,
+ WhiteboardMembershipCanJoin canJoin,
+ WhiteboardMembershipLastInteractedAt lastInteractedAt)
+ {
+ Id = id;
+ WhiteboardId = owner.Id;
+ UserId = user.Id;
+ IsBanned = isBanned;
+ EditingEnabled = editingEnabled;
+ CanJoin = canJoin;
+ LastInteractedAt = lastInteractedAt;
+ }
+
+ public WhiteboardMembership(
+ WhiteboardMembershipId id,
+ WhiteboardId ownerId,
+ UserId userId,
+ WhiteboardMembershipIsBanned isBanned,
+ WhiteboardMembershipEditingEnabled editingEnabled,
+ WhiteboardMembershipCanJoin canJoin,
+ WhiteboardMembershipLastInteractedAt lastInteractedAt)
+ {
+ Id = id;
+ WhiteboardId = ownerId;
+ UserId = userId;
+ IsBanned = isBanned;
+ EditingEnabled = editingEnabled;
+ CanJoin = canJoin;
+ LastInteractedAt = lastInteractedAt;
+ }
+
+ public static WhiteboardMembership Create(
+ string id,
+ string ownerId,
+ string userId,
+ bool isBanned,
+ bool editingEnabled,
+ bool canJoin,
+ DateTime lastInteractedAt)
+ {
+ var whiteboardMembershipId = new WhiteboardMembershipId(id);
+ var whiteboardId = new WhiteboardId(ownerId);
+ var userIdVo = new UserId(userId);
+ var isBannedVo = new WhiteboardMembershipIsBanned(isBanned);
+ var editingEnabledVo = new WhiteboardMembershipEditingEnabled(editingEnabled);
+ var canJoinVo = new WhiteboardMembershipCanJoin(canJoin);
+ var lastInteractedAtVo = new WhiteboardMembershipLastInteractedAt(lastInteractedAt);
+
+ return new WhiteboardMembership(
+ whiteboardMembershipId,
+ whiteboardId,
+ userIdVo,
+ isBannedVo,
+ editingEnabledVo,
+ canJoinVo,
+ lastInteractedAtVo);
+ }
+
+ public static WhiteboardMembership Create(
+ string ownerId,
+ string userId,
+ bool isBanned,
+ bool editingEnabled,
+ bool canJoin,
+ DateTime lastInteractedAt)
+ {
+ var whiteboardMembershipId = WhiteboardMembershipId.Any();
+ var whiteboardId = new WhiteboardId(ownerId);
+ var userIdVo = new UserId(userId);
+ var isBannedVo = new WhiteboardMembershipIsBanned(isBanned);
+ var editingEnabledVo = new WhiteboardMembershipEditingEnabled(editingEnabled);
+ var canJoinVo = new WhiteboardMembershipCanJoin(canJoin);
+ var lastInteractedAtVo = new WhiteboardMembershipLastInteractedAt(lastInteractedAt);
+
+ return new WhiteboardMembership(
+ whiteboardMembershipId,
+ whiteboardId,
+ userIdVo,
+ isBannedVo,
+ editingEnabledVo,
+ canJoinVo,
+ lastInteractedAtVo);
+ }
+}
\ No newline at end of file