Compare commits

...
2 Commits
Author SHA1 Message Date
Khwezi Mngoma 4b822c63b2 Added support for notifications 2026-05-05 14:36:46 +02:00
Khwezi Mngoma f5c9557f59 Generalised lead entries 2026-05-05 14:06:38 +02:00
28 changed files with 687 additions and 27 deletions
@@ -10,7 +10,8 @@ public class LeadConfiguration : IEntityTypeConfiguration<Lead>
builder.Property(f => f.CreatedAt).ValueGeneratedOnAdd();
builder.Property(f => f.UpdatedAt).IsRequired(false).ValueGeneratedOnUpdate();
builder.Property(f => f.CustomerId).IsRequired(false);
builder.Property(f => f.GoogleClickId);
builder.Property(f => f.Source);
builder.Property(f => f.ClickId);
builder.Property(f => f.WebClickId);
builder.Property(f => f.AppClickId);
builder.Property(f => f.CampaignId);
@@ -0,0 +1,21 @@
namespace LiteCharms.Entities.Configuration;
public class NotificationConfiguration : IEntityTypeConfiguration<Notification>
{
public void Configure(EntityTypeBuilder<Notification> builder)
{
builder.ToTable(nameof(Notification));
builder.HasKey(f => f.Id);
builder.Property(f => f.CreatedAt).IsRequired().ValueGeneratedOnAdd();
builder.Property(f => f.Direction).IsRequired();
builder.Property(f => f.Author).IsRequired();
builder.Property(f => f.Title).IsRequired();
builder.Property(f => f.Description).IsRequired();
builder.Property(f => f.Platform).IsRequired();
builder.Property(f => f.PlatformAddress).IsRequired();
builder.Property(f => f.CorrelationId).IsRequired();
builder.Property(f => f.CorrelationIdType).IsRequired();
builder.Property(f => f.IsInternal).IsRequired();
}
}
+6
View File
@@ -0,0 +1,6 @@
using LiteCharms.Entities.Configuration;
namespace LiteCharms.Entities;
[EntityTypeConfiguration<NotificationConfiguration, Notification>]
public class Notification : Models.Notification;
+18 -1
View File
@@ -4,6 +4,22 @@ namespace LiteCharms.Extensions;
public static class EntityModeMappers
{
public static Notification ToModel(this Entities.Notification entity) =>
new()
{
Id = entity.Id,
CreatedAt = entity.CreatedAt,
Description = entity.Description,
Direction = entity.Direction,
CorrelationId = entity.CorrelationId,
CorrelationIdType = entity.CorrelationIdType,
IsInternal = entity.IsInternal,
Author = entity.Author,
Platform = entity.Platform,
PlatformAddress = entity.PlatformAddress,
Title = entity.Title
};
public static Customer ToModel(this Entities.Customer entity) =>
new()
{
@@ -43,7 +59,8 @@ public static class EntityModeMappers
ClickLocation = entity.ClickLocation,
CustomerId = entity.CustomerId,
FeedItemId = entity.FeedItemId,
GoogleClickId = entity.GoogleClickId,
Source = entity.Source,
ClickId = entity.ClickId,
TargetId = entity.TargetId,
WebClickId = entity.WebClickId,
Status = entity.Status
@@ -8,17 +8,23 @@ public class GetCustomersQuery : IRequest<Result<Customer[]>>
public DateOnly To { get; set; }
private GetCustomersQuery(DateOnly from, DateOnly to)
public int MaxRecords { get; set; }
private GetCustomersQuery(DateOnly from, DateOnly to, int maxRecords = 1000)
{
From = from;
To = to;
MaxRecords = maxRecords;
}
public static GetCustomersQuery Create(DateOnly from, DateOnly to)
public static GetCustomersQuery Create(DateOnly from, DateOnly to, int maxRecords = 1000)
{
if (from > to)
throw new ArgumentException("From date cannot be greater than To date.");
return new(from, to);
if(maxRecords <= 0)
throw new ArgumentException("MaxRecords must be a positive integer.");
return new(from, to, maxRecords);
}
}
@@ -18,6 +18,7 @@ public class GetCustomersQueryHandler(IDbContextFactory<LeadGeneratorDbContext>
var customers = await context.Customers.AsNoTracking()
.OrderByDescending(o => o.CreatedAt)
.Where(c => c.CreatedAt >= fromDate && c.CreatedAt <= toDate)
.Take(request.MaxRecords)
.ToArrayAsync(cancellationToken);
return customers?.Length > 0
@@ -4,7 +4,9 @@ public class CreateLeadCommand : IRequest<Result<Guid>>
{
public Guid? CustomerId { get; set; }
public string? GoogleClickId { get; set; }
public string? Source { get; set; }
public string? ClickId { get; set; }
public string? WebClickId { get; set; }
@@ -24,10 +26,11 @@ public class CreateLeadCommand : IRequest<Result<Guid>>
public string? AttribusionHash { get; set; }
private CreateLeadCommand(Guid? customerId, string googleClickId, string webClickId, string appClickId, long? campaignId, long? adGroupId, long? adName, long? targetId, long? feedItemId, string? clickLocation, string? attribusionHash)
private CreateLeadCommand(Guid? customerId, string source, string clickId, string webClickId, string appClickId, long? campaignId, long? adGroupId, long? adName, long? targetId, long? feedItemId, string? clickLocation, string? attribusionHash)
{
CustomerId = customerId;
GoogleClickId = googleClickId;
Source = source;
ClickId = clickId;
WebClickId = webClickId;
AppClickId = appClickId;
CampaignId = campaignId;
@@ -39,11 +42,14 @@ public class CreateLeadCommand : IRequest<Result<Guid>>
AttribusionHash = attribusionHash;
}
public static CreateLeadCommand Create(Guid? customerId, string googleClickId, string webClickId, string appClickId, long? campaignId, long? adGroupId, long? adName, long? targetId, long? feedItemId, string? clickLocation, string? attribusionHash)
public static CreateLeadCommand Create(Guid? customerId, string source, string clickId, string webClickId, string appClickId, long? campaignId, long? adGroupId, long? adName, long? targetId, long? feedItemId, string? clickLocation, string? attribusionHash)
{
if (string.IsNullOrWhiteSpace(googleClickId) || string.IsNullOrWhiteSpace(appClickId) || string.IsNullOrWhiteSpace(webClickId))
throw new ArgumentException("Google ClickId, App ClickId and Web ClickId are required to create a lead.");
if(string.IsNullOrWhiteSpace(source))
throw new ArgumentNullException("Lead source is required to create a lead.", nameof(source));
return new(customerId, googleClickId, webClickId, appClickId, campaignId, adGroupId, adName, targetId, feedItemId, clickLocation, attribusionHash);
if (string.IsNullOrWhiteSpace(clickId) || string.IsNullOrWhiteSpace(appClickId) || string.IsNullOrWhiteSpace(webClickId))
throw new ArgumentException("ClickId, App ClickId and Web ClickId are required to create a lead.");
return new(customerId, source, clickId, webClickId, appClickId, campaignId, adGroupId, adName, targetId, feedItemId, clickLocation, attribusionHash);
}
}
@@ -9,11 +9,11 @@ public class CreateLeadCommandHandler(IDbContextFactory<LeadGeneratorDbContext>
{
try
{
var hashCommand = ComputeHashCommand.Create($"{request.GoogleClickId}{request.AppClickId}{request.WebClickId}");
var hashCommand = ComputeHashCommand.Create($"{request.ClickId}{request.AppClickId}{request.WebClickId}");
var hashResult = await mediator.Send(hashCommand, cancellationToken);
if(hashResult.IsFailed)
return Result.Fail<Guid>(new Error($"Failed to compute hash for lead -> Google ClickId: {request.GoogleClickId}, App ClickId: {request.AppClickId}, Web ClickId: {request.WebClickId}")
return Result.Fail<Guid>(new Error($"Failed to compute hash for lead -> Google ClickId: {request.ClickId}, App ClickId: {request.AppClickId}, Web ClickId: {request.WebClickId}")
.CausedBy(hashResult.Errors));
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
@@ -22,7 +22,8 @@ public class CreateLeadCommandHandler(IDbContextFactory<LeadGeneratorDbContext>
{
WebClickId = request.WebClickId,
AppClickId = request.AppClickId,
GoogleClickId = request.GoogleClickId,
Source = request.Source,
ClickId = request.ClickId,
AdGroupId = request.AdGroupId,
AdName = request.AdName,
CampaignId = request.CampaignId,
@@ -36,7 +37,7 @@ public class CreateLeadCommandHandler(IDbContextFactory<LeadGeneratorDbContext>
return await context.SaveChangesAsync(cancellationToken) > 0
? Result.Ok(newLead.Entity.Id)
: Result.Fail<Guid>(new Error($"Failed to create lead -> Google ClickId: {request.GoogleClickId}, App ClickId: {request.AppClickId}, Web ClickId: {request.WebClickId}"));
: Result.Fail<Guid>(new Error($"Failed to create lead -> Google ClickId: {request.ClickId}, App ClickId: {request.AppClickId}, Web ClickId: {request.WebClickId}"));
}
catch (Exception ex)
{
@@ -8,17 +8,23 @@ public class GetLeadsQuery : IRequest<Result<Lead[]>>
public DateOnly To { get; set; }
private GetLeadsQuery(DateOnly from, DateOnly to)
public int MaxRecords { get; set; }
private GetLeadsQuery(DateOnly from, DateOnly to, int maxRecords = 1000)
{
From = from;
To = to;
MaxRecords = maxRecords;
}
public static GetLeadsQuery Create(DateOnly from, DateOnly to)
public static GetLeadsQuery Create(DateOnly from, DateOnly to, int maxRecords = 1000)
{
if (from > to)
throw new ArgumentException("From date cannot be greater than To date.");
return new(from, to);
if(maxRecords <= 0)
throw new ArgumentException("MaxRecords must be a positive integer.");
return new(from, to, maxRecords);
}
}
@@ -18,6 +18,7 @@ public class GetLeadsQueryHandler(IDbContextFactory<LeadGeneratorDbContext> cont
var leads = await context.Leads.AsNoTracking()
.OrderByDescending(o => o.CreatedAt)
.Where(l => l.CreatedAt.Date >= fromDate && l.CreatedAt.Date <= toDate)
.Take(request.MaxRecords)
.ToArrayAsync(cancellationToken);
return leads?.Length > 0
@@ -0,0 +1,30 @@
using LiteCharms.Models;
namespace LiteCharms.Features.Notifications.Queries;
public class GetNotificationsQuery : IRequest<Result<Notification[]>>
{
public DateOnly From { get; set; }
public DateOnly To { get; set; }
public int MaxRecords { get; set; }
private GetNotificationsQuery(DateOnly from, DateOnly to, int maxRecords = 1000)
{
From = from;
To = to;
MaxRecords = maxRecords;
}
public static GetNotificationsQuery Create(DateOnly from, DateOnly to, int maxRecords = 1000)
{
if (from > to)
throw new ArgumentException("From date cannot be greater than To date.");
if(maxRecords <= 0)
throw new ArgumentException("MaxRecords must be a positive integer.", nameof(maxRecords));
return new(from, to, maxRecords);
}
}
@@ -0,0 +1,33 @@
using LiteCharms.Extensions;
using LiteCharms.Infrastructure.Database;
using LiteCharms.Models;
namespace LiteCharms.Features.Notifications.Queries.Handlers;
public class GetNotificationsQueryHandler(IDbContextFactory<LeadGeneratorDbContext> contextFactory) : IRequestHandler<GetNotificationsQuery, Result<Notification[]>>
{
public async ValueTask<Result<Notification[]>> Handle(GetNotificationsQuery request, CancellationToken cancellationToken)
{
try
{
var fromDate = request.From.ToDateTime(TimeOnly.MinValue);
var toDate = request.To.ToDateTime(TimeOnly.MaxValue);
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var notifications = await context.Notifications
.Where(n => n.CreatedAt >= fromDate && n.CreatedAt <= toDate)
.OrderByDescending(n => n.CreatedAt)
.Take(request.MaxRecords)
.ToArrayAsync(cancellationToken);
return notifications?.Length > 0
? Result.Ok(notifications.Select(n => n.ToModel()).ToArray())
: Result.Fail(new Error($"No notifications found for the specified date range {request.From} to {request.To}."));
}
catch (Exception ex)
{
return Result.Fail(new Error(ex.Message).CausedBy(ex));
}
}
}
@@ -8,17 +8,23 @@ public class GetOrdersQuery : IRequest<Result<Order[]>>
public DateOnly To { get; set; }
private GetOrdersQuery(DateOnly from, DateOnly to)
public int MaxRecords { get; set; }
private GetOrdersQuery(DateOnly from, DateOnly to, int maxRecords = 1000)
{
From = from;
To = to;
MaxRecords = maxRecords;
}
public static GetOrdersQuery Create(DateOnly from, DateOnly to)
public static GetOrdersQuery Create(DateOnly from, DateOnly to, int maxRecords = 1000)
{
if (from > to)
throw new ArgumentException("From date cannot be greater than To date.");
return new(from, to);
if(maxRecords <= 0)
throw new ArgumentException("MaxRecords must be a positive integer.");
return new(from, to, maxRecords);
}
}
@@ -18,6 +18,7 @@ public class GetOrdersQueryHandler(IDbContextFactory<LeadGeneratorDbContext> con
var orders = await context.Orders
.OrderByDescending(o => o.CreatedAt)
.Where(o => o.CreatedAt >= fromDate && o.CreatedAt <= toDate)
.Take(request.MaxRecords)
.ToArrayAsync(cancellationToken);
return orders?.Length > 0
@@ -0,0 +1,18 @@
using LiteCharms.Models;
namespace LiteCharms.Features.Products.Queries;
public class GetProductPricesQuery : IRequest<Result<ProductPrice[]>>
{
public int MaxRecords { get; set; }
private GetProductPricesQuery(int maxRecords = 1000) => MaxRecords = maxRecords;
public static GetProductPricesQuery Create(int maxRecords = 1000)
{
if (maxRecords <= 0)
throw new ArgumentOutOfRangeException(nameof(maxRecords), "MaxRecords must be greater than zero.");
return new(maxRecords);
}
}
@@ -4,5 +4,15 @@ namespace LiteCharms.Features.Products.Queries;
public class GetProductsQuery : IRequest<Result<Product[]>>
{
public static GetProductsQuery Create() => new();
public int MaxRecords { get; set; }
private GetProductsQuery(int maxRecords = 1000) => MaxRecords = maxRecords;
public static GetProductsQuery Create(int maxRecords = 1000)
{
if (maxRecords <= 0)
throw new ArgumentException("MaxRecords must be a positive integer.");
return new(maxRecords);
}
}
@@ -0,0 +1,27 @@
using LiteCharms.Extensions;
using LiteCharms.Infrastructure.Database;
using LiteCharms.Models;
namespace LiteCharms.Features.Products.Queries.Handlers;
public class GetProductPricesQueryHandler(IDbContextFactory<LeadGeneratorDbContext> contextFactory) : IRequestHandler<GetProductPricesQuery, Result<ProductPrice[]>>
{
public async ValueTask<Result<ProductPrice[]>> Handle(GetProductPricesQuery request, CancellationToken cancellationToken)
{
try
{
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var products = await context.ProductPrices.AsNoTracking()
.OrderByDescending(o => o.Id)
.Take(request.MaxRecords)
.ToArrayAsync(cancellationToken);
return Result.Ok(products.Select(p => p.ToModel()).ToArray());
}
catch (Exception ex)
{
return Result.Fail<ProductPrice[]>(new Error(ex.Message).CausedBy(ex));
}
}
}
@@ -14,6 +14,7 @@ public class GetProductsQueryHandler(IDbContextFactory<LeadGeneratorDbContext> c
var products = await context.Products.AsNoTracking()
.OrderByDescending(o => o.Id)
.Take(request.MaxRecords)
.ToArrayAsync(cancellationToken);
return Result.Ok(products.Select(p => p.ToModel()).ToArray());
@@ -15,4 +15,6 @@ public class LeadGeneratorDbContext(DbContextOptions<LeadGeneratorDbContext> opt
public DbSet<Product> Products { get; set; }
public DbSet<ProductPrice> ProductPrices { get; set; }
public DbSet<Notification> Notifications { get; set; }
}
@@ -0,0 +1,360 @@
// <auto-generated />
using System;
using LiteCharms.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace LiteCharms.Infrastructure.Database.Migrations
{
[DbContext(typeof(LeadGeneratorDbContext))]
[Migration("20260505120450_GeneralisedLead")]
partial class GeneralisedLead
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.7")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("LiteCharms.Entities.Customer", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Active")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<string>("Address")
.HasColumnType("text");
b.Property<string>("City")
.HasColumnType("text");
b.Property<string>("Company")
.HasColumnType("text");
b.Property<string>("Country")
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone");
b.Property<string>("Discord")
.HasColumnType("text");
b.Property<string>("Email")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LastName")
.IsRequired()
.HasColumnType("text");
b.Property<string>("LinkedIn")
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Phone")
.HasColumnType("text");
b.Property<string>("PostalCode")
.HasColumnType("text");
b.Property<string>("Region")
.HasColumnType("text");
b.Property<string>("Slack")
.HasColumnType("text");
b.Property<string>("Tax")
.HasColumnType("text");
b.Property<DateTimeOffset?>("UpdatedAt")
.ValueGeneratedOnUpdate()
.HasColumnType("timestamp with time zone");
b.Property<string>("Website")
.HasColumnType("text");
b.Property<string>("Whatsapp")
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Customer", (string)null);
});
modelBuilder.Entity("LiteCharms.Entities.Lead", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<long?>("AdGroupId")
.HasColumnType("bigint");
b.Property<long?>("AdName")
.HasColumnType("bigint");
b.Property<string>("AppClickId")
.HasColumnType("text");
b.Property<string>("AttributionHash")
.IsRequired()
.HasColumnType("text");
b.Property<long?>("CampaignId")
.HasColumnType("bigint");
b.Property<string>("ClickId")
.HasColumnType("text");
b.Property<string>("ClickLocation")
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone");
b.Property<Guid?>("CustomerId")
.HasColumnType("uuid");
b.Property<long?>("FeedItemId")
.HasColumnType("bigint");
b.Property<string>("Source")
.HasColumnType("text");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<long?>("TargetId")
.HasColumnType("bigint");
b.Property<DateTimeOffset?>("UpdatedAt")
.ValueGeneratedOnUpdate()
.HasColumnType("timestamp with time zone");
b.Property<string>("WebClickId")
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("CustomerId");
b.ToTable("Lead", (string)null);
});
modelBuilder.Entity("LiteCharms.Entities.Order", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone");
b.Property<Guid>("CustomerId")
.HasColumnType("uuid");
b.PrimitiveCollection<string>("Notes")
.HasColumnType("jsonb");
b.Property<Guid>("ProductPriceId")
.HasColumnType("uuid");
b.Property<Guid?>("RefundId")
.HasColumnType("uuid");
b.Property<int>("Status")
.HasColumnType("integer");
b.Property<DateTimeOffset?>("UpdatedAt")
.ValueGeneratedOnUpdate()
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("CustomerId");
b.HasIndex("ProductPriceId");
b.ToTable("Order", (string)null);
});
modelBuilder.Entity("LiteCharms.Entities.OrderRefund", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<decimal>("Amount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone");
b.Property<Guid>("OrderId")
.HasColumnType("uuid");
b.Property<string>("Reason")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("OrderId")
.IsUnique();
b.ToTable("OrderRefund", (string)null);
});
modelBuilder.Entity("LiteCharms.Entities.Product", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Active")
.ValueGeneratedOnAdd()
.HasColumnType("boolean")
.HasDefaultValue(true);
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Product", (string)null);
});
modelBuilder.Entity("LiteCharms.Entities.ProductPrice", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<bool>("Active")
.HasColumnType("boolean");
b.Property<DateTimeOffset>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("timestamp with time zone");
b.Property<decimal>("Discount")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<decimal>("Price")
.HasPrecision(18, 2)
.HasColumnType("numeric(18,2)");
b.Property<Guid>("ProductId")
.HasColumnType("uuid");
b.Property<DateTimeOffset?>("UpdatedAt")
.ValueGeneratedOnUpdate()
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("ProductId");
b.ToTable("ProductPrice", (string)null);
});
modelBuilder.Entity("LiteCharms.Entities.Lead", b =>
{
b.HasOne("LiteCharms.Entities.Customer", "Customer")
.WithMany("Leads")
.HasForeignKey("CustomerId")
.OnDelete(DeleteBehavior.NoAction);
b.Navigation("Customer");
});
modelBuilder.Entity("LiteCharms.Entities.Order", b =>
{
b.HasOne("LiteCharms.Entities.Customer", "Customer")
.WithMany("Orders")
.HasForeignKey("CustomerId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("LiteCharms.Entities.ProductPrice", "ProductPrice")
.WithMany()
.HasForeignKey("ProductPriceId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Customer");
b.Navigation("ProductPrice");
});
modelBuilder.Entity("LiteCharms.Entities.OrderRefund", b =>
{
b.HasOne("LiteCharms.Entities.Order", "Order")
.WithOne("Refund")
.HasForeignKey("LiteCharms.Entities.OrderRefund", "OrderId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Order");
});
modelBuilder.Entity("LiteCharms.Entities.ProductPrice", b =>
{
b.HasOne("LiteCharms.Entities.Product", "Product")
.WithMany("ProductPrices")
.HasForeignKey("ProductId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Product");
});
modelBuilder.Entity("LiteCharms.Entities.Customer", b =>
{
b.Navigation("Leads");
b.Navigation("Orders");
});
modelBuilder.Entity("LiteCharms.Entities.Order", b =>
{
b.Navigation("Refund");
});
modelBuilder.Entity("LiteCharms.Entities.Product", b =>
{
b.Navigation("ProductPrices");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,38 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace LiteCharms.Infrastructure.Database.Migrations
{
/// <inheritdoc />
public partial class GeneralisedLead : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.RenameColumn(
name: "GoogleClickId",
table: "Lead",
newName: "Source");
migrationBuilder.AddColumn<string>(
name: "ClickId",
table: "Lead",
type: "text",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "ClickId",
table: "Lead");
migrationBuilder.RenameColumn(
name: "Source",
table: "Lead",
newName: "GoogleClickId");
}
}
}
@@ -119,6 +119,9 @@ namespace LiteCharms.Infrastructure.Migrations
b.Property<long?>("CampaignId")
.HasColumnType("bigint");
b.Property<string>("ClickId")
.HasColumnType("text");
b.Property<string>("ClickLocation")
.HasColumnType("text");
@@ -132,7 +135,7 @@ namespace LiteCharms.Infrastructure.Migrations
b.Property<long?>("FeedItemId")
.HasColumnType("bigint");
b.Property<string>("GoogleClickId")
b.Property<string>("Source")
.HasColumnType("text");
b.Property<int>("Status")
+25
View File
@@ -0,0 +1,25 @@
namespace LiteCharms.Models;
public sealed class EmailEnquiry
{
[Required]
[MinLength(2)]
[MaxLength(255)]
public string? FullName { get; set; }
[Required]
[EmailAddress]
[MinLength(20)]
[MaxLength(255)]
public string? EmailAddress { get; set; }
[Required]
[MinLength(2)]
[MaxLength(255)]
public string? EmailSubject { get; set; }
[Required]
[MinLength(2)]
[MaxLength(2000)]
public string? Message { get; set; }
}
+7
View File
@@ -19,3 +19,10 @@ public enum LeadStatus : int
Converted = 4,
Lost = 5
}
public enum NotificationDirection : int
{
Incoming = 0,
Outgoing = 1,
Neutral = 2
}
+3 -1
View File
@@ -10,7 +10,9 @@ public class Lead
public Guid? CustomerId { get; set; }
public string? GoogleClickId { get; set; }
public string? Source { get; set; }
public string? ClickId { get; set; }
public string? WebClickId { get; set; }
@@ -23,6 +23,11 @@
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>
<!-- Global Usings -->
<ItemGroup>
<Using Include="System.ComponentModel.DataAnnotations"/>
</ItemGroup>
<ItemGroup>
<None Include="..\LICENSE" Pack="true" PackagePath="\" />
<None Include="..\icon.png" Pack="true" PackagePath="\" />
+26
View File
@@ -0,0 +1,26 @@
namespace LiteCharms.Models;
public class Notification
{
public Guid Id { get; set; }
public DateTimeOffset CreatedAt { get; set; }
public NotificationDirection Direction { get; set; }
public string? Author { get; set; }
public string? Title { get; set; }
public string? Description { get; set; }
public string? Platform { get; set; }
public string? PlatformAddress { get; set; }
public string? CorrelationId { get; set; }
public string? CorrelationIdType { get; set; }
public bool IsInternal { get; set; }
}