From 9733ec44fbac2a55760f39cc35d651aaa9efc314 Mon Sep 17 00:00:00 2001 From: Yami An Date: Mon, 28 Aug 2023 00:34:37 +0700 Subject: [PATCH] 230828 --- .dockerignore | 25 +++++++ .gitignore | 1 + CleanArchitecture.sln | 65 ++++++++++++++++++ .../Abstractions/Messaging/ICommand.cs | 7 ++ .../Abstractions/Messaging/ICommandHandler.cs | 7 ++ .../Abstractions/Messaging/IQuery.cs | 7 ++ .../Abstractions/Messaging/IQueryHandler.cs | 7 ++ Core/Application/Application.csproj | 19 ++++++ Core/Application/AssemblyReference.cs | 5 ++ .../Behaviors/ValidationBehavior.cs | 34 ++++++++++ .../Exceptions/ValidationException.cs | 10 +++ .../CreateWebinar/CreateWebinarCommand.cs | 5 ++ .../CreateWebinarCommandHandler.cs | 27 ++++++++ .../CreateWebinarCommandValidator.cs | 12 ++++ .../CreateWebinar/CreateWebinarRequest.cs | 3 + .../GetWebinarById/GetWebinarByIdQuery.cs | 5 ++ .../GetWebinarById/GetWebinarQueryHandler.cs | 24 +++++++ .../Queries/GetWebinarById/WebinarResponse.cs | 3 + Core/Domain/Abstractions/IUnitOfWork.cs | 6 ++ .../Domain/Abstractions/IWebinarRepository.cs | 8 +++ Core/Domain/AssemblyReference.cs | 5 ++ Core/Domain/Domain.csproj | 9 +++ Core/Domain/Entities/Webinar.cs | 19 ++++++ .../Exceptions/Base/BadRequestException.cs | 8 +++ .../Exceptions/Base/NotFoundException.cs | 8 +++ .../Exceptions/WebinarNotFoundException.cs | 10 +++ Core/Domain/Primitives/Entity.cs | 12 ++++ .../Infrastructure/ApplicationDbContext.cs | 13 ++++ External/Infrastructure/AssemblyReference.cs | 5 ++ .../Configurations/WebinarConfiguration.cs | 16 +++++ External/Infrastructure/Infrastructure.csproj | 17 +++++ ...0827152248_clearn_architecture.Designer.cs | 48 +++++++++++++ .../20230827152248_clearn_architecture.cs | 35 ++++++++++ .../ApplicationDbContextModelSnapshot.cs | 45 ++++++++++++ .../Repositories/WebinarRepository.cs | 13 ++++ External/Presentation/AssemblyReference.cs | 8 +++ .../Presentation/Controllers/ApiController.cs | 20 ++++++ .../Controllers/WebinarsController.cs | 50 ++++++++++++++ External/Presentation/Presentation.csproj | 23 +++++++ External/Presentation/Presentation.xml | 44 ++++++++++++ Web/AssemblyReference.cs | 5 ++ Web/Dockerfile | 22 ++++++ .../ExceptionHandlingMiddleware.cs | 58 ++++++++++++++++ Web/Program.cs | 27 ++++++++ Web/Properties/launchSettings.json | 47 +++++++++++++ Web/Startup.cs | 68 +++++++++++++++++++ Web/Web.csproj | 27 ++++++++ Web/appsettings.Development.json | 11 +++ Web/appsettings.json | 12 ++++ docker-compose.dcproj | 18 +++++ docker-compose.override.yml | 13 ++++ docker-compose.yml | 26 +++++++ launchSettings.json | 11 +++ 53 files changed, 1033 insertions(+) create mode 100644 .dockerignore create mode 100644 CleanArchitecture.sln create mode 100644 Core/Application/Abstractions/Messaging/ICommand.cs create mode 100644 Core/Application/Abstractions/Messaging/ICommandHandler.cs create mode 100644 Core/Application/Abstractions/Messaging/IQuery.cs create mode 100644 Core/Application/Abstractions/Messaging/IQueryHandler.cs create mode 100644 Core/Application/Application.csproj create mode 100644 Core/Application/AssemblyReference.cs create mode 100644 Core/Application/Behaviors/ValidationBehavior.cs create mode 100644 Core/Application/Exceptions/ValidationException.cs create mode 100644 Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommand.cs create mode 100644 Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommandHandler.cs create mode 100644 Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommandValidator.cs create mode 100644 Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarRequest.cs create mode 100644 Core/Application/Webinars/Queries/GetWebinarById/GetWebinarByIdQuery.cs create mode 100644 Core/Application/Webinars/Queries/GetWebinarById/GetWebinarQueryHandler.cs create mode 100644 Core/Application/Webinars/Queries/GetWebinarById/WebinarResponse.cs create mode 100644 Core/Domain/Abstractions/IUnitOfWork.cs create mode 100644 Core/Domain/Abstractions/IWebinarRepository.cs create mode 100644 Core/Domain/AssemblyReference.cs create mode 100644 Core/Domain/Domain.csproj create mode 100644 Core/Domain/Entities/Webinar.cs create mode 100644 Core/Domain/Exceptions/Base/BadRequestException.cs create mode 100644 Core/Domain/Exceptions/Base/NotFoundException.cs create mode 100644 Core/Domain/Exceptions/WebinarNotFoundException.cs create mode 100644 Core/Domain/Primitives/Entity.cs create mode 100644 External/Infrastructure/ApplicationDbContext.cs create mode 100644 External/Infrastructure/AssemblyReference.cs create mode 100644 External/Infrastructure/Configurations/WebinarConfiguration.cs create mode 100644 External/Infrastructure/Infrastructure.csproj create mode 100644 External/Infrastructure/Migrations/20230827152248_clearn_architecture.Designer.cs create mode 100644 External/Infrastructure/Migrations/20230827152248_clearn_architecture.cs create mode 100644 External/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 External/Infrastructure/Repositories/WebinarRepository.cs create mode 100644 External/Presentation/AssemblyReference.cs create mode 100644 External/Presentation/Controllers/ApiController.cs create mode 100644 External/Presentation/Controllers/WebinarsController.cs create mode 100644 External/Presentation/Presentation.csproj create mode 100644 External/Presentation/Presentation.xml create mode 100644 Web/AssemblyReference.cs create mode 100644 Web/Dockerfile create mode 100644 Web/Middlerware/ExceptionHandlingMiddleware.cs create mode 100644 Web/Program.cs create mode 100644 Web/Properties/launchSettings.json create mode 100644 Web/Startup.cs create mode 100644 Web/Web.csproj create mode 100644 Web/appsettings.Development.json create mode 100644 Web/appsettings.json create mode 100644 docker-compose.dcproj create mode 100644 docker-compose.override.yml create mode 100644 docker-compose.yml create mode 100644 launchSettings.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3729ff0 --- /dev/null +++ b/.dockerignore @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 426d76d..3396c0d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ bld/ [Oo]bj/ [Ll]og/ [Ll]ogs/ +[Ff]iles/ # Visual Studio 2015/2017 cache/options directory .vs/ diff --git a/CleanArchitecture.sln b/CleanArchitecture.sln new file mode 100644 index 0000000..bd5789d --- /dev/null +++ b/CleanArchitecture.sln @@ -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 diff --git a/Core/Application/Abstractions/Messaging/ICommand.cs b/Core/Application/Abstractions/Messaging/ICommand.cs new file mode 100644 index 0000000..6b958d3 --- /dev/null +++ b/Core/Application/Abstractions/Messaging/ICommand.cs @@ -0,0 +1,7 @@ +using MediatR; + +namespace Application.Abstractions.Messaging; + +public interface ICommand : IRequest +{ +} diff --git a/Core/Application/Abstractions/Messaging/ICommandHandler.cs b/Core/Application/Abstractions/Messaging/ICommandHandler.cs new file mode 100644 index 0000000..1789b64 --- /dev/null +++ b/Core/Application/Abstractions/Messaging/ICommandHandler.cs @@ -0,0 +1,7 @@ +using MediatR; + +namespace Application.Abstractions.Messaging; + +public interface ICommandHandler : IRequestHandler where TCommand : ICommand +{ +} diff --git a/Core/Application/Abstractions/Messaging/IQuery.cs b/Core/Application/Abstractions/Messaging/IQuery.cs new file mode 100644 index 0000000..68eb6a3 --- /dev/null +++ b/Core/Application/Abstractions/Messaging/IQuery.cs @@ -0,0 +1,7 @@ +using MediatR; + +namespace Application.Abstractions.Messaging; + +public interface IQuery : IRequest +{ +} diff --git a/Core/Application/Abstractions/Messaging/IQueryHandler.cs b/Core/Application/Abstractions/Messaging/IQueryHandler.cs new file mode 100644 index 0000000..1b5b0a0 --- /dev/null +++ b/Core/Application/Abstractions/Messaging/IQueryHandler.cs @@ -0,0 +1,7 @@ +using MediatR; + +namespace Application.Abstractions.Messaging; + +public interface IQueryHandler : IRequestHandler where TQuery : IQuery +{ +} diff --git a/Core/Application/Application.csproj b/Core/Application/Application.csproj new file mode 100644 index 0000000..3df5a73 --- /dev/null +++ b/Core/Application/Application.csproj @@ -0,0 +1,19 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + + + diff --git a/Core/Application/AssemblyReference.cs b/Core/Application/AssemblyReference.cs new file mode 100644 index 0000000..e9504f9 --- /dev/null +++ b/Core/Application/AssemblyReference.cs @@ -0,0 +1,5 @@ +namespace Application; + +public static class AssemblyReference +{ +} diff --git a/Core/Application/Behaviors/ValidationBehavior.cs b/Core/Application/Behaviors/ValidationBehavior.cs new file mode 100644 index 0000000..7956c4c --- /dev/null +++ b/Core/Application/Behaviors/ValidationBehavior.cs @@ -0,0 +1,34 @@ +using Application.Abstractions.Messaging; +using FluentValidation; +using MediatR; + +namespace Application.Behaviors; + +public sealed class ValidationBehavior : IPipelineBehavior where TRequest : class, ICommand +{ + private readonly IEnumerable> _validators; + + public ValidationBehavior(IEnumerable> validators) => _validators = validators; + + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + if (!_validators.Any()) + { + return await next(); + } + + var context = new ValidationContext(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(); + } +} diff --git a/Core/Application/Exceptions/ValidationException.cs b/Core/Application/Exceptions/ValidationException.cs new file mode 100644 index 0000000..c6761dd --- /dev/null +++ b/Core/Application/Exceptions/ValidationException.cs @@ -0,0 +1,10 @@ +using Domain.Exceptions.Base; + +namespace Application.Exceptions; + +public sealed class ValidationException : BadRequestException +{ + public ValidationException(Dictionary errors) : base("Validation errors occurred") => Errors = errors; + + public Dictionary Errors { get; } +} diff --git a/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommand.cs b/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommand.cs new file mode 100644 index 0000000..6a83b9a --- /dev/null +++ b/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommand.cs @@ -0,0 +1,5 @@ +using Application.Abstractions.Messaging; + +namespace Application.Webinars.Commands.CreateWebinar; + +public sealed record CreateWebinarCommand(string Nane, DateTime ScheduleOn) : ICommand; diff --git a/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommandHandler.cs b/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommandHandler.cs new file mode 100644 index 0000000..96c90d1 --- /dev/null +++ b/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommandHandler.cs @@ -0,0 +1,27 @@ +using Application.Abstractions.Messaging; +using Domain.Abstractions; +using Domain.Entities; + +namespace Application.Webinars.Commands.CreateWebinar; + +public sealed record CreateWebinarCommandHandler : ICommandHandler +{ + private readonly IWebinarRepository _repository; + private readonly IUnitOfWork _unitOfWork; + + public CreateWebinarCommandHandler(IWebinarRepository repository, IUnitOfWork unitOfWork) + { + _repository = repository; + _unitOfWork = unitOfWork; + } + + public async Task 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; + } +} diff --git a/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommandValidator.cs b/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommandValidator.cs new file mode 100644 index 0000000..54e4824 --- /dev/null +++ b/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarCommandValidator.cs @@ -0,0 +1,12 @@ +using FluentValidation; + +namespace Application.Webinars.Commands.CreateWebinar; + +public sealed class CreateWebinarCommandValidator : AbstractValidator +{ + public CreateWebinarCommandValidator() + { + _ = RuleFor(x => x.Nane).NotEmpty(); + _ = RuleFor(x => x.ScheduleOn).NotEmpty(); + } +} diff --git a/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarRequest.cs b/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarRequest.cs new file mode 100644 index 0000000..0be30d7 --- /dev/null +++ b/Core/Application/Webinars/Commands/CreateWebinar/CreateWebinarRequest.cs @@ -0,0 +1,3 @@ +namespace Application.Webinars.Commands.CreateWebinar; + +public sealed record CreateWebinarRequest(string Name, DateTime ScheduleOn); diff --git a/Core/Application/Webinars/Queries/GetWebinarById/GetWebinarByIdQuery.cs b/Core/Application/Webinars/Queries/GetWebinarById/GetWebinarByIdQuery.cs new file mode 100644 index 0000000..5e645b2 --- /dev/null +++ b/Core/Application/Webinars/Queries/GetWebinarById/GetWebinarByIdQuery.cs @@ -0,0 +1,5 @@ +using Application.Abstractions.Messaging; + +namespace Application.Webinars.Queries.GetWebinarById; + +public sealed record GetWebinarByIdQuery(Guid WebinarId) : IQuery; diff --git a/Core/Application/Webinars/Queries/GetWebinarById/GetWebinarQueryHandler.cs b/Core/Application/Webinars/Queries/GetWebinarById/GetWebinarQueryHandler.cs new file mode 100644 index 0000000..b7e0761 --- /dev/null +++ b/Core/Application/Webinars/Queries/GetWebinarById/GetWebinarQueryHandler.cs @@ -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 +{ + private readonly IDbConnection _dbConnection; + + public GetWebinarQueryHandler(IDbConnection dbConnection) => _dbConnection = dbConnection; + + public async Task Handle(GetWebinarByIdQuery request, CancellationToken cancellationToken) + { + const string sql = @"SELECT * FROM ""Webinars"" WHERE ""Id"" = @WebinarId"; + var webinar = await _dbConnection.QueryFirstOrDefaultAsync(sql, new + { + request.WebinarId + }); + + return webinar is null ? throw new WebinarNotFoundException(request.WebinarId) : webinar; + } +} diff --git a/Core/Application/Webinars/Queries/GetWebinarById/WebinarResponse.cs b/Core/Application/Webinars/Queries/GetWebinarById/WebinarResponse.cs new file mode 100644 index 0000000..5029bdd --- /dev/null +++ b/Core/Application/Webinars/Queries/GetWebinarById/WebinarResponse.cs @@ -0,0 +1,3 @@ +namespace Application.Webinars.Queries.GetWebinarById; + +public sealed record WebinarResponse(Guid Id, string Name, DateTime ScheduleOn); diff --git a/Core/Domain/Abstractions/IUnitOfWork.cs b/Core/Domain/Abstractions/IUnitOfWork.cs new file mode 100644 index 0000000..04463ec --- /dev/null +++ b/Core/Domain/Abstractions/IUnitOfWork.cs @@ -0,0 +1,6 @@ +namespace Domain.Abstractions; + +public interface IUnitOfWork +{ + public Task SaveChangesAsync(CancellationToken cancellationToken = default); +} diff --git a/Core/Domain/Abstractions/IWebinarRepository.cs b/Core/Domain/Abstractions/IWebinarRepository.cs new file mode 100644 index 0000000..d4bb5cb --- /dev/null +++ b/Core/Domain/Abstractions/IWebinarRepository.cs @@ -0,0 +1,8 @@ +using Domain.Entities; + +namespace Domain.Abstractions; + +public interface IWebinarRepository +{ + public void Insert(Webinar webinar); +} diff --git a/Core/Domain/AssemblyReference.cs b/Core/Domain/AssemblyReference.cs new file mode 100644 index 0000000..ac9ec57 --- /dev/null +++ b/Core/Domain/AssemblyReference.cs @@ -0,0 +1,5 @@ +namespace Domain; + +public static class AssemblyReference +{ +} diff --git a/Core/Domain/Domain.csproj b/Core/Domain/Domain.csproj new file mode 100644 index 0000000..fe5518a --- /dev/null +++ b/Core/Domain/Domain.csproj @@ -0,0 +1,9 @@ + + + + net7.0 + enable + enable + + + diff --git a/Core/Domain/Entities/Webinar.cs b/Core/Domain/Entities/Webinar.cs new file mode 100644 index 0000000..f730370 --- /dev/null +++ b/Core/Domain/Entities/Webinar.cs @@ -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; } +} diff --git a/Core/Domain/Exceptions/Base/BadRequestException.cs b/Core/Domain/Exceptions/Base/BadRequestException.cs new file mode 100644 index 0000000..dffc763 --- /dev/null +++ b/Core/Domain/Exceptions/Base/BadRequestException.cs @@ -0,0 +1,8 @@ +namespace Domain.Exceptions.Base; + +public abstract class BadRequestException : Exception +{ + protected BadRequestException(string message) : base(message) + { + } +} diff --git a/Core/Domain/Exceptions/Base/NotFoundException.cs b/Core/Domain/Exceptions/Base/NotFoundException.cs new file mode 100644 index 0000000..7913557 --- /dev/null +++ b/Core/Domain/Exceptions/Base/NotFoundException.cs @@ -0,0 +1,8 @@ +namespace Domain.Exceptions.Base; + +public abstract class NotFoundException : Exception +{ + protected NotFoundException(string message) : base(message) + { + } +} diff --git a/Core/Domain/Exceptions/WebinarNotFoundException.cs b/Core/Domain/Exceptions/WebinarNotFoundException.cs new file mode 100644 index 0000000..5c26897 --- /dev/null +++ b/Core/Domain/Exceptions/WebinarNotFoundException.cs @@ -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.") + { + } +} diff --git a/Core/Domain/Primitives/Entity.cs b/Core/Domain/Primitives/Entity.cs new file mode 100644 index 0000000..c445a70 --- /dev/null +++ b/Core/Domain/Primitives/Entity.cs @@ -0,0 +1,12 @@ +namespace Domain.Primitives; + +public abstract class Entity +{ + protected Entity() + { + } + + protected Entity(Guid id) => Id = id; + + public Guid Id { get; set; } +} diff --git a/External/Infrastructure/ApplicationDbContext.cs b/External/Infrastructure/ApplicationDbContext.cs new file mode 100644 index 0000000..3977f54 --- /dev/null +++ b/External/Infrastructure/ApplicationDbContext.cs @@ -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); +} diff --git a/External/Infrastructure/AssemblyReference.cs b/External/Infrastructure/AssemblyReference.cs new file mode 100644 index 0000000..b77c326 --- /dev/null +++ b/External/Infrastructure/AssemblyReference.cs @@ -0,0 +1,5 @@ +namespace Infrastructure; + +public static class AssemblyReference +{ +} diff --git a/External/Infrastructure/Configurations/WebinarConfiguration.cs b/External/Infrastructure/Configurations/WebinarConfiguration.cs new file mode 100644 index 0000000..2b8a647 --- /dev/null +++ b/External/Infrastructure/Configurations/WebinarConfiguration.cs @@ -0,0 +1,16 @@ +using Domain.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace Infrastructure.Configurations; + +public sealed class WebinarConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("Webinars"); + builder.HasKey(w => w.Id); + builder.Property(w => w.Name).HasMaxLength(100); + builder.Property(w => w.ScheduleOn).IsRequired(); + } +} diff --git a/External/Infrastructure/Infrastructure.csproj b/External/Infrastructure/Infrastructure.csproj new file mode 100644 index 0000000..f82d012 --- /dev/null +++ b/External/Infrastructure/Infrastructure.csproj @@ -0,0 +1,17 @@ + + + + net7.0 + enable + enable + + + + + + + + + + + diff --git a/External/Infrastructure/Migrations/20230827152248_clearn_architecture.Designer.cs b/External/Infrastructure/Migrations/20230827152248_clearn_architecture.Designer.cs new file mode 100644 index 0000000..a21e01b --- /dev/null +++ b/External/Infrastructure/Migrations/20230827152248_clearn_architecture.Designer.cs @@ -0,0 +1,48 @@ +// +using System; +using Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20230827152248_clearn_architecture")] + partial class clearn_architecture + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Webinar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ScheduleOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Webinars", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/External/Infrastructure/Migrations/20230827152248_clearn_architecture.cs b/External/Infrastructure/Migrations/20230827152248_clearn_architecture.cs new file mode 100644 index 0000000..163340e --- /dev/null +++ b/External/Infrastructure/Migrations/20230827152248_clearn_architecture.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Infrastructure.Migrations +{ + /// + public partial class clearn_architecture : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Webinars", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(100)", maxLength: 100, nullable: true), + ScheduleOn = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Webinars", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Webinars"); + } + } +} diff --git a/External/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs b/External/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..08fb435 --- /dev/null +++ b/External/Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,45 @@ +// +using System; +using Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Infrastructure.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Domain.Entities.Webinar", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("Name") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("ScheduleOn") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.ToTable("Webinars", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/External/Infrastructure/Repositories/WebinarRepository.cs b/External/Infrastructure/Repositories/WebinarRepository.cs new file mode 100644 index 0000000..dd0fe50 --- /dev/null +++ b/External/Infrastructure/Repositories/WebinarRepository.cs @@ -0,0 +1,13 @@ +using Domain.Abstractions; +using Domain.Entities; + +namespace Infrastructure.Repositories; + +public sealed class WebinarRepository : IWebinarRepository +{ + private readonly ApplicationDbContext _dbContext; + + public WebinarRepository(ApplicationDbContext dbContext) => _dbContext = dbContext; + + public void Insert(Webinar webinar) => _dbContext.Set().Add(webinar); +} diff --git a/External/Presentation/AssemblyReference.cs b/External/Presentation/AssemblyReference.cs new file mode 100644 index 0000000..837d15c --- /dev/null +++ b/External/Presentation/AssemblyReference.cs @@ -0,0 +1,8 @@ +namespace Presentation; + +/// +/// A reference class for the presentation assembly. +/// +public static class AssemblyReference +{ +} diff --git a/External/Presentation/Controllers/ApiController.cs b/External/Presentation/Controllers/ApiController.cs new file mode 100644 index 0000000..1157d89 --- /dev/null +++ b/External/Presentation/Controllers/ApiController.cs @@ -0,0 +1,20 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace Presentation.Controllers; + +/// +/// Represents the base API controller. +/// +[ApiController] +[Route("api/[controller]")] +public abstract class ApiController : ControllerBase +{ + private ISender? _sender; + + /// + /// Gets the sender. + /// + protected ISender? Sender => _sender ??= HttpContext.RequestServices.GetService(); +} diff --git a/External/Presentation/Controllers/WebinarsController.cs b/External/Presentation/Controllers/WebinarsController.cs new file mode 100644 index 0000000..34a0556 --- /dev/null +++ b/External/Presentation/Controllers/WebinarsController.cs @@ -0,0 +1,50 @@ +using Application.Webinars.Commands.CreateWebinar; +using Application.Webinars.Queries.GetWebinarById; +using Mapster; +using Microsoft.AspNetCore.Mvc; +using static Microsoft.AspNetCore.Http.StatusCodes; + +namespace Presentation.Controllers; + +/// +/// Represents the webinars controller. +/// +public sealed class WebinarsController : ApiController +{ + /// + /// Gets the webinar with the specified identifier, if it exists. + /// + /// The webinar identifier. + /// The cancellation token. + /// The webinar with the specified identifier, if it exists. + [HttpGet("{webinarId:guid}")] + [ProducesResponseType(typeof(WebinarResponse), Status200OK)] + [ProducesResponseType(Status404NotFound)] + public async ValueTask GetWebinar(Guid webinarId, CancellationToken cancellationToken) + { + var query = new GetWebinarByIdQuery(webinarId); + var webinar = await Sender!.Send(query, cancellationToken); + + return Ok(webinar); + } + + /// + /// Creates a new webinar based on the specified request. + /// + /// The create webinar request. + /// The cancellation token. + /// The identifier of the newly created webinar. + [HttpPost] + [ProducesResponseType(typeof(Guid), Status200OK)] + [ProducesResponseType(Status404NotFound)] + public async Task CreateWebinar([FromBody] CreateWebinarRequest request, CancellationToken cancellationToken) + { + var command = request.Adapt(); + var webinarId = await Sender!.Send(command, cancellationToken); + + return CreatedAtAction(nameof(GetWebinar), new + { + webinarId + }, webinarId); + } +} diff --git a/External/Presentation/Presentation.csproj b/External/Presentation/Presentation.csproj new file mode 100644 index 0000000..c5e27be --- /dev/null +++ b/External/Presentation/Presentation.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + + + Presentation.xml + + + + + + + + + + + + + diff --git a/External/Presentation/Presentation.xml b/External/Presentation/Presentation.xml new file mode 100644 index 0000000..7c7ea5f --- /dev/null +++ b/External/Presentation/Presentation.xml @@ -0,0 +1,44 @@ + + + + Presentation + + + + + A reference class for the presentation assembly. + + + + + Represents the base API controller. + + + + + Gets the sender. + + + + + Represents the webinars controller. + + + + + Gets the webinar with the specified identifier, if it exists. + + The webinar identifier. + The cancellation token. + The webinar with the specified identifier, if it exists. + + + + Creates a new webinar based on the specified request. + + The create webinar request. + The cancellation token. + The identifier of the newly created webinar. + + + diff --git a/Web/AssemblyReference.cs b/Web/AssemblyReference.cs new file mode 100644 index 0000000..f8f7760 --- /dev/null +++ b/Web/AssemblyReference.cs @@ -0,0 +1,5 @@ +namespace Web; + +public static class AssemblyReference +{ +} diff --git a/Web/Dockerfile b/Web/Dockerfile new file mode 100644 index 0000000..cbca2f4 --- /dev/null +++ b/Web/Dockerfile @@ -0,0 +1,22 @@ +#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. + +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base +WORKDIR /app +EXPOSE 80 +EXPOSE 443 + +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build +WORKDIR /src +COPY ["Web/Web.csproj", "Web/"] +RUN dotnet restore "Web/Web.csproj" +COPY . . +WORKDIR "/src/Web" +RUN dotnet build "Web.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Web.csproj" -c Release -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "Web.dll"] \ No newline at end of file diff --git a/Web/Middlerware/ExceptionHandlingMiddleware.cs b/Web/Middlerware/ExceptionHandlingMiddleware.cs new file mode 100644 index 0000000..adbb1c3 --- /dev/null +++ b/Web/Middlerware/ExceptionHandlingMiddleware.cs @@ -0,0 +1,58 @@ +using Application.Exceptions; +using Domain.Exceptions.Base; +using YANLib; +using static Microsoft.AspNetCore.Http.StatusCodes; + +namespace Web.Middlerware; + +public sealed class ExceptionHandlingMiddleware : IMiddleware +{ + private readonly ILogger _logger; + + public ExceptionHandlingMiddleware(ILogger logger) => _logger = logger; + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + try + { + await next(context); + } + catch (Exception ex) + { + _logger.LogError(ex, "Exception: {Message}", ex.Message); + await HandleExceptionAsync(context, ex); + } + } + + private static async Task HandleExceptionAsync(HttpContext httpContent, Exception exception) + { + httpContent.Response.ContentType = "application/json"; + + httpContent.Response.StatusCode = exception switch + { + BadRequestException or ValidationException => Status400BadRequest, + NotFoundException => Status404NotFound, + _ => Status500InternalServerError + }; + + var errors = Array.Empty(); + + if (exception is ValidationException validationException) + { + errors = validationException.Errors + .SelectMany(p => p.Value, (p, value) => new ApiError(p.Key, value)) + .ToArray(); + } + + var response = new + { + status = httpContent.Response.StatusCode, + message = exception.Message, + errors + }; + + await httpContent.Response.WriteAsync(response.CamelSerialize()); + } + + private record ApiError(string PropertyName, string ErrorMessage); +} diff --git a/Web/Program.cs b/Web/Program.cs new file mode 100644 index 0000000..e144d49 --- /dev/null +++ b/Web/Program.cs @@ -0,0 +1,27 @@ +using Infrastructure; +using Microsoft.EntityFrameworkCore; +using static Microsoft.Extensions.Hosting.Host; + +namespace Web; + +public class Program +{ + public static async Task Main(string[] args) + { + var webHost = CreateHostBuilder(args).Build(); + + await ApplyMigrations(webHost.Services); + await webHost.RunAsync(); + } + + private static IHostBuilder CreateHostBuilder(string[] args) => CreateDefaultBuilder(args).ConfigureWebHostDefaults(w => w.UseStartup()); + + private static async Task ApplyMigrations(IServiceProvider serviceProvider) + { + using var scope = serviceProvider.CreateScope(); + + await using var dbContext = scope.ServiceProvider.GetRequiredService(); + + await dbContext.Database.MigrateAsync(); + } +} \ No newline at end of file diff --git a/Web/Properties/launchSettings.json b/Web/Properties/launchSettings.json new file mode 100644 index 0000000..3990c19 --- /dev/null +++ b/Web/Properties/launchSettings.json @@ -0,0 +1,47 @@ +{ + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "http://localhost:5194" + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7021;http://localhost:5194" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", + "environmentVariables": { + "ASPNETCORE_URLS": "https://+:443;http://+:80" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:16676", + "sslPort": 44318 + } + } +} \ No newline at end of file diff --git a/Web/Startup.cs b/Web/Startup.cs new file mode 100644 index 0000000..2cdd780 --- /dev/null +++ b/Web/Startup.cs @@ -0,0 +1,68 @@ +using Application.Behaviors; +using Domain.Abstractions; +using FluentValidation; +using Infrastructure; +using Infrastructure.Repositories; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.OpenApi.Models; +using System.Data; +using Web.Middlerware; +using static System.AppContext; +using static System.IO.Path; + +namespace Web; + +public class Startup +{ + public Startup(IConfiguration configuration) => Configuration = configuration; + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + var presentationAssembly = typeof(Presentation.AssemblyReference).Assembly; + + _ = services.AddControllers().AddApplicationPart(presentationAssembly); + + var applicationAssembly = typeof(Application.AssemblyReference).Assembly; + + _ = services.AddMediatR(applicationAssembly); + _ = services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); + _ = services.AddValidatorsFromAssembly(applicationAssembly); + _ = services.AddSwaggerGen(c => + { + var presentationDocumentationFile = $"{presentationAssembly.GetName().Name}.xml"; + var presentationDocumentationFilePath = Combine(BaseDirectory, presentationDocumentationFile); + + c.IncludeXmlComments(presentationDocumentationFilePath); + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "Web", + Version = "v1" + }); + }); + + _ = services.AddDbContext(b => b.UseNpgsql(Configuration.GetConnectionString("Default"))); + _ = services.AddScoped(); + _ = services.AddScoped(f => f.GetRequiredService()); + _ = services.AddScoped(f => f.GetRequiredService().Database.GetDbConnection()); + _ = services.AddTransient(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + _ = app.UseDeveloperExceptionPage(); + _ = app.UseSwagger(); + _ = app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Web v1")); + } + + _ = app.UseMiddleware(); + _ = app.UseHttpsRedirection(); + _ = app.UseRouting(); + _ = app.UseAuthorization(); + _ = app.UseEndpoints(e => e.MapControllers()); + } +} diff --git a/Web/Web.csproj b/Web/Web.csproj new file mode 100644 index 0000000..21de7a7 --- /dev/null +++ b/Web/Web.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + 7497d66c-4140-4e9e-b044-2cd6a8b72e36 + Linux + ..\docker-compose.dcproj + + + + + + + + + + + + + + + + + + diff --git a/Web/appsettings.Development.json b/Web/appsettings.Development.json new file mode 100644 index 0000000..2798f2c --- /dev/null +++ b/Web/appsettings.Development.json @@ -0,0 +1,11 @@ +{ + "ConnectionStrings": { + "Default": "Host=localhost;Port=5432;Database=webinar;User Id=admin;Password=admin123@" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Web/appsettings.json b/Web/appsettings.json new file mode 100644 index 0000000..178316a --- /dev/null +++ b/Web/appsettings.json @@ -0,0 +1,12 @@ +{ + "ConnectionStrings": { + "Default": "Host=localhost;Port=5432;Database=webinar;User Id=admin;Password=admin123@" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/docker-compose.dcproj b/docker-compose.dcproj new file mode 100644 index 0000000..b341d18 --- /dev/null +++ b/docker-compose.dcproj @@ -0,0 +1,18 @@ + + + + 2.1 + Linux + e675562a-ce6a-4d94-b8de-8913cdd95f75 + LaunchBrowser + {Scheme}://localhost:{ServicePort} + web + + + + docker-compose.yml + + + + + \ No newline at end of file diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 0000000..36831de --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,13 @@ +version: '3.4' + +services: + clean_architecture.web: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=https://+:443;http://+:80 + ports: + - "80" + - "443" + volumes: + - ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/usersecrets:ro + - ${APPDATA}/ASP.NET/Https:/root/.aspnet/https:ro \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..04eec96 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,26 @@ +version: '3.4' + +services: + clean_architecture.web: + image: ${DOCKER_REGISTRY-}clean_architecture_web + container_name: clean_architecture_web + build: + context: . + dockerfile: Web/Dockerfile + ports: + - 5000:80 + - 5001:443 + depends_on: + - clean_architecture.db + + clean_architecture.db: + image: postgres + container_name: clean_architecture_db + environment: + - POSTGRES_DB=webinar + - POSTGRES_USER=admin + - POSTGRES_PASSWORD=admin123@ + volumes: + - ./files/db:/var/lib/postgresql/data + ports: + - 5432:5432 diff --git a/launchSettings.json b/launchSettings.json new file mode 100644 index 0000000..ec1d0c2 --- /dev/null +++ b/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "Docker Compose": { + "commandName": "DockerCompose", + "commandVersion": "1.0", + "serviceActions": { + "web": "StartDebugging" + } + } + } +} \ No newline at end of file