diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 397089cc..a0c6435a 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -23,6 +23,15 @@ services: - "5102:80" - "5581:5001" + courseregistration.api: + environment: + - ASPNETCORE_ENVIRONMENT=Development + - ASPNETCORE_URLS=http://0.0.0.0:80 + - ConnectionStrings=Server=sql.data;Database=OpenCodeFoundation.CourseRegistrationDb;User Id=sa;Password=Pass@word + ports: + - "5103:80" + - "5582:5001" + webstatus: environment: - ASPNETCORE_ENVIRONMENT=Development @@ -30,6 +39,8 @@ services: - ConnectionStrings=Server=sql.data;Database=OpenCodeFoundation.WebStatusDb;User Id=sa;Password=Pass@word - HealthChecksUI__HealthChecks__0__Name=Enrolling HTTP Check - HealthChecksUI__HealthChecks__0__Uri=http://enrolling.api/hc + - HealthChecksUI__HealthChecks__1__Name=CourseRegistration HTTP Check + - HealthChecksUI__HealthChecks__1__Uri=http://courseregistration.api/hc ports: - "5107:80" diff --git a/docker-compose.yml b/docker-compose.yml index 0a345a6e..c49259b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,14 @@ services: depends_on: - sql.data + courseregistration.api: + image: ${REGISTRY:-eschool}/courseregistration.api:${TAG:-latest} + build: + context: . + dockerfile: src/Services/CourseRegistration/CourseRegistration.API/Dockerfile + depends_on: + - sql.data + webstatus: image: ${REGISTRY:-eschool}/webstatus:${TAG:-latest} build: diff --git a/eSchool.sln b/eSchool.sln index 02ebdc1a..b63ac41d 100644 --- a/eSchool.sln +++ b/eSchool.sln @@ -41,6 +41,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frontend.Blazor.Server", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Frontend.Blazor.Shared", "src\Web\Frontend.Blazor\Frontend.Blazor.Shared\Frontend.Blazor.Shared.csproj", "{4EB86635-CF79-4D15-909E-C41C98B0B586}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CourseRegistration", "CourseRegistration", "{F5B40B9B-057F-4D22-B34E-946F7F6E7B86}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CourseRegistration.API", "src\Services\CourseRegistration\CourseRegistration.API\CourseRegistration.API.csproj", "{09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CourseRegistration.Domain", "src\Services\CourseRegistration\CourseRegistration.Domain\CourseRegistration.Domain.csproj", "{21012592-FADD-4457-93F5-8B3F7B265ED4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CourseRegistration.FunctionalTests", "src\Services\CourseRegistration\CourseRegistration.FunctionalTests\CourseRegistration.FunctionalTests.csproj", "{D1F9863A-DEC3-42CC-86DF-E81E2570D063}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CourseRegistration.Infrastructure", "src\Services\CourseRegistration\CourseRegistration.Infrastructure\CourseRegistration.Infrastructure.csproj", "{60584837-1CE9-4EF2-899C-EF3982FAA4B0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CourseRegistration.UnitTests", "src\Services\CourseRegistration\CourseRegistration.UnitTests\CourseRegistration.UnitTests.csproj", "{2982CB50-A029-4C8E-938F-F70ADA4DF207}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -195,6 +207,66 @@ Global {4EB86635-CF79-4D15-909E-C41C98B0B586}.Release|x64.Build.0 = Release|Any CPU {4EB86635-CF79-4D15-909E-C41C98B0B586}.Release|x86.ActiveCfg = Release|Any CPU {4EB86635-CF79-4D15-909E-C41C98B0B586}.Release|x86.Build.0 = Release|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Debug|x64.ActiveCfg = Debug|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Debug|x64.Build.0 = Debug|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Debug|x86.ActiveCfg = Debug|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Debug|x86.Build.0 = Debug|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Release|Any CPU.Build.0 = Release|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Release|x64.ActiveCfg = Release|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Release|x64.Build.0 = Release|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Release|x86.ActiveCfg = Release|Any CPU + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D}.Release|x86.Build.0 = Release|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Debug|x64.ActiveCfg = Debug|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Debug|x64.Build.0 = Debug|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Debug|x86.ActiveCfg = Debug|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Debug|x86.Build.0 = Debug|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Release|Any CPU.Build.0 = Release|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Release|x64.ActiveCfg = Release|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Release|x64.Build.0 = Release|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Release|x86.ActiveCfg = Release|Any CPU + {21012592-FADD-4457-93F5-8B3F7B265ED4}.Release|x86.Build.0 = Release|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Debug|x64.Build.0 = Debug|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Debug|x86.Build.0 = Debug|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Release|Any CPU.Build.0 = Release|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Release|x64.ActiveCfg = Release|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Release|x64.Build.0 = Release|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Release|x86.ActiveCfg = Release|Any CPU + {D1F9863A-DEC3-42CC-86DF-E81E2570D063}.Release|x86.Build.0 = Release|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Debug|x64.ActiveCfg = Debug|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Debug|x64.Build.0 = Debug|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Debug|x86.ActiveCfg = Debug|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Debug|x86.Build.0 = Debug|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Release|Any CPU.Build.0 = Release|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Release|x64.ActiveCfg = Release|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Release|x64.Build.0 = Release|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Release|x86.ActiveCfg = Release|Any CPU + {60584837-1CE9-4EF2-899C-EF3982FAA4B0}.Release|x86.Build.0 = Release|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Debug|x64.ActiveCfg = Debug|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Debug|x64.Build.0 = Debug|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Debug|x86.ActiveCfg = Debug|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Debug|x86.Build.0 = Debug|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Release|Any CPU.Build.0 = Release|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Release|x64.ActiveCfg = Release|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Release|x64.Build.0 = Release|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Release|x86.ActiveCfg = Release|Any CPU + {2982CB50-A029-4C8E-938F-F70ADA4DF207}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -217,6 +289,12 @@ Global {53F4E6F9-6B91-45F9-97F9-F6EFA0EBEFCE} = {0C00A596-0FE3-4FA6-B54B-FE2BE83371EF} {3BABD4D9-56A1-4BA3-B30C-30E6765AB389} = {0C00A596-0FE3-4FA6-B54B-FE2BE83371EF} {4EB86635-CF79-4D15-909E-C41C98B0B586} = {0C00A596-0FE3-4FA6-B54B-FE2BE83371EF} + {F5B40B9B-057F-4D22-B34E-946F7F6E7B86} = {1C120673-72F4-4679-AC4C-68286E9091A5} + {09BD4566-E6F2-4FD0-A8AE-D3663BF7184D} = {F5B40B9B-057F-4D22-B34E-946F7F6E7B86} + {21012592-FADD-4457-93F5-8B3F7B265ED4} = {F5B40B9B-057F-4D22-B34E-946F7F6E7B86} + {D1F9863A-DEC3-42CC-86DF-E81E2570D063} = {F5B40B9B-057F-4D22-B34E-946F7F6E7B86} + {60584837-1CE9-4EF2-899C-EF3982FAA4B0} = {F5B40B9B-057F-4D22-B34E-946F7F6E7B86} + {2982CB50-A029-4C8E-938F-F70ADA4DF207} = {F5B40B9B-057F-4D22-B34E-946F7F6E7B86} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E418719F-3193-403E-AF58-9BE9F94FD8BE} diff --git a/src/ApiGateways/eSchool.GraphQL/Dockerfile b/src/ApiGateways/eSchool.GraphQL/Dockerfile index 9a50f089..75ef8ff2 100644 --- a/src/ApiGateways/eSchool.GraphQL/Dockerfile +++ b/src/ApiGateways/eSchool.GraphQL/Dockerfile @@ -15,6 +15,12 @@ COPY "src/Services/Enrolling/Enrolling.Infrastructure/Enrolling.Infrastructure.c COPY "src/Services/Enrolling/Enrolling.UnitTests/Enrolling.UnitTests.csproj" "src/Services/Enrolling/Enrolling.UnitTests/Enrolling.UnitTests.csproj" COPY "src/Services/Enrolling/Enrolling.FunctionalTests/Enrolling.FunctionalTests.csproj" "src/Services/Enrolling/Enrolling.FunctionalTests/Enrolling.FunctionalTests.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.API/CourseRegistration.API.csproj" "src/Services/CourseRegistration/CourseRegistration.API/CourseRegistration.API.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.Domain/CourseRegistration.Domain.csproj" "src/Services/CourseRegistration/CourseRegistration.Domain/CourseRegistration.Domain.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistration.Infrastructure.csproj" "src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistration.Infrastructure.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.UnitTests/CourseRegistration.UnitTests.csproj" "src/Services/CourseRegistration/CourseRegistration.UnitTests/Enrolling.UnitTests.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.FunctionalTests/CourseRegistration.FunctionalTests.csproj" "src/Services/CourseRegistration/CourseRegistration.FunctionalTests/CourseRegistration.FunctionalTests.csproj" + COPY "src/Libraries/OpenTelemetry/OpenTelemetry.csproj" "src/Libraries/OpenTelemetry/OpenTelemetry.csproj" COPY "src/Web/WebStatus/WebStatus.csproj" "src/Web/WebStatus/WebStatus.csproj" diff --git a/src/ApiGateways/eSchool.GraphQL/Startup.cs b/src/ApiGateways/eSchool.GraphQL/Startup.cs index 6d6d900f..7a708fd8 100644 --- a/src/ApiGateways/eSchool.GraphQL/Startup.cs +++ b/src/ApiGateways/eSchool.GraphQL/Startup.cs @@ -12,6 +12,7 @@ namespace OpenCodeFoundation.ESchool.ApiGateways.ESchool.GraphQL public class Startup { public const string Enrolling = "enrolling"; + public const string CourseRegistration = "courseregistration"; // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 @@ -22,9 +23,13 @@ public void ConfigureServices(IServiceCollection services) services.AddHttpClient(Enrolling, c => c.BaseAddress = new Uri("http://enrolling.api/graphql")); + services.AddHttpClient(CourseRegistration, c => + c.BaseAddress = new Uri("http://courseregistration.api/graphql")); + services .AddGraphQLServer() - .AddRemoteSchema(Enrolling); + .AddRemoteSchema(Enrolling) + .AddRemoteSchema(CourseRegistration); services.AddOpenTelemetryIntegration(); } diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Application/Behaviors/LoggingBehavior.cs b/src/Services/CourseRegistration/CourseRegistration.API/Application/Behaviors/LoggingBehavior.cs new file mode 100644 index 00000000..a17e8723 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Application/Behaviors/LoggingBehavior.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace CourseRegistration.API.Application.Behaviors +{ + public class LoggingBehavior : IPipelineBehavior + { + + private readonly ILogger> _logger; + + public LoggingBehavior(ILogger> logger) + { + _logger = logger ?? throw new System.ArgumentNullException(nameof(logger)); + } + + public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + { + _logger.LogInformation("Handling request {RequestName} ({@Request})", request.GetType().Name, request); + + var response = await next(); + + _logger.LogInformation( + "Request {RequestName} handled. Response: {@Response}", + request.GetType().Name, + response); + + return response; + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Application/Commands/CourseRegistrationCommand.cs b/src/Services/CourseRegistration/CourseRegistration.API/Application/Commands/CourseRegistrationCommand.cs new file mode 100644 index 00000000..6ebd5516 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Application/Commands/CourseRegistrationCommand.cs @@ -0,0 +1,11 @@ +using MediatR; + +namespace CourseRegistration.API.Application.Commands +{ + public class CourseRegistrationCommand : IRequest + { + public string CourseCode { get; set; } + public string CourseName { get; set; } + public string Description { get; set; } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Application/Commands/CourseRegistrationCommandHandler.cs b/src/Services/CourseRegistration/CourseRegistration.API/Application/Commands/CourseRegistrationCommandHandler.cs new file mode 100644 index 00000000..160c200d --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Application/Commands/CourseRegistrationCommandHandler.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CourseRegistration.Infrastructure; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace CourseRegistration.API.Application.Commands +{ + public class CourseRegistrationCommandHandler : IRequestHandler + { + private readonly ILogger _logger; + private readonly CourseRegistrationContext _context; + + public CourseRegistrationCommandHandler(CourseRegistrationContext context, + ILogger logger) + { + _context = context ?? throw new ArgumentException(nameof(context)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public async Task Handle(CourseRegistrationCommand command, CancellationToken cancellationToken) + { + var courseRegistration = new CourseRegistration.Domain.AggregatesModel.CourseRegistrationAggregate.CourseRegistration(command.CourseCode, command.CourseName, command.Description); + _context.CourseRegistrations.Add(courseRegistration); + await _context.SaveChangesAsync(); + return true; + + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Application/Queries/FindAllCourseRegistrationHandler.cs b/src/Services/CourseRegistration/CourseRegistration.API/Application/Queries/FindAllCourseRegistrationHandler.cs new file mode 100644 index 00000000..4599b691 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Application/Queries/FindAllCourseRegistrationHandler.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CourseRegistration.Infrastructure; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CourseRegistration.API.Application.Queries +{ + public class FindAllCourseRegistrationHandler + : IRequestHandler> + { + private readonly CourseRegistrationContext _context; + + + public FindAllCourseRegistrationHandler(CourseRegistrationContext context) + { + _context = context ?? throw new System.ArgumentNullException(nameof(context)); + } + + + public async Task> Handle(FindAllCourseRegistrationQuery request, CancellationToken cancellationToken) + { + return await _context.CourseRegistrations.ToListAsync(); + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Application/Queries/FindAllCourseRegistrationQuery.cs b/src/Services/CourseRegistration/CourseRegistration.API/Application/Queries/FindAllCourseRegistrationQuery.cs new file mode 100644 index 00000000..06f74ac7 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Application/Queries/FindAllCourseRegistrationQuery.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MediatR; + +namespace CourseRegistration.API.Application.Queries +{ + public class FindAllCourseRegistrationQuery + :IRequest> + { + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Controllers/CourseRegistrationController.cs b/src/Services/CourseRegistration/CourseRegistration.API/Controllers/CourseRegistrationController.cs new file mode 100644 index 00000000..e94a6c21 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Controllers/CourseRegistrationController.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CourseRegistration.API.Application.Commands; +using CourseRegistration.API.Application.Queries; +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace CourseRegistration.API.Controllers +{ + [ApiController] + [Route("[controller]")] + public class CourseRegistrationController : Controller + { + private readonly IMediator _mediator; + + public CourseRegistrationController(IMediator mediator) + { + _mediator = mediator; + } + + [HttpGet] + public async Task> Get() + => await _mediator.Send(new FindAllCourseRegistrationQuery()); + + [HttpPost] + public async Task Post([FromBody] CourseRegistrationCommand command) + { + await _mediator.Send(command); + return Ok(); + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Controllers/WeatherForecastController.cs b/src/Services/CourseRegistration/CourseRegistration.API/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..c05a03e8 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Controllers/WeatherForecastController.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace CourseRegistration.API.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/CourseRegistration.API.csproj b/src/Services/CourseRegistration/CourseRegistration.API/CourseRegistration.API.csproj new file mode 100644 index 00000000..3df9e64b --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/CourseRegistration.API.csproj @@ -0,0 +1,36 @@ + + + + net5.0 + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Dockerfile b/src/Services/CourseRegistration/CourseRegistration.API/Dockerfile new file mode 100644 index 00000000..0e43e614 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Dockerfile @@ -0,0 +1,53 @@ +FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base +WORKDIR /app +EXPOSE 80 + +FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build +WORKDIR /src + +COPY "eSchool.sln" "eSchool.sln" + +COPY "src/ApiGateways/eSchool.GraphQL/eSchool.GraphQL.csproj" "src/ApiGateways/eSchool.GraphQL/eSchool.GraphQL.csproj" + +COPY "src/Services/Enrolling/Enrolling.API/Enrolling.API.csproj" "src/Services/Enrolling/Enrolling.API/Enrolling.API.csproj" +COPY "src/Services/Enrolling/Enrolling.Domain/Enrolling.Domain.csproj" "src/Services/Enrolling/Enrolling.Domain/Enrolling.Domain.csproj" +COPY "src/Services/Enrolling/Enrolling.Infrastructure/Enrolling.Infrastructure.csproj" "src/Services/Enrolling/Enrolling.Infrastructure/Enrolling.Infrastructure.csproj" +COPY "src/Services/Enrolling/Enrolling.UnitTests/Enrolling.UnitTests.csproj" "src/Services/Enrolling/Enrolling.UnitTests/Enrolling.UnitTests.csproj" +COPY "src/Services/Enrolling/Enrolling.FunctionalTests/Enrolling.FunctionalTests.csproj" "src/Services/Enrolling/Enrolling.FunctionalTests/Enrolling.FunctionalTests.csproj" + +COPY "src/Services/CourseRegistration/CourseRegistration.API/CourseRegistration.API.csproj" "src/Services/CourseRegistration/CourseRegistration.API/CourseRegistration.API.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.Domain/CourseRegistration.Domain.csproj" "src/Services/CourseRegistration/CourseRegistration.Domain/CourseRegistration.Domain.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistration.Infrastructure.csproj" "src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistration.Infrastructure.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.UnitTests/CourseRegistration.UnitTests.csproj" "src/Services/CourseRegistration/CourseRegistration.UnitTests/CourseRegistration.UnitTests.csproj" +COPY "src/Services/CourseRegistration/CourseRegistration.FunctionalTests/CourseRegistration.FunctionalTests.csproj" "src/Services/CourseRegistration/CourseRegistration.FunctionalTests/CourseRegistration.FunctionalTests.csproj" + + +COPY "src/Libraries/OpenTelemetry/OpenTelemetry.csproj" "src/Libraries/OpenTelemetry/OpenTelemetry.csproj" + +COPY "src/Web/WebStatus/WebStatus.csproj" "src/Web/WebStatus/WebStatus.csproj" + +COPY "src/Web/Frontend.Blazor/Frontend.Blazor.Server/Frontend.Blazor.Server.csproj" "src/Web/Frontend.Blazor/Frontend.Blazor.Server/Frontend.Blazor.Server.csproj" +COPY "src/Web/Frontend.Blazor/Frontend.Blazor.Client/Frontend.Blazor.Client.csproj" "src/Web/Frontend.Blazor/Frontend.Blazor.Client/Frontend.Blazor.Client.csproj" +COPY "src/Web/Frontend.Blazor/Frontend.Blazor.Shared/Frontend.Blazor.Shared.csproj" "src/Web/Frontend.Blazor/Frontend.Blazor.Shared/Frontend.Blazor.Shared.csproj" + +COPY "docker-compose.dcproj" "docker-compose.dcproj" + +RUN dotnet restore eSchool.sln -nowarn:msb3202,nu1503 + +COPY . . +WORKDIR /src/src/Services/CourseRegistration/CourseRegistration.API +RUN dotnet build --no-restore -c Release -o /app/build + +FROM build as unittest +WORKDIR /src/src/Services/CourseRegistration/CourseRegistration.UnitTests + +FROM build as functionaltest +WORKDIR /src/src/Services/CourseRegistration/CourseRegistration.FunctionalTests + +FROM build AS publish +RUN dotnet publish --no-restore -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "CourseRegistration.API.dll"] \ No newline at end of file diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Extensions/ServiceCollectionExtensions.cs b/src/Services/CourseRegistration/CourseRegistration.API/Extensions/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..79753d8d --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Extensions/ServiceCollectionExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace CourseRegistration.API.Extensions +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddCustomHealthChecks(this IServiceCollection services, IConfiguration configuration) + { + var hcBuilder = services.AddHealthChecks(); + hcBuilder.AddCheck("self", () => HealthCheckResult.Healthy()) + .AddSqlServer( + configuration["ConnectionStrings"], + name: "CourseRegistrationDB-check", + tags: new string[] { "courseregistrationdb" }); + + return services; + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/GraphQlErrorFilter.cs b/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/GraphQlErrorFilter.cs new file mode 100644 index 00000000..721a76c1 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/GraphQlErrorFilter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using HotChocolate; + +namespace CourseRegistration.API.GraphQL +{ + public class GraphQlErrorFilter + : IErrorFilter + { + public IError OnError(IError error) + { + return error.WithMessage( + error.Exception?.Message ?? string.Empty); + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/Mutation.cs b/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/Mutation.cs new file mode 100644 index 00000000..8bc5691c --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/Mutation.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CourseRegistration.API.Application.Commands; +using CourseRegistration.Infrastructure; +using HotChocolate; + +namespace CourseRegistration.API.GraphQL +{ + public class Mutation + { + public async Task AddCourseRegistationAsync( + CourseRegistrationCommand input, + [Service] CourseRegistrationContext context) + { + var courseRegistration = new CourseRegistration.Domain.AggregatesModel.CourseRegistrationAggregate.CourseRegistration( + input.CourseCode, + input.CourseName, + input.Description); + + await context.CourseRegistrations.AddAsync(courseRegistration); + await context.SaveChangesAsync(); + return courseRegistration; + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/Query.cs b/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/Query.cs new file mode 100644 index 00000000..a9929a9c --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/GraphQL/Query.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CourseRegistration.Infrastructure; +using HotChocolate; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CourseRegistration.API.GraphQL +{ + public class Query + { + public async Task> GetCourseRegistrationAsync( + [Service] CourseRegistrationContext context, + [Service] ILogger logger) + { + var courseRegistration = await context.CourseRegistrations + .ToListAsync(); + + logger.LogInformation( + "Returning course registration {CourseRegistrationCount} with payload {@CourseRegistration}", + courseRegistration.Count, + courseRegistration); + + return courseRegistration; + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/IWebHostExtensions.cs b/src/Services/CourseRegistration/CourseRegistration.API/IWebHostExtensions.cs new file mode 100644 index 00000000..692b34fe --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/IWebHostExtensions.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Data.SqlClient; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Polly; + +namespace CourseRegistration.API +{ + public static class IWebHostExtensions + { + public static IHost MigrateDbContext(this IHost webHost, Action seeder) + where TContext : DbContext + { + using (var scope = webHost.Services.CreateScope()) + { + var services = scope.ServiceProvider; + //var logger = services.GetRequiredService>(); + var context = services.GetService(); + + try + { + //logger.LogInformation("Migrating database associated with context {ContextName}", typeof(TContext).Name); + + var retry = Policy.Handle() + .WaitAndRetry(new TimeSpan[] + { + TimeSpan.FromSeconds(5), + TimeSpan.FromSeconds(10), + TimeSpan.FromSeconds(15), + }); + + retry.Execute(() => + { + context.Database.Migrate(); + seeder(context, services); + }); + + //logger.LogInformation($"Migrated database associated with context {typeof(TContext).Name}"); + } + catch (Exception ex) + { + /// logger.LogError(ex, $"An error occurred while migrating the databases used on context {typeof(TContext).Name}"); + } + } + + return webHost; + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Program.cs b/src/Services/CourseRegistration/CourseRegistration.API/Program.cs new file mode 100644 index 00000000..5ea659d8 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Program.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using CourseRegistration.Infrastructure; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Enrichers.Span; + +namespace CourseRegistration.API +{ + public class Program + { + public static readonly string Namespace = typeof(Program).Namespace!; + public static readonly string AppName = Namespace.Substring(Namespace.LastIndexOf('.', Namespace.LastIndexOf('.') - 1) + 1); + + + public static int Main(string[] args) + { + var configuration = GetConfiguration(); + + Log.Logger = CreateSerilogLogger(configuration); + + try + { + Log.Information("Configuring web host ({ApplicationContext})...", AppName); + var host = CreateHostBuilder(configuration, args).Build(); + + Log.Information("Applying migrations ({ApplicationContext})...", AppName); + + host.MigrateDbContext((_, __) => { }); + + Log.Information("Starting web host ({ApplicationContext})...", AppName); + host.Run(); + //CreateHostBuilder(args).Build().Run(); + + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + + + } + + public static IHostBuilder CreateHostBuilder(IConfiguration configuration, string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + webBuilder.UseConfiguration(configuration); + webBuilder.UseSerilog(); + }); + + //public static IHostBuilder CreateHostBuilder(string[] args) => + // Host.CreateDefaultBuilder(args) + // .ConfigureWebHostDefaults(webBuilder => + // { + // webBuilder.UseStartup(); + // }); + + + private static ILogger CreateSerilogLogger(IConfiguration configuration) + { + return new LoggerConfiguration() + .MinimumLevel.Verbose() + .Enrich.WithProperty("ApplicationContext", AppName) + .Enrich.FromLogContext() + .Enrich.WithSpan() + .WriteTo.Console() + .WriteTo.Seq("http://seq") + .ReadFrom.Configuration(configuration) + .CreateLogger(); + } + + private static IConfiguration GetConfiguration() + { + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) + .AddEnvironmentVariables(); + + // Load other configurations here. Ex. Keyvault or AppConfiguration + return builder.Build(); + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/Startup.cs b/src/Services/CourseRegistration/CourseRegistration.API/Startup.cs new file mode 100644 index 00000000..062c971b --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/Startup.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; +using MediatR; +using CourseRegistration.Infrastructure; +using Microsoft.EntityFrameworkCore; +using CourseRegistration.API.Extensions; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using HealthChecks.UI.Client; +using CourseRegistration.API.Application.Behaviors; +using Serilog; +using OpenCodeFoundation.OpenTelemetry; +using CourseRegistration.API.GraphQL; + +namespace CourseRegistration.API +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly); + services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>)); + + + services.AddDbContext(options => + { + options.UseSqlServer( + Configuration["ConnectionStrings"], + sqlServerOptionsAction: sqlOptions => + { + sqlOptions.MigrationsAssembly(typeof(CourseRegistrationContext).GetTypeInfo().Assembly.GetName().Name); + sqlOptions.EnableRetryOnFailure(maxRetryCount: 15, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null); + }); + }); + + services.AddGraphQLServer() + .AddQueryType() + .AddMutationType() + .AddErrorFilter(); + + services.AddControllers() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.IgnoreNullValues = true; + }); + + services.AddCustomHealthChecks(Configuration); + + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "CourseRegistration HTTP API", Version = "v1" }); + }); + + services.AddOpenTelemetryIntegration(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) + { + var pathBase = Configuration["PATH_BASE"]; + if (!string.IsNullOrEmpty(pathBase)) + { + loggerFactory.CreateLogger().LogInformation("Using PATH BASE '{pathBase}'", pathBase); + app.UsePathBase(pathBase); + } + + app.UseSerilogRequestLogging(); + + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseSwagger(); + app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "CourseRegistration.API v1")); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + endpoints.MapHealthChecks("/hc", new HealthCheckOptions() + { + Predicate = _ => true, + ResponseWriter = UIResponseWriter.WriteHealthCheckUIResponse, + }); + endpoints.MapHealthChecks("/liveness", new HealthCheckOptions() + { + Predicate = r => r.Name.Contains("self"), + }); + + endpoints.MapGraphQL(); + }); + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/WeatherForecast.cs b/src/Services/CourseRegistration/CourseRegistration.API/WeatherForecast.cs new file mode 100644 index 00000000..3f7fc4f4 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace CourseRegistration.API +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string Summary { get; set; } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/appsettings.Development.json b/src/Services/CourseRegistration/CourseRegistration.API/appsettings.Development.json new file mode 100644 index 00000000..8983e0fc --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.API/appsettings.json b/src/Services/CourseRegistration/CourseRegistration.API/appsettings.json new file mode 100644 index 00000000..648d07fd --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.API/appsettings.json @@ -0,0 +1,21 @@ +{ + "ConnectionStrings": "Server=tcp:127.0.0.1,5433;Database=OpenCodeFoundation.CourseRegistrationDb;User Id=sa;Password=Pass@word;", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "OpenTelemetry": { + "Enabled": true, + "Istio": false, + "Jaeger": { + "Enabled": true, + "ServiceName": "courseregistration.api", + "Host": "jaeger", + "Port": 6831 + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.Domain/AggregatesModel/CourseRegistrationAggregate/CourseRegistration.cs b/src/Services/CourseRegistration/CourseRegistration.Domain/AggregatesModel/CourseRegistrationAggregate/CourseRegistration.cs new file mode 100644 index 00000000..e0c9d7ab --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Domain/AggregatesModel/CourseRegistrationAggregate/CourseRegistration.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using CourseRegistration.Domain.SeedWork; + +namespace CourseRegistration.Domain.AggregatesModel.CourseRegistrationAggregate +{ + public class CourseRegistration + : Entity, IAggregateRoot + { + public CourseRegistration(string courseCode, string courseName, string description) + { + CourseCode = !string.IsNullOrWhiteSpace(courseCode) ? courseCode + : throw new ArgumentNullException(nameof(courseCode)); + + CourseName = !string.IsNullOrWhiteSpace(courseName) ? courseName + : throw new ArgumentNullException(nameof(courseName)); + + Description = !string.IsNullOrEmpty(description) ? description + : ""; + } + + public string CourseCode { get; private set; } + public string CourseName { get; private set; } + public string Description { get; private set; } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.Domain/CourseRegistration.Domain.csproj b/src/Services/CourseRegistration/CourseRegistration.Domain/CourseRegistration.Domain.csproj new file mode 100644 index 00000000..f208d303 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Domain/CourseRegistration.Domain.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/Entity.cs b/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/Entity.cs new file mode 100644 index 00000000..33f4ba0a --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/Entity.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CourseRegistration.Domain.SeedWork +{ + public abstract class Entity + { + public Guid Id { get; protected set; } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/IAggregateRoot.cs b/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/IAggregateRoot.cs new file mode 100644 index 00000000..193fa7dd --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/IAggregateRoot.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CourseRegistration.Domain.SeedWork +{ + public interface IAggregateRoot + { + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/IRepository.cs b/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/IRepository.cs new file mode 100644 index 00000000..eac18b08 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Domain/SeedWork/IRepository.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CourseRegistration.Domain.SeedWork +{ + public interface IRepository + where T : IAggregateRoot + { + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.FunctionalTests/CourseRegistration.FunctionalTests.csproj b/src/Services/CourseRegistration/CourseRegistration.FunctionalTests/CourseRegistration.FunctionalTests.csproj new file mode 100644 index 00000000..c0dc1ace --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.FunctionalTests/CourseRegistration.FunctionalTests.csproj @@ -0,0 +1,22 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistration.Infrastructure.csproj b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistration.Infrastructure.csproj new file mode 100644 index 00000000..c916e4c6 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistration.Infrastructure.csproj @@ -0,0 +1,20 @@ + + + + net5.0 + + + + + + + + + + + + + + + + diff --git a/src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistrationContext.cs b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistrationContext.cs new file mode 100644 index 00000000..e30b5d83 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/CourseRegistrationContext.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace CourseRegistration.Infrastructure +{ + public class CourseRegistrationContext : DbContext + { + public CourseRegistrationContext(DbContextOptions options) + : base(options) + { + + } + + public DbSet CourseRegistrations { get; set; } = default!; + } + + /// + /// Helper class for creating migration. To create new migration, run the + /// command from `CourseRegistration.Intrastructure` folder. + /// + /// $ dotnet ef migrations add name_of_migration --startup-project ../CourseRegistration.API + /// + public class CourseRegistrationContextFactory : IDesignTimeDbContextFactory + { + public CourseRegistrationContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder() + .UseSqlServer("Server=.;Initial Catalog=OpenCodeFoundation.CourseRegistrationDb;Integrated Security=true"); + + return new CourseRegistrationContext(optionsBuilder.Options); + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/20210408151402_initmig.Designer.cs b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/20210408151402_initmig.Designer.cs new file mode 100644 index 00000000..3701088f --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/20210408151402_initmig.Designer.cs @@ -0,0 +1,46 @@ +// +using System; +using CourseRegistration.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace CourseRegistration.Infrastructure.Migrations +{ + [DbContext(typeof(CourseRegistrationContext))] + [Migration("20210408151402_initmig")] + partial class initmig + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.5") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("CourseRegistration.Domain.AggregatesModel.CourseRegistrationAggregate.CourseRegistration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CourseCode") + .HasColumnType("nvarchar(max)"); + + b.Property("CourseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CourseRegistrations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/20210408151402_initmig.cs b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/20210408151402_initmig.cs new file mode 100644 index 00000000..a9329f8e --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/20210408151402_initmig.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace CourseRegistration.Infrastructure.Migrations +{ + public partial class initmig : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "CourseRegistrations", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + CourseCode = table.Column(type: "nvarchar(max)", nullable: true), + CourseName = table.Column(type: "nvarchar(max)", nullable: true), + Description = table.Column(type: "nvarchar(max)", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CourseRegistrations", x => x.Id); + }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CourseRegistrations"); + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/CourseRegistrationContextModelSnapshot.cs b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/CourseRegistrationContextModelSnapshot.cs new file mode 100644 index 00000000..155a0d90 --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.Infrastructure/Migrations/CourseRegistrationContextModelSnapshot.cs @@ -0,0 +1,44 @@ +// +using System; +using CourseRegistration.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace CourseRegistration.Infrastructure.Migrations +{ + [DbContext(typeof(CourseRegistrationContext))] + partial class CourseRegistrationContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.5") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("CourseRegistration.Domain.AggregatesModel.CourseRegistrationAggregate.CourseRegistration", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CourseCode") + .HasColumnType("nvarchar(max)"); + + b.Property("CourseName") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("CourseRegistrations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Services/CourseRegistration/CourseRegistration.UnitTests/CourseRegistration.UnitTests.csproj b/src/Services/CourseRegistration/CourseRegistration.UnitTests/CourseRegistration.UnitTests.csproj new file mode 100644 index 00000000..c0dc1ace --- /dev/null +++ b/src/Services/CourseRegistration/CourseRegistration.UnitTests/CourseRegistration.UnitTests.csproj @@ -0,0 +1,22 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + +