Add project files.

This commit is contained in:
2025-09-01 13:34:36 +04:30
parent 1f2fb6e2d7
commit 81e43f5b92
83 changed files with 1733 additions and 0 deletions
@@ -0,0 +1,16 @@
using VizhTalk.BuildingBlocks.Application.Contract;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
public interface ICommandHandler<in TCommand>
where TCommand : ICommand
{
Task Handle(TCommand request, CancellationToken cancellationToken);
}
public interface ICommandHandler<in TCommand, TResult>
where TCommand : ICommand<TResult>
{
Task<TResult> Handle(TCommand request, CancellationToken cancellationToken);
}
@@ -0,0 +1,9 @@
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Application.Contract;
public interface INotificationHandler<T>
where T : INotification
{
Task Handle(T notification, CancellationToken cancellationToken);
}
@@ -0,0 +1,9 @@
using VizhTalk.BuildingBlocks.Application.Contract;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Application.Configuration.Query;
public interface IQueryHandler<in TQuery, TResult>
where TQuery : IQuery<TResult>
{
}
@@ -0,0 +1,11 @@
namespace VizhTalk.BuildingBlocks.Application.Contract;
public interface IExecutionContextAccessor
{
Guid UserId { get; }
Guid CorrelationId { get; }
bool IsAvailable { get; }
}
@@ -0,0 +1,17 @@
using VizhTalk.BuildingBlocks.Domain;
namespace VizhTalk.BuildingBlocks.Application.Events;
public class DomainNotificationBase<T> : IDomainEventNotification<T>
where T : IDomainEvent
{
public T DomainEvent { get; }
public Guid Id { get; }
public DomainNotificationBase(T domainEvent, Guid id)
{
this.Id = id;
this.DomainEvent = domainEvent;
}
}
@@ -0,0 +1,14 @@
using VizhTalk.BuildingBlocks.Application.Contract;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Application.Events;
public interface IDomainEventNotification<out TEventType> : IDomainEventNotification
{
TEventType DomainEvent { get; }
}
public interface IDomainEventNotification : INotification
{
Guid Id { get; }
}
@@ -0,0 +1,8 @@
namespace VizhTalk.BuildingBlocks.Application.Outbox;
public interface IOutbox
{
void Add(OutboxMessage message);
Task Save();
}
@@ -0,0 +1,26 @@
namespace VizhTalk.BuildingBlocks.Application.Outbox;
public class OutboxMessage
{
public Guid Id { get; set; }
public DateTime OccurredOn { get; set; }
public string Type { get; set; }
public string Data { get; set; }
public DateTime? ProcessedDate { get; set; }
public OutboxMessage(Guid id, DateTime occurredOn, string type, string data)
{
this.Id = id;
this.OccurredOn = occurredOn;
this.Type = type;
this.Data = data;
}
private OutboxMessage()
{
}
}
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Domain\VizhTalk.BuildingBlocks.Domain.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data\" />
</ItemGroup>
</Project>
@@ -0,0 +1,27 @@
namespace VizhTalk.BuildingBlocks.Domain.Contract;
public abstract record CommandBase<T> : ICommand<T>
{
public Guid Id { get; private set; }
protected CommandBase()
{
Id = Guid.NewGuid();
}
protected CommandBase(Guid id)
{
Id = id;
}
}
public abstract record CommandBase : ICommand
{
public Guid Id { get; private set; }
protected CommandBase()
{
Id = Guid.NewGuid();
}
protected CommandBase(Guid id)
{
Id = id;
}
}
@@ -0,0 +1,11 @@
namespace VizhTalk.BuildingBlocks.Domain.Contract;
public interface ICommand<out TResult>
{
Guid Id { get; }
}
public interface ICommand
{
Guid Id { get; }
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.BuildingBlocks.Domain.Contract
{
public interface IGuardException
{
}
}
@@ -0,0 +1,5 @@
namespace VizhTalk.BuildingBlocks.Domain.Contract;
public interface INotification
{
}
@@ -0,0 +1,5 @@
namespace VizhTalk.BuildingBlocks.Domain.Contract;
public interface IQuery<out TResult>
{
}
@@ -0,0 +1,14 @@
namespace VizhTalk.BuildingBlocks.Domain;
public class DomainEventBase : IDomainEvent
{
public Guid Id { get; }
public DateTime OccurredOn { get; }
public DomainEventBase()
{
this.Id = Guid.NewGuid();
this.OccurredOn = DateTime.UtcNow;
}
}
+40
View File
@@ -0,0 +1,40 @@
using VizhTalk.BuildingBlocks.Domain.Exceptions;
namespace VizhTalk.BuildingBlocks.Domain;
public abstract class Entity<T>
where T : ITypeIdValueBase
{
public Entity()
{
IsDeleted = false;
IsActive = true;
}
public T Id { get; protected set; }
public bool IsDeleted { get; protected set; }
public bool IsActive { get; protected set; }
private List<IDomainEvent> _domainEvents;
public void ClearDomainEvents()
{
_domainEvents?.Clear();
}
protected void AddDomainEvent(IDomainEvent domainEvent)
{
_domainEvents ??= [];
this._domainEvents.Add(domainEvent);
}
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents;
protected void CheckRule(IBusinessRule rule)
{
if (rule.IsBroken())
{
throw new BusinessRuleValidationException(rule);
}
}
}
@@ -0,0 +1,20 @@
namespace VizhTalk.BuildingBlocks.Domain.Exceptions;
public class BusinessRuleValidationException : Exception
{
public IBusinessRule BrokenRule { get; }
public string Details { get; }
public BusinessRuleValidationException(IBusinessRule brokenRule)
: base(brokenRule.Message)
{
BrokenRule = brokenRule;
this.Details = brokenRule.Message;
}
public override string ToString()
{
return $"{BrokenRule.GetType().FullName}: {BrokenRule.Message}";
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Domain.Exceptions.GuardExceptions;
public class NullOrEmptyException(string message) : Exception(message), IGuardException
{
}
@@ -0,0 +1,8 @@
namespace VizhTalk.BuildingBlocks.Domain.Exceptions;
public interface IBusinessRule
{
bool IsBroken();
string Message { get; }
}
@@ -0,0 +1,9 @@
namespace VizhTalk.BuildingBlocks.Domain.Exceptions;
public class ValueObjectValidationException : Exception
{
public ValueObjectValidationException(string message)
:base(message)
{
}
}
@@ -0,0 +1,8 @@
namespace VizhTalk.BuildingBlocks.Domain.Guards
{
public static class Guard
{
public static void NullOrEmpty(this string value, string name)
=> StringGuard.NullOrEmpty(value, name);
}
}
@@ -0,0 +1,12 @@
using VizhTalk.BuildingBlocks.Domain.Exceptions.GuardExceptions;
namespace VizhTalk.BuildingBlocks.Domain.Guards;
internal static class StringGuard
{
internal static void NullOrEmpty(string value,string name)
{
if(string.IsNullOrEmpty(value))
throw new NullOrEmptyException($"{name} cannot be empty");
}
}
@@ -0,0 +1,7 @@
namespace VizhTalk.BuildingBlocks.Domain;
public interface IAggregateRoot
{
}
@@ -0,0 +1,12 @@
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Domain
{
public interface IDomainEvent : INotification
{
Guid Id { get; }
DateTime OccurredOn { get; }
}
}
@@ -0,0 +1,10 @@
namespace VizhTalk.BuildingBlocks.Domain;
public interface IRepositoryBase<TDomain, TId>
where TDomain : Entity<TId> where TId : ITypeIdValueBase
{
Task<TDomain> CreateAsync(TDomain entity, CancellationToken cancellationToken = default);
Task<TDomain> UpdateAsync(TDomain entity, CancellationToken cancellationToken = default);
Task DeleteAsync(TId id, CancellationToken cancellationToken = default);
Task<TDomain> GetByIdAsync(TId id, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,6 @@
namespace VizhTalk.BuildingBlocks.Domain;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public class IgnoreMemberAttribute : Attribute
{
}
@@ -0,0 +1,59 @@
namespace VizhTalk.BuildingBlocks.Domain;
public abstract class TypedIdValueBase<T> : IEquatable<TypedIdValueBase<T>> , ITypeIdValueBase
{
public T Value { get; }
protected TypedIdValueBase(T value)
{
if (EqualityComparer<T>.Default.Equals(value, default(T)))
{
throw new InvalidOperationException($"Id value cannot be empty ! : id is {value}");
}
Value = value;
}
public override bool Equals(object? obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
return obj is TypedIdValueBase<T> other && Equals(other);
}
public override int GetHashCode()
{
return Value!.GetHashCode();
}
public bool Equals(TypedIdValueBase<T>? other)
{
return EqualityComparer<T>.Default.Equals(this.Value, other.Value);
}
public static bool operator ==(TypedIdValueBase<T>? obj1, TypedIdValueBase<T>? obj2)
{
if (object.Equals(obj1, null))
{
if (object.Equals(obj2, null))
{
return true;
}
return false;
}
return obj1.Equals(obj2);
}
public static bool operator !=(TypedIdValueBase<T>? x, TypedIdValueBase<T>? y)
{
return !(x == y);
}
}
public interface ITypeIdValueBase { }
@@ -0,0 +1,123 @@
using System.Reflection;
using VizhTalk.BuildingBlocks.Domain.Exceptions;
namespace VizhTalk.BuildingBlocks.Domain;
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));
}
public override int GetHashCode()
{
unchecked
{
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;
}
}
protected static void CheckRule(IBusinessRule rule)
{
if (rule.IsBroken())
{
throw new BusinessRuleValidationException(rule);
}
}
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 | BindingFlags.NonPublic)
.Where(p => p.GetCustomAttribute(typeof(IgnoreMemberAttribute)) == null)
.ToList();
}
return this._fields;
}
private int HashValue(int seed, object value)
{
var currentHash = value?.GetHashCode() ?? 0;
return (seed * 23) + currentHash;
}
}
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>
@@ -0,0 +1,30 @@
namespace VizhTalk.BuildingBlocks.Infrastructure;
public class BiDictionary<TFirst, TSecond>
{
private readonly IDictionary<TFirst, TSecond> _firstToSecond = new Dictionary<TFirst, TSecond>();
private readonly IDictionary<TSecond, TFirst> _secondToFirst = new Dictionary<TSecond, TFirst>();
public void Add(TFirst first, TSecond second)
{
if (_firstToSecond.ContainsKey(first) ||
_secondToFirst.ContainsKey(second))
{
throw new ArgumentException("Duplicate first or second");
}
_firstToSecond.Add(first, second);
_secondToFirst.Add(second, first);
}
public bool TryGetByFirst(TFirst first, out TSecond second)
{
return _firstToSecond.TryGetValue(first, out second);
}
public bool TryGetBySecond(TSecond second, out TFirst first)
{
return _secondToFirst.TryGetValue(second, out first);
}
}
@@ -0,0 +1,35 @@
using Microsoft.EntityFrameworkCore;
using VizhTalk.BuildingBlocks.Domain;
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
public class DomainEventsAccessor : IDomainEventsAccessor
{
private readonly DbContext _dbContext;
public DomainEventsAccessor(DbContext dbContext)
{
_dbContext = dbContext;
}
public IReadOnlyCollection<IDomainEvent> GetAllDomainEvents()
{
var domainEntities = this._dbContext.ChangeTracker
.Entries<Entity<ITypeIdValueBase>>()
.Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any()).ToList();
return domainEntities
.SelectMany(x => x.Entity.DomainEvents)
.ToList();
}
public void ClearAllDomainEvents()
{
var domainEntities = this._dbContext.ChangeTracker
.Entries<Entity<ITypeIdValueBase>>()
.Where(x => x.Entity.DomainEvents != null && x.Entity.DomainEvents.Any()).ToList();
domainEntities
.ForEach(entity => entity.Entity.ClearDomainEvents());
}
}
@@ -0,0 +1,78 @@
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json;
using VizhTalk.BuildingBlocks.Application.Events;
using VizhTalk.BuildingBlocks.Application.Outbox;
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.BuildingBlocks.Infrastructure.Serialization;
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
public class DomainEventsDispatcher : IDomainEventsDispatcher
{
private readonly IExecutor _executor;
private readonly IServiceProvider _provider;
private readonly IOutbox _outbox;
private readonly IDomainEventsAccessor _domainEventsProvider;
private readonly IDomainNotificationsMapper _domainNotificationsMapper;
public DomainEventsDispatcher(
IExecutor executor,
IServiceProvider provider,
IOutbox outbox,
IDomainEventsAccessor domainEventsProvider,
IDomainNotificationsMapper domainNotificationsMapper)
{
_executor = executor;
_provider = provider;
_outbox = outbox;
_domainEventsProvider = domainEventsProvider;
_domainNotificationsMapper = domainNotificationsMapper;
}
public async Task DispatchEventsAsync()
{
var domainEvents = _domainEventsProvider.GetAllDomainEvents();
List<IDomainEventNotification<IDomainEvent>> domainEventNotifications = [];
foreach (var domainEvent in domainEvents)
{
Type domainEvenNotificationType = typeof(IDomainEventNotification<>);
var domainNotificationWithGenericType = domainEvenNotificationType.MakeGenericType(domainEvent.GetType());
var domainNotification = _provider.GetServices(domainNotificationWithGenericType);
if (domainNotification != null)
{
domainEventNotifications.Add(domainNotification as IDomainEventNotification<IDomainEvent>);
}
}
_domainEventsProvider.ClearAllDomainEvents();
foreach (var domainEvent in domainEvents)
{
await _executor.Execute(domainEvent);
}
foreach (var domainEventNotification in domainEventNotifications)
{
var type = _domainNotificationsMapper.GetName(domainEventNotification.GetType());
var data = JsonConvert.SerializeObject(domainEventNotification, new JsonSerializerSettings
{
ContractResolver = new AllPropertiesContractResolver()
});
var outboxMessage = new OutboxMessage(
domainEventNotification.Id,
domainEventNotification.DomainEvent.OccurredOn,
type,
data);
_outbox.Add(outboxMessage);
}
await _outbox.Save();
}
}
@@ -0,0 +1,27 @@
using VizhTalk.BuildingBlocks.Application.Contract;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
public class DomainEventsDispatcherNotificationHandlerDecorator<T> : INotificationHandler<T>
where T : INotification
{
private readonly INotificationHandler<T> _decorated;
private readonly IDomainEventsDispatcher _domainEventsDispatcher;
public DomainEventsDispatcherNotificationHandlerDecorator(
IDomainEventsDispatcher domainEventsDispatcher,
INotificationHandler<T> decorated)
{
_domainEventsDispatcher = domainEventsDispatcher;
_decorated = decorated;
}
public async Task Handle(T notification, CancellationToken cancellationToken)
{
await _decorated.Handle(notification, cancellationToken);
await _domainEventsDispatcher.DispatchEventsAsync();
}
}
@@ -0,0 +1,21 @@
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
public class DomainNotificationsMapper : IDomainNotificationsMapper
{
private readonly BiDictionary<string, Type> _domainNotificationsMap;
public DomainNotificationsMapper(BiDictionary<string, Type> domainNotificationsMap)
{
_domainNotificationsMap = domainNotificationsMap;
}
public string GetName(Type type)
{
return _domainNotificationsMap.TryGetBySecond(type, out var name) ? name : null;
}
public Type GetType(string name)
{
return _domainNotificationsMap.TryGetByFirst(name, out var type) ? type : null;
}
}
@@ -0,0 +1,10 @@
using VizhTalk.BuildingBlocks.Domain;
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
public interface IDomainEventsAccessor
{
IReadOnlyCollection<IDomainEvent> GetAllDomainEvents();
void ClearAllDomainEvents();
}
@@ -0,0 +1,8 @@
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
public interface IDomainEventsDispatcher
{
Task DispatchEventsAsync();
}
@@ -0,0 +1,7 @@
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
public interface IDomainNotificationsMapper
{
string GetName(Type type);
Type GetType(string name);
}
@@ -0,0 +1,60 @@
using VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
using VizhTalk.BuildingBlocks.Application.Contract;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
public class UnitOfWorkCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
where TCommand : ICommand
{
private readonly ICommandHandler<TCommand> _decorated;
private readonly IUnitOfWork _unitOfWork;
private readonly IDomainEventsDispatcher _domainEventsDispatcher;
public UnitOfWorkCommandHandlerDecorator(
ICommandHandler<TCommand> decorated,
IUnitOfWork unitOfWork,
IDomainEventsDispatcher domainEventsDispatcher)
{
_decorated = decorated;
_unitOfWork = unitOfWork;
_domainEventsDispatcher = domainEventsDispatcher;
}
public async Task Handle(TCommand command, CancellationToken cancellationToken)
{
await this._decorated.Handle(command, cancellationToken);
await this._unitOfWork.CommitAsync(cancellationToken);
await this._domainEventsDispatcher.DispatchEventsAsync();
}
}
public class UnitOfWorkCommandHandlerDecorator<TCommand,TResponse> : ICommandHandler<TCommand, TResponse>
where TCommand : ICommand<TResponse>
{
private readonly ICommandHandler<TCommand, TResponse> _decorated;
private readonly IUnitOfWork _unitOfWork;
private readonly IDomainEventsDispatcher _domainEventsDispatcher;
public UnitOfWorkCommandHandlerDecorator(
ICommandHandler<TCommand, TResponse> decorated,
IUnitOfWork unitOfWork,
IDomainEventsDispatcher domainEventsDispatcher)
{
_decorated = decorated;
_unitOfWork = unitOfWork;
_domainEventsDispatcher = domainEventsDispatcher;
}
public async Task<TResponse> Handle(TCommand command, CancellationToken cancellationToken)
{
var result = await this._decorated.Handle(command, cancellationToken);
await this._unitOfWork.CommitAsync(cancellationToken);
await this._domainEventsDispatcher.DispatchEventsAsync();
return result;
}
}
@@ -0,0 +1,31 @@
using Humanizer;
using Microsoft.EntityFrameworkCore;
using System.Reflection;
using VizhTalk.BuildingBlocks.Domain;
namespace VizhTalk.BuildingBlocks.Infrastructure.EF.Helpers
{
public static class Extensions
{
public static ModelBuilder FindEntities(this ModelBuilder modelBuilder)
{
var entityTypes = typeof(Entity<>).Assembly
.GetTypes()
.Where(t => t.IsClass && !t.IsAbstract && typeof(Entity<>).IsAssignableFrom(t));
foreach (var type in entityTypes)
modelBuilder.Entity(type);
return modelBuilder;
}
public static ModelBuilder PluralizeEntities(this ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var tableName = entityType.ClrType.Name.Pluralize();
modelBuilder.Entity(entityType.ClrType).ToTable(tableName);
}
return modelBuilder;
}
}
}
@@ -0,0 +1,12 @@
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.BuildingBlocks.Domain.Contract;
namespace VizhTalk.BuildingBlocks.Infrastructure;
public interface IExecutor
{
Task Execute(ICommand command, CancellationToken cancellationToken = default);
Task<TResult> Execute<TResult>(ICommand<TResult> command, CancellationToken cancellationToken = default);
Task<TResult> Execute<TResult>(IQuery<TResult> query, CancellationToken cancellationToken = default);
Task Execute(IDomainEvent notification, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,7 @@
namespace VizhTalk.BuildingBlocks.Infrastructure;
public interface IUnitOfWork
{
Task<int> CommitAsync(
CancellationToken cancellationToken = default);
}
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.BuildingBlocks.Infrastructure.Inbox
{
public interface IInbox
{
void Add(InboxMessage message);
Task Save();
}
}
@@ -0,0 +1,26 @@
namespace VizhTalk.BuildingBlocks.Infrastructure.Inbox;
public class InboxMessage
{
public Guid Id { get; set; }
public DateTime OccurredOn { get; set; }
public string Type { get; set; }
public string Data { get; set; }
public DateTime? ProcessedDate { get; set; }
public InboxMessage(DateTime occurredOn, string type, string data)
{
this.Id = Guid.NewGuid();
this.OccurredOn = occurredOn;
this.Type = type;
this.Data = data;
}
private InboxMessage()
{
}
}
@@ -0,0 +1,12 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace VizhTalk.BuildingBlocks.Infrastructure.Mapper;
internal static class ReplaceMediatorOpenTypes
{
public static void Replace(this IServiceCollection services)
{
//services.Replace(new ServiceDescriptor()) // ToDo
}
}
@@ -0,0 +1,25 @@
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using System.Reflection;
namespace VizhTalk.BuildingBlocks.Infrastructure.Serialization;
public class AllPropertiesContractResolver : DefaultContractResolver
{
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
{
var properties = type.GetProperties(
BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.Select(p => this.CreateProperty(p, memberSerialization))
.ToList();
properties.ForEach(p =>
{
p.Writable = true;
p.Readable = true;
});
return properties;
}
}
@@ -0,0 +1,26 @@
using Microsoft.EntityFrameworkCore;
using VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
namespace VizhTalk.BuildingBlocks.Infrastructure;
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext _context;
private readonly IDomainEventsDispatcher _domainEventsDispatcher;
public UnitOfWork(
DbContext context,
IDomainEventsDispatcher domainEventsDispatcher)
{
this._context = context;
this._domainEventsDispatcher = domainEventsDispatcher;
}
public async Task<int> CommitAsync(
CancellationToken cancellationToken = default)
{
await this._domainEventsDispatcher.DispatchEventsAsync();
return await _context.SaveChangesAsync(cancellationToken);
}
}
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Humanizer" Version="3.0.0-beta.96" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.19" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.19" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.19" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Scrutor" Version="6.1.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Application\VizhTalk.BuildingBlocks.Application.csproj" />
</ItemGroup>
</Project>
+18
View File
@@ -0,0 +1,18 @@
using VizhTalk.Gateway.Web.BFF.Queries;
using VizhTalk.Grpc.Learning.Language;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpcClient<LanguageGrpc.LanguageGrpcClient>(o =>
{
o.Address = new Uri("https://localhost:5000");
});
builder.Services.AddScoped<LanguageQuery>();
builder.AddGraphQL()
.AddQueryType<LanguageQuery>();
var app = builder.Build();
app.MapGraphQL();
app.Run();
@@ -0,0 +1,38 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:37086",
"sslPort": 44394
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5137",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7211;http://localhost:5137",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,16 @@
using VizhTalk.Grpc.Learning.Language;
namespace VizhTalk.Gateway.Web.BFF.Queries
{
public class LanguageQuery(LanguageGrpc.LanguageGrpcClient languageGrpcClient)
{
[GraphQLName("Languages")]
public async Task<GetAllLanguageReply> GetAll()
{
return await languageGrpcClient.GetAllAsync(new Empty());
}
[GraphQLName("Language")]
public async Task<Language> GetLanguageByTitle(GetLanguageByTitleRequest request)
=> await languageGrpcClient.GetLanguageByTitleAsync(request);
}
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
<PackageReference Include="HotChocolate.AspNetCore" Version="16.0.0-p.7.40" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.Services.Grpc.Learning\VizhTalk.Grpc.Learning.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
@@ -0,0 +1,9 @@
using VizhTalk.BuildingBlocks.Domain.Contract;
using VizhTalk.BuildingBlocks.Infrastructure;
namespace VizhTalk.Learning.Application;
public interface ILearningModule : IExecutor
{
}
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Application\VizhTalk.BuildingBlocks.Application.csproj" />
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Infrastructure\VizhTalk.BuildingBlocks.Infrastructure.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class Course
{
}
}
@@ -0,0 +1,11 @@
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.Learning.Domain.TypeIds;
using VizhTalk.Learning.Domain.ValueObjects;
namespace VizhTalk.Learning.Domain.Entities;
public class Language : Entity<LanguageId>, IAggregateRoot
{
public LanguageTitle Title { get; private set; }
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class Learn
{
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class Lesson
{
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class LessonType
{
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class Option
{
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class Section
{
}
}
+12
View File
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class Test
{
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class TestOption
{
}
}
+12
View File
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VizhTalk.Learning.Domain.Entities
{
internal class Unit
{
}
}
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VizhTalk.BuildingBlocks.Domain;
namespace VizhTalk.Learning.Domain.TypeIds
{
public class LanguageId : TypedIdValueBase<LanguageId>
{
public LanguageId(LanguageId value) : base(value)
{
}
}
}
@@ -0,0 +1,15 @@
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.BuildingBlocks.Domain.Guards;
namespace VizhTalk.Learning.Domain.ValueObjects
{
public class LanguageTitle : ValueObject
{
public LanguageTitle(string title)
{
Guard.NullOrEmpty(title, nameof(LanguageTitle));
Value = title;
}
public string Value { get; set; }
}
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Folder Include="Events\" />
<Folder Include="Exceptions\" />
<Folder Include="Rules\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Domain\VizhTalk.BuildingBlocks.Domain.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VizhTalk.Learning.Application;
namespace VizhTalk.Learning.Infrastructure.Configuration
{
public static class Assemblies
{
public static Assembly Infrastructure = typeof(LearningStartup).Assembly;
public static Assembly Application = typeof(ILearningModule).Assembly;
}
}
@@ -0,0 +1,48 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
using VizhTalk.BuildingBlocks.Application.Contract;
using VizhTalk.BuildingBlocks.Domain;
using VizhTalk.BuildingBlocks.Domain.Contract;
using VizhTalk.Learning.Application;
namespace VizhTalk.Learning.Infrastructure.Configuration;
public class LearningModule(IServiceProvider serviceProvider) : ILearningModule
{
public async Task Execute(ICommand command, CancellationToken cancellationToken = default)
{
var handlerType = typeof(ICommandHandler<>).MakeGenericType(command.GetType());
var handler = serviceProvider.GetRequiredService(handlerType);
dynamic dynamicHandler = handler;
dynamic dynamicCommand = command;
await dynamicHandler.Handle(dynamicCommand, cancellationToken);
}
public async Task<TResult> Execute<TResult>(ICommand<TResult> command, CancellationToken cancellationToken = default)
{
var handlerType = typeof(ICommandHandler<,>).MakeGenericType(command.GetType());
var handler = serviceProvider.GetRequiredService(handlerType);
dynamic dynamicHandler = handler;
dynamic dynamicCommand = command;
return await dynamicHandler.Handle(dynamicCommand, cancellationToken);
}
public async Task<TResult> Execute<TResult>(IQuery<TResult> query, CancellationToken cancellationToken = default)
{
var handlerType = typeof(ICommandHandler<,>).MakeGenericType(query.GetType());
var handler = serviceProvider.GetRequiredService(handlerType);
dynamic dynamicHandler = handler;
dynamic dynamicQuery = query;
return await dynamicHandler.Handle(dynamicQuery, cancellationToken);
}
public async Task Execute(IDomainEvent notification, CancellationToken cancellationToken = default)
{
var handlerType = typeof(INotificationHandler<>).MakeGenericType(notification.GetType());
var handler = serviceProvider.GetRequiredService(handlerType);
dynamic dynamicHandler = handler;
dynamic dynamicNotification = notification;
await dynamicHandler.Handle(dynamicNotification, cancellationToken);
}
}
@@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
using VizhTalk.BuildingBlocks.Infrastructure.EF.Helpers;
namespace VizhTalk.Learning.Infrastructure.EF;
public class LearningDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.FindEntities()
.PluralizeEntities();
}
}
@@ -0,0 +1,60 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using VizhTalk.BuildingBlocks.Application.Configuration.Commnad;
using VizhTalk.BuildingBlocks.Application.Configuration.Query;
using VizhTalk.BuildingBlocks.Application.Events;
using VizhTalk.BuildingBlocks.Application.Outbox;
using VizhTalk.BuildingBlocks.Infrastructure;
using VizhTalk.BuildingBlocks.Infrastructure.DomainEventsDispatching;
using VizhTalk.Learning.Application;
using VizhTalk.Learning.Infrastructure.Configuration;
using VizhTalk.Learning.Infrastructure.EF;
namespace VizhTalk.Learning.Infrastructure;
public static class LearningStartup
{
public static void Initialize(this IServiceCollection services, string connectionString)
{
services.AddDbContext<LearningDbContext>(op =>
{
op.UseSqlServer(connectionString);
});
services.AddScoped<ILearningModule, LearningModule>();
services.AddScoped<IExecutor, LearningModule>();
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IDomainEventsAccessor, DomainEventsAccessor>();
services.AddScoped<IDomainEventsDispatcher, DomainEventsDispatcher>();
services.AddScoped<IDomainNotificationsMapper, DomainNotificationsMapper>();
services.Scan(scan => scan.
FromAssemblies(Assemblies.Application, Assemblies.Infrastructure)
.AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>)), publicOnly: false)
.AsImplementedInterfaces()
.WithScopedLifetime()
.AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<,>)), publicOnly: false)
.AsImplementedInterfaces()
.WithScopedLifetime()
.AddClasses(classes => classes.AssignableTo(typeof(IQueryHandler<,>)), publicOnly: false)
.AsImplementedInterfaces()
.WithScopedLifetime()
.AddClasses(classes => classes.AssignableTo(typeof(ICommandHandler<>)), publicOnly: false)
.AsImplementedInterfaces()
.WithScopedLifetime());
services.Decorate(typeof(ICommandHandler<>), typeof(UnitOfWorkCommandHandlerDecorator<>));
services.AddScoped(sa =>
{
var notifications = sa.GetServices<IDomainEventNotification>();
var notificationDic = new BiDictionary<string, Type>();
foreach (var notification in notifications)
notificationDic.Add(notification!.ToString()!, notification!.GetType());
return notificationDic;
});
services.AddScoped<IOutbox>(sa =>
{
return null; // ToDo
});
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VizhTalk.BuildingBlocks.Application.Outbox;
namespace VizhTalk.Learning.Infrastructure.Outbox
{
public class OutboxAccessor : IOutbox
{
public OutboxAccessor()
{
}
public void Add(OutboxMessage message)
{
throw new NotImplementedException();
}
public Task Save()
{
throw new NotImplementedException();
}
}
}
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.BuildingBlocks.Infrastructure\VizhTalk.BuildingBlocks.Infrastructure.csproj" />
<ProjectReference Include="..\VizhTalk.Learning.Application\VizhTalk.Learning.Application.csproj" />
</ItemGroup>
</Project>
+16
View File
@@ -0,0 +1,16 @@
using VizhTalk.Learning.Infrastructure;
using VizhTalk.Learning.Presentation.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddGrpc();
builder.Services.Initialize("");
var app = builder.Build();
// Configure the HTTP request pipeline.
app.MapGrpcService<LanguageService>();
app.Run();
@@ -0,0 +1,23 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
@@ -0,0 +1,27 @@
using Google.Protobuf.Collections;
using Grpc.Core;
using VizhTalk.Grpc.Learning.Language;
namespace VizhTalk.Learning.Presentation.Services;
public class LanguageService : LanguageGrpc.LanguageGrpcBase
{
public static GetAllLanguageReply Languages = new GetAllLanguageReply();
public LanguageService()
{
Languages.Languages.Add(new Language() { Title = "English" });
Languages.Languages.Add(new Language() { Title = "Germany" });
Languages.Languages.Add(new Language() { Title = "Persian" });
Languages.Languages.Add(new Language() { Title = "Spain" });
}
public override async Task<GetAllLanguageReply> GetAll(Empty request, ServerCallContext context)
{
return Languages;
}
public override async Task<Language> GetLanguageByTitle(GetLanguageByTitleRequest request, ServerCallContext context)
{
var language = Languages.Languages.FirstOrDefault(x => x.Title == request.Title);
return language;
}
}
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.57.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VizhTalk.Learning.Infrastructure\VizhTalk.Learning.Infrastructure.csproj" />
<ProjectReference Include="..\VizhTalk.Services.Grpc.Learning\VizhTalk.Grpc.Learning.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
@@ -0,0 +1,14 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
}
@@ -0,0 +1,24 @@
syntax="proto3";
option csharp_namespace="VizhTalk.Grpc.Learning.Language";
package greet;
message Language {
string title = 1;
}
message GetAllLanguageReply {
repeated Language languages = 1;
}
message Empty {}
message GetLanguageByTitleRequest {
string title = 1;
}
service LanguageGrpc {
rpc GetAll (Empty) returns (GetAllLanguageReply);
rpc GetLanguageByTitle (GetLanguageByTitleRequest) returns (Language);
}
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.32.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.71.0" />
<PackageReference Include="Grpc.Tools" Version="2.58.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="Language.proto" GrpcServices="Both" />
</ItemGroup>
</Project>
+120
View File
@@ -0,0 +1,120 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34330.188
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateways", "Gateways", "{FCA7DC48-FD9C-43FA-AC1B-464049337778}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "User", "User", "{AC8EE492-AB63-4799-946A-98F819478EB2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Chat", "Chat", "{E507BEFF-5CB6-43BA-B95A-B6975840B043}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Learning", "Learning", "{1305E6AB-69E5-47BD-B03C-694832C4D46A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "AI", "AI", "{35CC083E-B7AE-4495-A95A-68C0C0F9A2F7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CDN", "CDN", "{9E64DF44-8256-40C8-8AFA-B841CE80DD0E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meetting", "Meetting", "{26FDAD4D-55F4-445B-BE66-65193D21D14D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Profile", "Profile", "{D74A2A69-224B-4498-8E99-1BC086C594CB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UserProgress", "UserProgress", "{75826056-3765-4A48-9827-02ED1F27E40D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{F1386DBF-90B7-4285-93F2-FD60DFBAD600}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Grpcs", "Grpcs", "{16D91427-4F51-48CD-A2F2-6CC1B9A9D8A5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VizhTalk.Learning.Domain", "VizhTalk.Learning.Domain\VizhTalk.Learning.Domain.csproj", "{B294A620-DADE-4D31-9BF5-C1CC5D7E29EB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VizhTalk.Learning.Application", "VizhTalk.Learning.Application\VizhTalk.Learning.Application.csproj", "{B5065F1D-F391-4511-8697-966806841816}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VizhTalk.Learning.Infrastructure", "VizhTalk.Learning.Infrastructure\VizhTalk.Learning.Infrastructure.csproj", "{1495DF56-D227-4D68-86CA-0249D886C07C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "BuildingBlocks", "BuildingBlocks", "{45B6CABB-4197-4C70-BF3F-F3AE7C8CB380}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VizhTalk.BuildingBlocks.Application", "VizhTalk.BuildingBlocks.Application\VizhTalk.BuildingBlocks.Application.csproj", "{EBFE2D6C-BA5B-460E-B57C-A8295BBCD727}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VizhTalk.BuildingBlocks.Domain", "VizhTalk.BuildingBlocks.Domain\VizhTalk.BuildingBlocks.Domain.csproj", "{22CD7011-A8DB-4DB6-8597-1601E7EF8725}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VizhTalk.BuildingBlocks.Infrastructure", "VizhTalk.BuildingBlocks.Infrastructure\VizhTalk.BuildingBlocks.Infrastructure.csproj", "{B2014B29-FE4E-4B59-829E-2AAC38B039AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VizhTalk.Grpc.Learning", "VizhTalk.Services.Grpc.Learning\VizhTalk.Grpc.Learning.csproj", "{A3F0C4E9-3957-4B25-8AB5-6751ECB01F39}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VizhTalk.Learning.Presentation", "VizhTalk.Learning.Presentation\VizhTalk.Learning.Presentation.csproj", "{8327C994-F1D5-493B-8EF7-B60EB888E92A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VizhTalk.Gateway.Web.BFF", "VizhTalk.Gateway.BFF\VizhTalk.Gateway.Web.BFF.csproj", "{21DDD623-E75A-4D16-894B-2DEA0CA5C073}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B294A620-DADE-4D31-9BF5-C1CC5D7E29EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B294A620-DADE-4D31-9BF5-C1CC5D7E29EB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B294A620-DADE-4D31-9BF5-C1CC5D7E29EB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B294A620-DADE-4D31-9BF5-C1CC5D7E29EB}.Release|Any CPU.Build.0 = Release|Any CPU
{B5065F1D-F391-4511-8697-966806841816}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B5065F1D-F391-4511-8697-966806841816}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B5065F1D-F391-4511-8697-966806841816}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B5065F1D-F391-4511-8697-966806841816}.Release|Any CPU.Build.0 = Release|Any CPU
{1495DF56-D227-4D68-86CA-0249D886C07C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1495DF56-D227-4D68-86CA-0249D886C07C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1495DF56-D227-4D68-86CA-0249D886C07C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1495DF56-D227-4D68-86CA-0249D886C07C}.Release|Any CPU.Build.0 = Release|Any CPU
{EBFE2D6C-BA5B-460E-B57C-A8295BBCD727}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EBFE2D6C-BA5B-460E-B57C-A8295BBCD727}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EBFE2D6C-BA5B-460E-B57C-A8295BBCD727}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EBFE2D6C-BA5B-460E-B57C-A8295BBCD727}.Release|Any CPU.Build.0 = Release|Any CPU
{22CD7011-A8DB-4DB6-8597-1601E7EF8725}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{22CD7011-A8DB-4DB6-8597-1601E7EF8725}.Debug|Any CPU.Build.0 = Debug|Any CPU
{22CD7011-A8DB-4DB6-8597-1601E7EF8725}.Release|Any CPU.ActiveCfg = Release|Any CPU
{22CD7011-A8DB-4DB6-8597-1601E7EF8725}.Release|Any CPU.Build.0 = Release|Any CPU
{B2014B29-FE4E-4B59-829E-2AAC38B039AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2014B29-FE4E-4B59-829E-2AAC38B039AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2014B29-FE4E-4B59-829E-2AAC38B039AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2014B29-FE4E-4B59-829E-2AAC38B039AA}.Release|Any CPU.Build.0 = Release|Any CPU
{A3F0C4E9-3957-4B25-8AB5-6751ECB01F39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A3F0C4E9-3957-4B25-8AB5-6751ECB01F39}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A3F0C4E9-3957-4B25-8AB5-6751ECB01F39}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A3F0C4E9-3957-4B25-8AB5-6751ECB01F39}.Release|Any CPU.Build.0 = Release|Any CPU
{8327C994-F1D5-493B-8EF7-B60EB888E92A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8327C994-F1D5-493B-8EF7-B60EB888E92A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8327C994-F1D5-493B-8EF7-B60EB888E92A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8327C994-F1D5-493B-8EF7-B60EB888E92A}.Release|Any CPU.Build.0 = Release|Any CPU
{21DDD623-E75A-4D16-894B-2DEA0CA5C073}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{21DDD623-E75A-4D16-894B-2DEA0CA5C073}.Debug|Any CPU.Build.0 = Debug|Any CPU
{21DDD623-E75A-4D16-894B-2DEA0CA5C073}.Release|Any CPU.ActiveCfg = Release|Any CPU
{21DDD623-E75A-4D16-894B-2DEA0CA5C073}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{AC8EE492-AB63-4799-946A-98F819478EB2} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{E507BEFF-5CB6-43BA-B95A-B6975840B043} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{1305E6AB-69E5-47BD-B03C-694832C4D46A} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{35CC083E-B7AE-4495-A95A-68C0C0F9A2F7} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{9E64DF44-8256-40C8-8AFA-B841CE80DD0E} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{26FDAD4D-55F4-445B-BE66-65193D21D14D} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{D74A2A69-224B-4498-8E99-1BC086C594CB} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{75826056-3765-4A48-9827-02ED1F27E40D} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{16D91427-4F51-48CD-A2F2-6CC1B9A9D8A5} = {F1386DBF-90B7-4285-93F2-FD60DFBAD600}
{B294A620-DADE-4D31-9BF5-C1CC5D7E29EB} = {1305E6AB-69E5-47BD-B03C-694832C4D46A}
{B5065F1D-F391-4511-8697-966806841816} = {1305E6AB-69E5-47BD-B03C-694832C4D46A}
{1495DF56-D227-4D68-86CA-0249D886C07C} = {1305E6AB-69E5-47BD-B03C-694832C4D46A}
{45B6CABB-4197-4C70-BF3F-F3AE7C8CB380} = {5B01E22E-8A79-4C40-A0B7-97D4EC2D120E}
{EBFE2D6C-BA5B-460E-B57C-A8295BBCD727} = {45B6CABB-4197-4C70-BF3F-F3AE7C8CB380}
{22CD7011-A8DB-4DB6-8597-1601E7EF8725} = {45B6CABB-4197-4C70-BF3F-F3AE7C8CB380}
{B2014B29-FE4E-4B59-829E-2AAC38B039AA} = {45B6CABB-4197-4C70-BF3F-F3AE7C8CB380}
{A3F0C4E9-3957-4B25-8AB5-6751ECB01F39} = {16D91427-4F51-48CD-A2F2-6CC1B9A9D8A5}
{8327C994-F1D5-493B-8EF7-B60EB888E92A} = {1305E6AB-69E5-47BD-B03C-694832C4D46A}
{21DDD623-E75A-4D16-894B-2DEA0CA5C073} = {FCA7DC48-FD9C-43FA-AC1B-464049337778}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9BD8917D-0A5E-401B-B153-400D610B23E4}
EndGlobalSection
EndGlobal