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
118 changes: 118 additions & 0 deletions src/LeadProcessor.Domain/Entities/Lead.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
namespace LeadProcessor.Domain.Entities;

/// <summary>
/// Represents a lead entity captured from various sources.
/// </summary>
public record Lead
{
/// <summary>
/// Gets the unique identifier for the lead.
/// </summary>
public int Id { get; init; }

/// <summary>
/// Gets the tenant identifier for multi-tenancy support.
/// </summary>
public required string TenantId { get; init; }

/// <summary>
/// Gets the correlation identifier for idempotency and message tracking.
/// </summary>
public required string CorrelationId { get; init; }

/// <summary>
/// Gets the email address of the lead.
/// </summary>
public required string Email { get; init; }

/// <summary>
/// Gets the first name of the lead.
/// </summary>
public string? FirstName { get; init; }

/// <summary>
/// Gets the last name of the lead.
/// </summary>
public string? LastName { get; init; }

/// <summary>
/// Gets the phone number of the lead.
/// </summary>
public string? Phone { get; init; }

/// <summary>
/// Gets the company name of the lead.
/// </summary>
public string? Company { get; init; }

/// <summary>
/// Gets the source from which the lead originated (e.g., website, mobile app, referral).
/// </summary>
public required string Source { get; init; }

/// <summary>
/// Gets the metadata as a JSON string containing additional information about the lead.
/// </summary>
public string? Metadata { get; init; }

/// <summary>
/// Gets the UTC date and time when the lead was created.
/// </summary>
public DateTimeOffset CreatedAt { get; init; }

/// <summary>
/// Gets the UTC date and time when the lead was last updated.
/// </summary>
public DateTimeOffset UpdatedAt { get; init; }

/// <summary>
/// Validates that the email format is correct.
/// </summary>
/// <returns>
/// True if the email is in a valid format according to RFC 5322, otherwise false.
/// Returns false if email is null, empty, or whitespace.
/// </returns>
public bool HasValidEmail()
{
if (string.IsNullOrWhiteSpace(Email))
return false;

try
{
var addr = new System.Net.Mail.MailAddress(Email);
return addr.Address == Email;
}
catch (FormatException)
{
return false;
}
}

/// <summary>
/// Validates that the correlation ID is in GUID format.
/// </summary>
/// <returns>True if the correlation ID can be parsed as a GUID, otherwise false.</returns>
public bool IsCorrelationIdGuid()
{
return Guid.TryParse(CorrelationId, out _);
}

/// <summary>
/// Gets the full name of the lead by combining first and last names.
/// </summary>
/// <returns>The full name, or null if both names are empty.</returns>
public string? GetFullName()
{
var hasFirst = !string.IsNullOrWhiteSpace(FirstName);
var hasLast = !string.IsNullOrWhiteSpace(LastName);

return (hasFirst, hasLast) switch
{
(true, true) => $"{FirstName} {LastName}",
(true, false) => FirstName,
(false, true) => LastName,
_ => null
};
}
}

35 changes: 35 additions & 0 deletions src/LeadProcessor.Domain/Repositories/ILeadRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using LeadProcessor.Domain.Entities;

namespace LeadProcessor.Domain.Repositories;

/// <summary>
/// Repository interface for managing lead persistence operations.
/// </summary>
public interface ILeadRepository
{
/// <summary>
/// Saves a lead to the data store asynchronously.
/// </summary>
/// <param name="lead">The lead entity to save.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The saved lead entity with updated fields (e.g., Id, timestamps).</returns>
Task<Lead> SaveLeadAsync(Lead lead, CancellationToken cancellationToken = default);

/// <summary>
/// Checks if a lead with the specified correlation ID already exists.
/// This method supports idempotency by allowing duplicate message detection.
/// </summary>
/// <param name="correlationId">The correlation ID to check for.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>True if a lead with the correlation ID exists, otherwise false.</returns>
Task<bool> ExistsByCorrelationIdAsync(string correlationId, CancellationToken cancellationToken = default);

/// <summary>
/// Retrieves a lead by its correlation ID asynchronously.
/// </summary>
/// <param name="correlationId">The correlation ID to search for.</param>
/// <param name="cancellationToken">Cancellation token to cancel the operation.</param>
/// <returns>The lead entity if found, otherwise null.</returns>
Task<Lead?> GetByCorrelationIdAsync(string correlationId, CancellationToken cancellationToken = default);
}