Skip to content

Commit

Permalink
230828
Browse files Browse the repository at this point in the history
  • Loading branch information
Tynab committed Aug 27, 2023
1 parent 3dbff8d commit 9733ec4
Show file tree
Hide file tree
Showing 53 changed files with 1,033 additions and 0 deletions.
25 changes: 25 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ bld/
[Oo]bj/
[Ll]og/
[Ll]ogs/
[Ff]iles/

# Visual Studio 2015/2017 cache/options directory
.vs/
Expand Down
65 changes: 65 additions & 0 deletions CleanArchitecture.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34018.315
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{D16EAB76-803C-4AAE-A02B-F8DA4A312C8E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External", "External", "{A9396A9F-1A77-468D-B101-7FA676690E3C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Web", "Web\Web.csproj", "{D8EAF347-3038-4C36-858F-B4A458E1F558}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Application", "Core\Application\Application.csproj", "{D99001C0-9F53-4736-B78F-A179E9582C25}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Domain", "Core\Domain\Domain.csproj", "{3AAC992A-8A98-496F-816B-4BD640A066BE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Infrastructure", "External\Infrastructure\Infrastructure.csproj", "{1C77451F-8638-4AFA-A6FB-DA7EFB9FB7F6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Presentation", "External\Presentation\Presentation.csproj", "{F97D94D0-0857-4F87-8B48-D2D1EA94B6FE}"
EndProject
Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{E675562A-CE6A-4D94-B8DE-8913CDD95F75}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D8EAF347-3038-4C36-858F-B4A458E1F558}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8EAF347-3038-4C36-858F-B4A458E1F558}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8EAF347-3038-4C36-858F-B4A458E1F558}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8EAF347-3038-4C36-858F-B4A458E1F558}.Release|Any CPU.Build.0 = Release|Any CPU
{D99001C0-9F53-4736-B78F-A179E9582C25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D99001C0-9F53-4736-B78F-A179E9582C25}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D99001C0-9F53-4736-B78F-A179E9582C25}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D99001C0-9F53-4736-B78F-A179E9582C25}.Release|Any CPU.Build.0 = Release|Any CPU
{3AAC992A-8A98-496F-816B-4BD640A066BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AAC992A-8A98-496F-816B-4BD640A066BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AAC992A-8A98-496F-816B-4BD640A066BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AAC992A-8A98-496F-816B-4BD640A066BE}.Release|Any CPU.Build.0 = Release|Any CPU
{1C77451F-8638-4AFA-A6FB-DA7EFB9FB7F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C77451F-8638-4AFA-A6FB-DA7EFB9FB7F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C77451F-8638-4AFA-A6FB-DA7EFB9FB7F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C77451F-8638-4AFA-A6FB-DA7EFB9FB7F6}.Release|Any CPU.Build.0 = Release|Any CPU
{F97D94D0-0857-4F87-8B48-D2D1EA94B6FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F97D94D0-0857-4F87-8B48-D2D1EA94B6FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F97D94D0-0857-4F87-8B48-D2D1EA94B6FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F97D94D0-0857-4F87-8B48-D2D1EA94B6FE}.Release|Any CPU.Build.0 = Release|Any CPU
{E675562A-CE6A-4D94-B8DE-8913CDD95F75}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E675562A-CE6A-4D94-B8DE-8913CDD95F75}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E675562A-CE6A-4D94-B8DE-8913CDD95F75}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E675562A-CE6A-4D94-B8DE-8913CDD95F75}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{D99001C0-9F53-4736-B78F-A179E9582C25} = {D16EAB76-803C-4AAE-A02B-F8DA4A312C8E}
{3AAC992A-8A98-496F-816B-4BD640A066BE} = {D16EAB76-803C-4AAE-A02B-F8DA4A312C8E}
{1C77451F-8638-4AFA-A6FB-DA7EFB9FB7F6} = {A9396A9F-1A77-468D-B101-7FA676690E3C}
{F97D94D0-0857-4F87-8B48-D2D1EA94B6FE} = {A9396A9F-1A77-468D-B101-7FA676690E3C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {ACC5D0C9-63B3-48C2-A80C-EB593F12E2E9}
EndGlobalSection
EndGlobal
7 changes: 7 additions & 0 deletions Core/Application/Abstractions/Messaging/ICommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using MediatR;

namespace Application.Abstractions.Messaging;

public interface ICommand<out TResponse> : IRequest<TResponse>
{
}
7 changes: 7 additions & 0 deletions Core/Application/Abstractions/Messaging/ICommandHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using MediatR;

namespace Application.Abstractions.Messaging;

public interface ICommandHandler<in TCommand, TResponse> : IRequestHandler<TCommand, TResponse> where TCommand : ICommand<TResponse>
{
}
7 changes: 7 additions & 0 deletions Core/Application/Abstractions/Messaging/IQuery.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using MediatR;

namespace Application.Abstractions.Messaging;

public interface IQuery<out TResponse> : IRequest<TResponse>
{
}
7 changes: 7 additions & 0 deletions Core/Application/Abstractions/Messaging/IQueryHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using MediatR;

namespace Application.Abstractions.Messaging;

public interface IQueryHandler<in TQuery, TResponse> : IRequestHandler<TQuery, TResponse> where TQuery : IQuery<TResponse>
{
}
19 changes: 19 additions & 0 deletions Core/Application/Application.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Dapper" Version="2.0.151" />
<PackageReference Include="FluentValidation" Version="11.7.1" />
<PackageReference Include="MediatR" Version="11.0.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>

</Project>
5 changes: 5 additions & 0 deletions Core/Application/AssemblyReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Application;

public static class AssemblyReference
{
}
34 changes: 34 additions & 0 deletions Core/Application/Behaviors/ValidationBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Application.Abstractions.Messaging;
using FluentValidation;
using MediatR;

namespace Application.Behaviors;

public sealed class ValidationBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : class, ICommand<TResponse>
{
private readonly IEnumerable<IValidator<TRequest>> _validators;

public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators) => _validators = validators;

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
if (!_validators.Any())
{
return await next();
}

var context = new ValidationContext<TRequest>(request);
var errorsDictionary = _validators
.Select(x => x.Validate(context))
.SelectMany(x => x.Errors)
.Where(x => x is not null)
.GroupBy(x => x.PropertyName, x => x.ErrorMessage, (propertyName, errorMessage) => new
{
Key = propertyName,
Values = errorMessage.Distinct().ToArray()
})
.ToDictionary(x => x.Key, x => x.Values);

return errorsDictionary.Any() ? throw new Exceptions.ValidationException(errorsDictionary) : await next();
}
}
10 changes: 10 additions & 0 deletions Core/Application/Exceptions/ValidationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Domain.Exceptions.Base;

namespace Application.Exceptions;

public sealed class ValidationException : BadRequestException
{
public ValidationException(Dictionary<string, string[]> errors) : base("Validation errors occurred") => Errors = errors;

public Dictionary<string, string[]> Errors { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Application.Abstractions.Messaging;

namespace Application.Webinars.Commands.CreateWebinar;

public sealed record CreateWebinarCommand(string Nane, DateTime ScheduleOn) : ICommand<Guid>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Application.Abstractions.Messaging;
using Domain.Abstractions;
using Domain.Entities;

namespace Application.Webinars.Commands.CreateWebinar;

public sealed record CreateWebinarCommandHandler : ICommandHandler<CreateWebinarCommand, Guid>
{
private readonly IWebinarRepository _repository;
private readonly IUnitOfWork _unitOfWork;

public CreateWebinarCommandHandler(IWebinarRepository repository, IUnitOfWork unitOfWork)
{
_repository = repository;
_unitOfWork = unitOfWork;
}

public async Task<Guid> Handle(CreateWebinarCommand request, CancellationToken cancellationToken)
{
var webinar = new Webinar(Guid.NewGuid(), request.Nane, request.ScheduleOn);

_repository.Insert(webinar);
_ = await _unitOfWork.SaveChangesAsync(cancellationToken);

return webinar.Id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using FluentValidation;

namespace Application.Webinars.Commands.CreateWebinar;

public sealed class CreateWebinarCommandValidator : AbstractValidator<CreateWebinarCommand>
{
public CreateWebinarCommandValidator()
{
_ = RuleFor(x => x.Nane).NotEmpty();
_ = RuleFor(x => x.ScheduleOn).NotEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Application.Webinars.Commands.CreateWebinar;

public sealed record CreateWebinarRequest(string Name, DateTime ScheduleOn);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using Application.Abstractions.Messaging;

namespace Application.Webinars.Queries.GetWebinarById;

public sealed record GetWebinarByIdQuery(Guid WebinarId) : IQuery<WebinarResponse>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Application.Abstractions.Messaging;
using Dapper;
using Domain.Exceptions;
using System.Data;

namespace Application.Webinars.Queries.GetWebinarById;

public sealed class GetWebinarQueryHandler : IQueryHandler<GetWebinarByIdQuery, WebinarResponse>
{
private readonly IDbConnection _dbConnection;

public GetWebinarQueryHandler(IDbConnection dbConnection) => _dbConnection = dbConnection;

public async Task<WebinarResponse> Handle(GetWebinarByIdQuery request, CancellationToken cancellationToken)
{
const string sql = @"SELECT * FROM ""Webinars"" WHERE ""Id"" = @WebinarId";
var webinar = await _dbConnection.QueryFirstOrDefaultAsync<WebinarResponse>(sql, new
{
request.WebinarId
});

return webinar is null ? throw new WebinarNotFoundException(request.WebinarId) : webinar;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Application.Webinars.Queries.GetWebinarById;

public sealed record WebinarResponse(Guid Id, string Name, DateTime ScheduleOn);
6 changes: 6 additions & 0 deletions Core/Domain/Abstractions/IUnitOfWork.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Domain.Abstractions;

public interface IUnitOfWork
{
public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default);
}
8 changes: 8 additions & 0 deletions Core/Domain/Abstractions/IWebinarRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Domain.Entities;

namespace Domain.Abstractions;

public interface IWebinarRepository
{
public void Insert(Webinar webinar);
}
5 changes: 5 additions & 0 deletions Core/Domain/AssemblyReference.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Domain;

public static class AssemblyReference
{
}
9 changes: 9 additions & 0 deletions Core/Domain/Domain.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
19 changes: 19 additions & 0 deletions Core/Domain/Entities/Webinar.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Domain.Primitives;

namespace Domain.Entities;

public sealed class Webinar : Entity
{
public Webinar()
{
}

public Webinar(Guid id, string name, DateTime scheduleOn) : base(id)
{
Name = name;
ScheduleOn = scheduleOn;
}

public string? Name { get; set; }
public DateTime ScheduleOn { get; set; }
}
8 changes: 8 additions & 0 deletions Core/Domain/Exceptions/Base/BadRequestException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Domain.Exceptions.Base;

public abstract class BadRequestException : Exception
{
protected BadRequestException(string message) : base(message)
{
}
}
8 changes: 8 additions & 0 deletions Core/Domain/Exceptions/Base/NotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Domain.Exceptions.Base;

public abstract class NotFoundException : Exception
{
protected NotFoundException(string message) : base(message)
{
}
}
10 changes: 10 additions & 0 deletions Core/Domain/Exceptions/WebinarNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Domain.Exceptions.Base;

namespace Domain.Exceptions;

public sealed class WebinarNotFoundException : NotFoundException
{
public WebinarNotFoundException(Guid webinarId) : base($"The webinar with the identifier {webinarId} was not found.")
{
}
}
12 changes: 12 additions & 0 deletions Core/Domain/Primitives/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Domain.Primitives;

public abstract class Entity
{
protected Entity()
{
}

protected Entity(Guid id) => Id = id;

public Guid Id { get; set; }
}
13 changes: 13 additions & 0 deletions External/Infrastructure/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Domain.Abstractions;
using Microsoft.EntityFrameworkCore;

namespace Infrastructure;

public sealed class ApplicationDbContext : DbContext, IUnitOfWork
{
public ApplicationDbContext(DbContextOptions options) : base(options)
{
}

protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
}
Loading

0 comments on commit 9733ec4

Please sign in to comment.