mirror of
https://github.com/farcasclaudiu/PracticeCalendar.git
synced 2026-06-22 07:01:16 +03:00
Add project files.
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
namespace PracticeCalendar.Domain.Common
|
||||
{
|
||||
public class DomainEventBase
|
||||
{
|
||||
public DateTime EventDate { get; protected set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace PracticeCalendar.Domain.Common
|
||||
{
|
||||
public abstract class DomainException : Exception
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PracticeCalendar.Domain.Common
|
||||
{
|
||||
public abstract class EntityBase
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
||||
private List<DomainEventBase> _domainEvents = new();
|
||||
[NotMapped]
|
||||
public IEnumerable<DomainEventBase> DomainEvents => _domainEvents.AsReadOnly();
|
||||
|
||||
protected void RegisterDomainEvent(DomainEventBase domainEvent) => _domainEvents.Add(domainEvent);
|
||||
internal void ClearDomainEvents() => _domainEvents.Clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace PracticeCalendar.Domain.Common
|
||||
{
|
||||
// source: https://github.com/jhewlett/ValueObject
|
||||
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
|
||||
public class IgnoreMemberAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace PracticeCalendar.Domain.Common.Interfaces
|
||||
{
|
||||
// Apply this marker interface only to aggregate root entities
|
||||
// Repositories will only work with aggregate roots, not their children
|
||||
public interface IAggregateRoot { }
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using Ardalis.Specification;
|
||||
|
||||
namespace PracticeCalendar.Domain.Common.Interfaces
|
||||
{
|
||||
public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace PracticeCalendar.Domain.Common
|
||||
{
|
||||
// source: https://github.com/jhewlett/ValueObject
|
||||
public abstract class ValueObject : IEquatable<ValueObject>
|
||||
{
|
||||
private List<PropertyInfo>? properties;
|
||||
private List<FieldInfo>? fields;
|
||||
|
||||
public static bool operator ==(ValueObject? obj1, ValueObject? obj2)
|
||||
{
|
||||
if (object.Equals(obj1, null))
|
||||
{
|
||||
if (object.Equals(obj2, null))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return obj1.Equals(obj2);
|
||||
}
|
||||
|
||||
public static bool operator !=(ValueObject? obj1, ValueObject? obj2)
|
||||
{
|
||||
return !(obj1 == obj2);
|
||||
}
|
||||
|
||||
public bool Equals(ValueObject? obj)
|
||||
{
|
||||
return Equals(obj as object);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj == null || GetType() != obj.GetType()) return false;
|
||||
|
||||
return GetProperties().All(p => PropertiesAreEqual(obj, p))
|
||||
&& GetFields().All(f => FieldsAreEqual(obj, f));
|
||||
}
|
||||
|
||||
private bool PropertiesAreEqual(object obj, PropertyInfo p)
|
||||
{
|
||||
return object.Equals(p.GetValue(this, null), p.GetValue(obj, null));
|
||||
}
|
||||
|
||||
private bool FieldsAreEqual(object obj, FieldInfo f)
|
||||
{
|
||||
return object.Equals(f.GetValue(this), f.GetValue(obj));
|
||||
}
|
||||
|
||||
private IEnumerable<PropertyInfo> GetProperties()
|
||||
{
|
||||
if (this.properties == null)
|
||||
{
|
||||
this.properties = GetType()
|
||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => p.GetCustomAttribute(typeof(IgnoreMemberAttribute)) == null)
|
||||
.ToList();
|
||||
|
||||
// Not available in Core
|
||||
// !Attribute.IsDefined(p, typeof(IgnoreMemberAttribute))).ToList();
|
||||
}
|
||||
|
||||
return this.properties;
|
||||
}
|
||||
|
||||
private IEnumerable<FieldInfo> GetFields()
|
||||
{
|
||||
if (this.fields == null)
|
||||
{
|
||||
this.fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => p.GetCustomAttribute(typeof(IgnoreMemberAttribute)) == null)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return this.fields;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked //allow overflow
|
||||
{
|
||||
int hash = 17;
|
||||
foreach (var prop in GetProperties())
|
||||
{
|
||||
var value = prop.GetValue(this, null);
|
||||
hash = HashValue(hash, value);
|
||||
}
|
||||
|
||||
foreach (var field in GetFields())
|
||||
{
|
||||
var value = field.GetValue(this);
|
||||
hash = HashValue(hash, value);
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
private int HashValue(int seed, object? value)
|
||||
{
|
||||
var currentHash = value != null
|
||||
? value.GetHashCode()
|
||||
: 0;
|
||||
|
||||
return seed * 23 + currentHash;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using Ardalis.GuardClauses;
|
||||
using PracticeCalendar.Domain.Common;
|
||||
using System.Diagnostics.Contracts;
|
||||
|
||||
namespace PracticeCalendar.Domain.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Attendee to an event
|
||||
/// </summary>
|
||||
public class Attendee: EntityBase
|
||||
{
|
||||
public Attendee(string name, string emailAddress)
|
||||
{
|
||||
Guard.Against.NullOrEmpty(name);
|
||||
Guard.Against.NullOrEmpty(emailAddress);
|
||||
Name = name;
|
||||
EmailAddress = emailAddress;
|
||||
}
|
||||
|
||||
public int PracticeEventId { get; private set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string EmailAddress { get; set; } = string.Empty;
|
||||
public bool IsAttending { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Set if the Attendee is attending
|
||||
/// </summary>
|
||||
/// <param name="isAttending"></param>
|
||||
public void SetIsAttending (bool isAttending)
|
||||
{
|
||||
this.IsAttending = isAttending;
|
||||
//TODO - raise event
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign Attendee to the practice event
|
||||
/// </summary>
|
||||
/// <param name="practiceEventId"></param>
|
||||
public void AssignToEvent(int practiceEventId)
|
||||
{
|
||||
this.PracticeEventId = practiceEventId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Ardalis.GuardClauses;
|
||||
using PracticeCalendar.Domain.Common;
|
||||
using PracticeCalendar.Domain.Common.Interfaces;
|
||||
using PracticeCalendar.Domain.Events;
|
||||
using PracticeCalendar.Domain.Exceptions;
|
||||
|
||||
namespace PracticeCalendar.Domain.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Practice event aggregate
|
||||
/// </summary>
|
||||
public class PracticeEvent : EntityBase, IAggregateRoot
|
||||
{
|
||||
public PracticeEvent(string title, string description)
|
||||
{
|
||||
Guard.Against.NullOrEmpty(title, nameof(title));
|
||||
Guard.Against.NullOrEmpty(description, nameof(description));
|
||||
this.Title = title;
|
||||
this.Description = description;
|
||||
}
|
||||
|
||||
public string Title { get; private set; } = string.Empty;
|
||||
public string Description{ get; private set; } = string.Empty;
|
||||
|
||||
private List<Attendee> attendees = new List<Attendee>();
|
||||
public IEnumerable<Attendee> Attendees => attendees.AsReadOnly();
|
||||
|
||||
public DateTime StartTime { get; private set; }
|
||||
public DateTime EndTime { get; private set; }
|
||||
|
||||
public void AddAttendee(Attendee attendee)
|
||||
{
|
||||
Guard.Against.Null(attendee, nameof(attendee));
|
||||
attendee.AssignToEvent(this.Id);
|
||||
attendees.Add(attendee);
|
||||
|
||||
var attendeeAddedEvent = new AttendeeAddedEvent(this, attendee);
|
||||
base.RegisterDomainEvent(attendeeAddedEvent);
|
||||
}
|
||||
|
||||
public void UpdateTitleAndDescription(string title, string description)
|
||||
{
|
||||
this.Title = title;
|
||||
this.Description = description;
|
||||
}
|
||||
|
||||
public void AttendeeAcceptEvent(int attendeeId)
|
||||
{
|
||||
var attendee = this.Attendees.FirstOrDefault(x => x.Id == attendeeId);
|
||||
if (attendee == null)
|
||||
throw new InvalidAttendeeException(attendeeId);
|
||||
attendee.SetIsAttending(true);
|
||||
}
|
||||
|
||||
public void AttendeeDeclineEvent(int attendeeId)
|
||||
{
|
||||
var attendee = this.Attendees.FirstOrDefault(x => x.Id == attendeeId);
|
||||
if (attendee == null)
|
||||
throw new InvalidAttendeeException(attendeeId);
|
||||
attendee.SetIsAttending(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Ardalis.Specification;
|
||||
|
||||
namespace PracticeCalendar.Domain.Entities.Specifications
|
||||
{
|
||||
public class PracticeEventByIdWithAttendees : Specification<PracticeEvent>
|
||||
{
|
||||
public PracticeEventByIdWithAttendees(int eventId)
|
||||
{
|
||||
Query.Where(x=>x.Id == eventId)
|
||||
.Include(x => x.Attendees);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Ardalis.Specification;
|
||||
|
||||
namespace PracticeCalendar.Domain.Entities.Specifications
|
||||
{
|
||||
public class PracticeEventsWithAttendees : Specification<PracticeEvent>
|
||||
{
|
||||
public PracticeEventsWithAttendees()
|
||||
{
|
||||
Query.Include(x => x.Attendees);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using PracticeCalendar.Domain.Common;
|
||||
using PracticeCalendar.Domain.Entities;
|
||||
|
||||
namespace PracticeCalendar.Domain.Events
|
||||
{
|
||||
public sealed class AttendeeAddedEvent : DomainEventBase
|
||||
{
|
||||
public AttendeeAddedEvent(PracticeEvent eventAggregate, Attendee addedAtendee)
|
||||
{
|
||||
EventAggregate = eventAggregate;
|
||||
AddedAtendee = addedAtendee;
|
||||
}
|
||||
|
||||
public PracticeEvent EventAggregate { get; }
|
||||
public Attendee AddedAtendee { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using PracticeCalendar.Domain.Common;
|
||||
|
||||
namespace PracticeCalendar.Domain.Exceptions
|
||||
{
|
||||
public class InvalidAttendeeException : DomainException
|
||||
{
|
||||
private int attendeeId;
|
||||
|
||||
public InvalidAttendeeException(int attendeeId)
|
||||
{
|
||||
this.attendeeId = attendeeId;
|
||||
}
|
||||
|
||||
public override string Message => $"Invalid Attendee with id: {attendeeId}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace PracticeCalendar.Domain.Interfaces
|
||||
{
|
||||
public interface IEmailSender
|
||||
{
|
||||
Task SendEmailAsync(string to, string from, string subject, string body);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="ValueObjects\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ardalis.GuardClauses" Version="4.0.1" />
|
||||
<PackageReference Include="Ardalis.Specification" Version="6.1.0" />
|
||||
<PackageReference Include="MediatR" Version="11.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user