├── .gitignore
├── Api.IntegrationTests
├── Api.IntegrationTests.csproj
├── BaseIntegrationTest.cs
├── GlobalUsings.cs
├── IntegrationTestApiFactory.cs
└── UserController
│ └── GetUserIntegrationTests.cs
├── Api
├── .dockerignore
├── Api.csproj
├── Controllers
│ ├── UserController.cs
│ └── UserTaskController.cs
├── Dockerfile
├── Dtos
│ └── UserTaskDtos
│ │ └── UpdateUserTaskBodyDto.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
└── appsettings.json
├── BackgroundWorker
├── .dockerignore
├── BackgroundWorker.csproj
├── Dockerfile
├── Program.cs
├── Properties
│ └── launchSettings.json
└── appsettings.json
├── Components.UnitTests
├── BaseUnitTest.cs
├── Components.UnitTests.csproj
├── GlobalUsings.cs
└── UserComponents
│ └── Queries
│ └── GetUserUnitTests.cs
├── Components
├── Components.csproj
├── ComponentsExtensions.cs
├── UserComponents
│ ├── Commands
│ │ ├── CreateUser.cs
│ │ └── DeleteUser.cs
│ └── Queries
│ │ └── GetUser.cs
└── UserTaskComponents
│ ├── Commands
│ ├── CreateUserTask.cs
│ ├── DeleteUserTask.cs
│ └── UpdateUserTask.cs
│ └── Queries
│ ├── GetUserTask.cs
│ └── GetUserTasksCreatedByUser.cs
├── DataModel
├── DataModel.csproj
├── DataModelExtensions.cs
├── DataModelOptions.cs
├── DatabaseContext.cs
├── DatabaseContextFactory.cs
├── Migrations
│ ├── 20230905193848_Init.Designer.cs
│ ├── 20230905193848_Init.cs
│ └── DatabaseContextModelSnapshot.cs
└── Models
│ ├── BaseModel.cs
│ ├── BaseModelEnum.cs
│ ├── BaseModelEnumExtensions.cs
│ ├── BaseModelExtensions.cs
│ ├── Refs
│ ├── TaskStatusRefs
│ │ ├── TaskStatusEnum.cs
│ │ ├── TaskStatusRef.cs
│ │ └── TaskStatusRefContext.cs
│ └── UserStatusRefs
│ │ ├── UserStatusEnum.cs
│ │ ├── UserStatusRef.cs
│ │ └── UserStatusRefContext.cs
│ ├── UserTasks
│ ├── UserTask.cs
│ └── UserTaskContext.cs
│ └── Users
│ ├── User.cs
│ └── UserContext.cs
├── EventHandlers.UnitTests
├── BaseUnitTest.cs
├── EventHandlers.UnitTests.csproj
├── GlobalUsings.cs
└── UserTaskEventHandlers
│ └── NotifyAssignedUserEventHandlerTests.cs
├── EventHandlers
├── EventHandlers.csproj
├── EventHandlersExtensions.cs
├── UserEventHandlers
│ ├── LogUserCreatedEventHandler.cs
│ └── LogUserDeletedEventHandler.cs
└── UserTaskEventHandlers
│ └── NotifyAssignedUserEventHandler.cs
├── Events
├── Events.csproj
├── UserEvents
│ ├── UserCreatedEvent.cs
│ └── UserDeletedEvent.cs
└── UserTaskEvents
│ ├── UserTaskCreatedEvent.cs
│ ├── UserTaskDeletedEvent.cs
│ └── UserTaskUpdatedEvent.cs
├── Infrastructure
├── Authentication
│ └── AuthenticationExtensions.cs
├── BackgroundJob
│ ├── BackgroundJobOptions.cs
│ ├── Hangfire
│ │ ├── HangfireExtensions.cs
│ │ └── HangfireJobBus.cs
│ └── IBackgroundJobBus.cs
├── CQRS
│ ├── CQRSExtensions.cs
│ ├── Commands
│ │ ├── CommandBus.cs
│ │ ├── ICommand.cs
│ │ ├── ICommandBus.cs
│ │ └── ICommandHandler.cs
│ ├── Events
│ │ ├── Event.cs
│ │ ├── EventBus.cs
│ │ ├── IEvent.cs
│ │ ├── IEventBus.cs
│ │ └── IEventHandler.cs
│ ├── Queries
│ │ ├── IQuery.cs
│ │ ├── IQueryBus.cs
│ │ ├── IQueryHandler.cs
│ │ └── QueryBus.cs
│ └── ValidationBehavior.cs
├── Cors
│ └── CorsExtensions.cs
├── ExceptionHandling
│ ├── ExceptionHandlingExtensions.cs
│ ├── ExceptionHandlingMiddleware.cs
│ └── Exceptions
│ │ ├── BadRequestException.cs
│ │ ├── ForbiddenException.cs
│ │ ├── NotFoundException.cs
│ │ └── NotSupportedException.cs
├── HealthChecks
│ └── HealthChecksExtensions.cs
├── Infrastructure.csproj
├── StronglyTypedIds
│ ├── InvalidStronglyTypedIdException.cs
│ ├── StronglyTypedIdBaseEntity.cs
│ ├── StronglyTypedIdConversions.cs
│ ├── StronglyTypedIdConverter.cs
│ ├── StronglyTypedIdHelper.cs
│ ├── StronglyTypedIdJsonConverterFactory.cs
│ ├── StronglyTypedIdValidator.cs
│ └── StronglyTypedIdsExtensions.cs
└── Swagger
│ ├── AuthOperationsFilter.cs
│ ├── StronglyTypedIdSchemaFilter.cs
│ └── SwaggerExtensions.cs
├── LICENSE
├── NotificationService
├── INotificationService.cs
├── NotificationService.cs
└── NotificationService.csproj
├── README.md
├── SampleApplication.sln
└── docker-compose.yaml
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 |
84 | # Visual Studio profiler
85 | *.psess
86 | *.vsp
87 | *.vspx
88 | *.sap
89 |
90 | # TFS 2012 Local Workspace
91 | $tf/
92 |
93 | # Guidance Automation Toolkit
94 | *.gpState
95 |
96 | # ReSharper is a .NET coding add-in
97 | _ReSharper*/
98 | *.[Rr]e[Ss]harper
99 | *.DotSettings.user
100 |
101 | # JustCode is a .NET coding add-in
102 | .JustCode
103 |
104 | # TeamCity is a build add-in
105 | _TeamCity*
106 |
107 | # DotCover is a Code Coverage Tool
108 | *.dotCover
109 |
110 | # NCrunch
111 | _NCrunch_*
112 | .*crunch*.local.xml
113 | nCrunchTemp_*
114 |
115 | # MightyMoose
116 | *.mm.*
117 | AutoTest.Net/
118 |
119 | # Web workbench (sass)
120 | .sass-cache/
121 |
122 | # Installshield output folder
123 | [Ee]xpress/
124 |
125 | # DocProject is a documentation generator add-in
126 | DocProject/buildhelp/
127 | DocProject/Help/*.HxT
128 | DocProject/Help/*.HxC
129 | DocProject/Help/*.hhc
130 | DocProject/Help/*.hhk
131 | DocProject/Help/*.hhp
132 | DocProject/Help/Html2
133 | DocProject/Help/html
134 |
135 | # Click-Once directory
136 | publish/
137 |
138 | # Publish Web Output
139 | *.[Pp]ublish.xml
140 | *.azurePubxml
141 | # TODO: Comment the next line if you want to checkin your web deploy settings
142 | # but database connection strings (with potential passwords) will be unencrypted
143 | *.pubxml
144 | *.publishproj
145 |
146 | # NuGet Packages
147 | *.nupkg
148 | # The packages folder can be ignored because of Package Restore
149 | **/packages/*
150 | # except build/, which is used as an MSBuild target.
151 | !**/packages/build/
152 | # Uncomment if necessary however generally it will be regenerated when needed
153 | #!**/packages/repositories.config
154 |
155 | # Microsoft Azure Build Output
156 | csx/
157 | *.build.csdef
158 |
159 | # Microsoft Azure Emulator
160 | ecf/
161 | rcf/
162 |
163 | # Microsoft Azure ApplicationInsights config file
164 | ApplicationInsights.config
165 |
166 | # Windows Store app package directory
167 | AppPackages/
168 | BundleArtifacts/
169 |
170 | # Visual Studio cache files
171 | # files ending in .cache can be ignored
172 | *.[Cc]ache
173 | # but keep track of directories ending in .cache
174 | !*.[Cc]ache/
175 |
176 | # Others
177 | ClientBin/
178 | ~$*
179 | *~
180 | *.dbmdl
181 | *.dbproj.schemaview
182 | *.pfx
183 | *.publishsettings
184 | node_modules/
185 | orleans.codegen.cs
186 |
187 | # RIA/Silverlight projects
188 | Generated_Code/
189 |
190 | # Backup & report files from converting an old project file
191 | # to a newer Visual Studio version. Backup files are not needed,
192 | # because we have git ;-)
193 | _UpgradeReport_Files/
194 | Backup*/
195 | UpgradeLog*.XML
196 | UpgradeLog*.htm
197 |
198 | # SQL Server files
199 | *.mdf
200 | *.ldf
201 |
202 | # Business Intelligence projects
203 | *.rdl.data
204 | *.bim.layout
205 | *.bim_*.settings
206 |
207 | # Microsoft Fakes
208 | FakesAssemblies/
209 |
210 | # GhostDoc plugin setting file
211 | *.GhostDoc.xml
212 |
213 | # Node.js Tools for Visual Studio
214 | .ntvs_analysis.dat
215 |
216 | # Visual Studio 6 build log
217 | *.plg
218 |
219 | # Visual Studio 6 workspace options file
220 | *.opt
221 |
222 | # Visual Studio LightSwitch build output
223 | **/*.HTMLClient/GeneratedArtifacts
224 | **/*.DesktopClient/GeneratedArtifacts
225 | **/*.DesktopClient/ModelManifest.xml
226 | **/*.Server/GeneratedArtifacts
227 | **/*.Server/ModelManifest.xml
228 | _Pvt_Extensions
229 |
230 | # Paket dependency manager
231 | .paket/paket.exe
232 |
233 | # FAKE - F# Make
234 | .fake/
235 |
236 | # Visual Studio 2015
237 | .vs/
238 |
239 | *.swp
240 | *.*~
241 | project.lock.json
242 | .DS_Store
243 | *.pyc
244 | nupkg/
245 |
246 | # Visual Studio Code
247 | .vscode
248 |
249 | # Jetbrains
250 | .idea/
251 |
252 | [Aa]ppsettings.[Pp]roduction.json
253 | [Aa]ppsettings.[Ss]taging.json
254 | [Aa]ppsettings.[Dd]evelopment.json
255 |
256 | API/API.xml
257 |
--------------------------------------------------------------------------------
/Api.IntegrationTests/Api.IntegrationTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 | all
22 |
23 |
24 | runtime; build; native; contentfiles; analyzers; buildtransitive
25 | all
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Api.IntegrationTests/BaseIntegrationTest.cs:
--------------------------------------------------------------------------------
1 | using DataModel;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.Extensions.DependencyInjection;
4 |
5 | namespace Api.IntegrationTests;
6 |
7 | public abstract class BaseIntegrationTest : IClassFixture
8 | {
9 | private readonly IServiceScope _scope;
10 | protected readonly HttpClient Client;
11 | protected readonly DatabaseContext Db;
12 |
13 | protected BaseIntegrationTest(IntegrationTestApiFactory factory)
14 | {
15 | _scope = factory.Services.CreateScope();
16 | Db = _scope.ServiceProvider.GetRequiredService();
17 | Db.Database.Migrate();
18 | Client = factory.CreateClient();
19 | }
20 | }
--------------------------------------------------------------------------------
/Api.IntegrationTests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Xunit;
--------------------------------------------------------------------------------
/Api.IntegrationTests/IntegrationTestApiFactory.cs:
--------------------------------------------------------------------------------
1 | using DataModel;
2 | using Microsoft.AspNetCore.Hosting;
3 | using Microsoft.AspNetCore.Mvc.Testing;
4 | using Microsoft.AspNetCore.TestHost;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.Extensions.DependencyInjection;
7 | using Testcontainers.SqlEdge;
8 |
9 | namespace Api.IntegrationTests;
10 |
11 | public class IntegrationTestApiFactory : WebApplicationFactory, IAsyncLifetime
12 | {
13 | private readonly SqlEdgeContainer _dbContainer =
14 | new SqlEdgeBuilder()
15 | .WithImage("mcr.microsoft.com/azure-sql-edge:latest")
16 | .WithPassword("pass123!")
17 | .Build();
18 |
19 | public Task InitializeAsync()
20 | {
21 | return _dbContainer.StartAsync();
22 | }
23 |
24 | public new Task DisposeAsync()
25 | {
26 | return _dbContainer.StopAsync();
27 | }
28 |
29 | protected override void ConfigureWebHost(IWebHostBuilder builder)
30 | {
31 | builder.ConfigureTestServices(services =>
32 | {
33 | var descriptor = services.SingleOrDefault(s => s.ServiceType == typeof(DbContextOptions));
34 |
35 | if (descriptor is not null)
36 | {
37 | services.Remove(descriptor);
38 | }
39 |
40 | services.AddDbContext(options =>
41 | {
42 | options.UseSqlServer(_dbContainer.GetConnectionString());
43 | });
44 | });
45 | }
46 | }
--------------------------------------------------------------------------------
/Api.IntegrationTests/UserController/GetUserIntegrationTests.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using Bogus;
3 | using Components.UserComponents.Queries;
4 | using DataModel.Models.Refs.UserStatusRefs;
5 | using DataModel.Models.Users;
6 | using FluentAssertions;
7 | using Newtonsoft.Json;
8 |
9 | namespace Api.IntegrationTests.UserController;
10 |
11 | public class GetUserTests : BaseIntegrationTest
12 | {
13 | public GetUserTests(IntegrationTestApiFactory factory) : base(factory)
14 | {
15 | }
16 |
17 | [Theory]
18 | [InlineData("Not valid id")]
19 | [InlineData("123456789")]
20 | [InlineData("us_123456789")]
21 | [InlineData("un_01h9h119dmpapchz8ap7x1f26b")]
22 | public async Task GetUser_Should_ReturnBadRequest_When_UserIdIsNotValid(string userId)
23 | {
24 | var expectedStatusCode = HttpStatusCode.BadRequest;
25 |
26 | var response = await Client.GetAsync($"/api/users/{userId}");
27 | var actualStatusCode = response.StatusCode;
28 |
29 | actualStatusCode.Should().Be(expectedStatusCode);
30 | }
31 |
32 | [Fact]
33 | public async Task Handle_Should_ReturnNotFound_When_UserIsNotFound()
34 | {
35 | var expectedStatusCode = HttpStatusCode.NotFound;
36 |
37 | var userId = UserId.New().ToString();
38 | var response = await Client.GetAsync($"/api/users/{userId}");
39 | var actualStatusCode = response.StatusCode;
40 |
41 | actualStatusCode.Should().Be(expectedStatusCode);
42 | }
43 |
44 | [Fact]
45 | public async Task Handle_Should_ReturnOK_When_UserIsFound()
46 | {
47 | var expectedStatusCode = HttpStatusCode.OK;
48 |
49 | var user = new Faker()
50 | .RuleFor(user => user.FirstName, f => f.Name.FirstName())
51 | .RuleFor(user => user.LastName, f => f.Name.LastName())
52 | .RuleFor(user => user.Email, f => f.Internet.Email())
53 | .RuleFor(user => user.StatusEnum, UserStatusEnum.Active)
54 | .Generate();
55 |
56 | Db.Users.Add(user);
57 | Db.SaveChanges();
58 |
59 | var response = await Client.GetAsync($"/api/users/{user.Id}");
60 | var actualStatusCode = response.StatusCode;
61 |
62 | actualStatusCode.Should().Be(expectedStatusCode);
63 | }
64 |
65 | [Fact]
66 | public async Task Handle_Should_ReturnUserResult_When_UserIsFound()
67 | {
68 | var user = new Faker()
69 | .RuleFor(user => user.FirstName, f => f.Name.FirstName())
70 | .RuleFor(user => user.LastName, f => f.Name.LastName())
71 | .RuleFor(user => user.Email, f => f.Internet.Email())
72 | .RuleFor(user => user.StatusEnum, UserStatusEnum.Active)
73 | .Generate();
74 |
75 | var expectedResult = new GetUser.Result
76 | {
77 | FirstName = user.FirstName,
78 | LastName = user.LastName
79 | };
80 |
81 | Db.Users.Add(user);
82 | Db.SaveChanges();
83 |
84 | var response = await Client.GetAsync($"/api/users/{user.Id}");
85 | var json = await response.Content.ReadAsStringAsync();
86 | var actualResult = JsonConvert.DeserializeObject(json);
87 |
88 | actualResult.Should().BeEquivalentTo(expectedResult);
89 | }
90 | }
--------------------------------------------------------------------------------
/Api/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.dockerignore
2 | **/.env
3 | **/.git
4 | **/.gitignore
5 | **/.project
6 | **/.settings
7 | **/.toolstarget
8 | **/.vs
9 | **/.vscode
10 | **/.idea
11 | **/*.*proj.user
12 | **/*.dbmdl
13 | **/*.jfm
14 | **/azds.yaml
15 | **/bin
16 | **/charts
17 | **/docker-compose*
18 | **/Dockerfile*
19 | **/node_modules
20 | **/npm-debug.log
21 | **/obj
22 | **/secrets.dev.yaml
23 | **/values.dev.yaml
24 | LICENSE
25 | README.md
--------------------------------------------------------------------------------
/Api/Api.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 | Linux
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/Api/Controllers/UserController.cs:
--------------------------------------------------------------------------------
1 | using Components.UserComponents.Commands;
2 | using Components.UserComponents.Queries;
3 | using DataModel.Models.Users;
4 | using Infrastructure.CQRS.Commands;
5 | using Infrastructure.CQRS.Queries;
6 | using Microsoft.AspNetCore.Mvc;
7 |
8 | namespace Api.Controllers;
9 |
10 | [ApiController]
11 | [Route("/api/users")]
12 | public class UserController : ControllerBase
13 | {
14 | private readonly ICommandBus _commandBus;
15 | private readonly IQueryBus _queryBus;
16 |
17 | public UserController(IQueryBus queryBus, ICommandBus commandBus)
18 | {
19 | _queryBus = queryBus;
20 | _commandBus = commandBus;
21 | }
22 |
23 | [HttpGet]
24 | [Route("{userId}", Name = nameof(GetUser))]
25 | public async Task> GetUser([FromRoute] UserId userId,
26 | CancellationToken cancellationToken)
27 | {
28 | var query = new GetUser.Query
29 | {
30 | UserId = userId
31 | };
32 | var result = await _queryBus.Send(query, cancellationToken);
33 |
34 | return Ok(result);
35 | }
36 |
37 | [HttpPost]
38 | [Route("", Name = nameof(CreateUser))]
39 | public async Task> CreateUser(
40 | [FromBody] CreateUser.Command command,
41 | CancellationToken cancellationToken
42 | )
43 | {
44 | var result = await _commandBus.Send(command, cancellationToken);
45 |
46 | return Created($"/api/users/{result.Id}", result);
47 | }
48 |
49 | [HttpDelete]
50 | [Route("{userId}", Name = nameof(DeleteUser))]
51 | public async Task DeleteUser(
52 | [FromRoute] string userId,
53 | CancellationToken cancellationToken
54 | )
55 | {
56 | var command = new DeleteUser.Command
57 | {
58 | UserId = userId
59 | };
60 |
61 | await _commandBus.Send(command, cancellationToken);
62 |
63 | return NoContent();
64 | }
65 | }
--------------------------------------------------------------------------------
/Api/Controllers/UserTaskController.cs:
--------------------------------------------------------------------------------
1 | using Api.Dtos.UserTaskDtos;
2 | using Components.UserTaskComponents.Commands;
3 | using Components.UserTaskComponents.Queries;
4 | using DataModel.Models.UserTasks;
5 | using Infrastructure.CQRS.Commands;
6 | using Infrastructure.CQRS.Queries;
7 | using Microsoft.AspNetCore.Mvc;
8 |
9 | namespace Api.Controllers;
10 |
11 | [ApiController]
12 | [Route("/api/user-tasks")]
13 | public class UserTaskController : ControllerBase
14 | {
15 | private readonly ICommandBus _commandBus;
16 | private readonly IQueryBus _queryBus;
17 |
18 | public UserTaskController(IQueryBus queryBus, ICommandBus commandBus)
19 | {
20 | _queryBus = queryBus;
21 | _commandBus = commandBus;
22 | }
23 |
24 | [HttpPost]
25 | [Route("", Name = nameof(CreateUserTask))]
26 | public async Task> CreateUserTask(
27 | [FromBody] CreateUserTask.Command command,
28 | CancellationToken cancellationToken
29 | )
30 | {
31 | var result = await _commandBus.Send(command, cancellationToken);
32 |
33 | return Created($"/api/user-tasks/{result.Id}", result);
34 | }
35 |
36 | [HttpGet]
37 | [Route("{userTaskId}", Name = nameof(GetUserTask))]
38 | public async Task> GetUserTask(
39 | [FromRoute] UserTaskId userTaskId,
40 | CancellationToken cancellationToken
41 | )
42 | {
43 | var query = new GetUserTask.Query
44 | {
45 | UserTaskId = userTaskId
46 | };
47 | var result = await _queryBus.Send(query, cancellationToken);
48 |
49 | return Ok(result);
50 | }
51 |
52 | [HttpDelete]
53 | [Route("{userTaskId}", Name = nameof(DeleteUserTask))]
54 | public async Task DeleteUserTask(
55 | [FromRoute] string userTaskId,
56 | CancellationToken cancellationToken
57 | )
58 | {
59 | var command = new DeleteUserTask.Command
60 | {
61 | UserTaskId = userTaskId
62 | };
63 | await _commandBus.Send(command, cancellationToken);
64 |
65 | return NoContent();
66 | }
67 |
68 | [HttpPatch]
69 | [Route("{userTaskId}", Name = nameof(UpdateUserTask))]
70 | public async Task UpdateUserTask(
71 | [FromRoute] UserTaskId userTaskId,
72 | [FromBody] UpdateUserTaskBodyDto body,
73 | CancellationToken cancellationToken
74 | )
75 | {
76 | var command = new UpdateUserTask.Command
77 | {
78 | UserTaskId = userTaskId,
79 | AssignToUserId = body.AssignToUserId
80 | };
81 | await _commandBus.Send(command, cancellationToken);
82 |
83 | return NoContent();
84 | }
85 | }
--------------------------------------------------------------------------------
/Api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
2 | WORKDIR /app
3 | EXPOSE 80
4 | EXPOSE 443
5 |
6 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
7 | WORKDIR /src
8 | COPY ["Api/Api.csproj", "Api/"]
9 | RUN dotnet restore "Api/Api.csproj"
10 | COPY . .
11 | WORKDIR "/src/Api"
12 | RUN dotnet build "Api.csproj" -c Release -o /app/build
13 |
14 | FROM build AS publish
15 | RUN dotnet publish "Api.csproj" -c Release -o /app/publish
16 |
17 | FROM base AS final
18 | WORKDIR /app
19 | COPY --from=publish /app/publish .
20 | ENTRYPOINT ["dotnet", "Api.dll"]
21 |
--------------------------------------------------------------------------------
/Api/Dtos/UserTaskDtos/UpdateUserTaskBodyDto.cs:
--------------------------------------------------------------------------------
1 | using DataModel.Models.Users;
2 |
3 | namespace Api.Dtos.UserTaskDtos;
4 |
5 | public class UpdateUserTaskBodyDto
6 | {
7 | public UserId AssignToUserId { get; init; } = null!;
8 | }
--------------------------------------------------------------------------------
/Api/Program.cs:
--------------------------------------------------------------------------------
1 | using Components;
2 | using DataModel;
3 | using Infrastructure.BackgroundJob;
4 | using Infrastructure.BackgroundJob.Hangfire;
5 | using Infrastructure.Cors;
6 | using Infrastructure.CQRS;
7 | using Infrastructure.ExceptionHandling;
8 | using Infrastructure.HealthChecks;
9 | using Infrastructure.StronglyTypedIds;
10 | using Infrastructure.Swagger;
11 |
12 | var builder = WebApplication.CreateBuilder(args);
13 |
14 | var dataModelConnectionString = builder.Configuration.GetConnectionString(DataModelOptions.ConnectionString) ??
15 | throw new Exception(
16 | $"{DataModelOptions.ConnectionString} is not found in configuration");
17 |
18 | var hangfireConnectionString = builder.Configuration.GetConnectionString(BackgroundJobOptions.ConnectionString) ??
19 | throw new Exception(
20 | $"{BackgroundJobOptions.ConnectionString} is not found in configuration");
21 |
22 | builder.Services
23 | .AddSwagger()
24 | .AddComponents()
25 | .AddDataModel(dataModelConnectionString)
26 | .AddHangfire(hangfireConnectionString)
27 | .AddCQRS()
28 | .AddExceptionHandling()
29 | .AddApiHealthChecks()
30 | .AddControllers()
31 | .AddStronglyTypedIds();
32 |
33 | var allowAllOrigins = "_allowAllOrigins";
34 | builder.Services.AddCorsPolicy(allowAllOrigins);
35 |
36 | var app = builder.Build();
37 |
38 | if (!app.Environment.IsProduction())
39 | {
40 | app.UseCorsPolicy(allowAllOrigins);
41 | app.UseSwaggerWithUI();
42 | }
43 |
44 | app.UseHttpsRedirection();
45 |
46 | app
47 | .UseCQRS()
48 | .UseExceptionhandling()
49 | .UseApiHealthChecks()
50 | .MapControllers();
51 |
52 | app.Run();
53 |
54 | // Needed for Api.IntegrationTests project
55 | public partial class Program
56 | {
57 | }
--------------------------------------------------------------------------------
/Api/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/launchsettings.json",
3 | "iisSettings": {
4 | "windowsAuthentication": false,
5 | "anonymousAuthentication": true,
6 | "iisExpress": {
7 | "applicationUrl": "http://localhost:40632",
8 | "sslPort": 44304
9 | }
10 | },
11 | "profiles": {
12 | "http": {
13 | "commandName": "Project",
14 | "dotnetRunMessages": true,
15 | "launchBrowser": false,
16 | "launchUrl": "swagger",
17 | "applicationUrl": "http://localhost:5290",
18 | "environmentVariables": {
19 | "ASPNETCORE_ENVIRONMENT": "Development"
20 | }
21 | },
22 | "https": {
23 | "commandName": "Project",
24 | "dotnetRunMessages": true,
25 | "launchBrowser": false,
26 | "launchUrl": "swagger",
27 | "applicationUrl": "https://localhost:7048;http://localhost:5290",
28 | "environmentVariables": {
29 | "ASPNETCORE_ENVIRONMENT": "Development"
30 | }
31 | },
32 | "IIS Express": {
33 | "commandName": "IISExpress",
34 | "launchBrowser": false,
35 | "launchUrl": "swagger",
36 | "environmentVariables": {
37 | "ASPNETCORE_ENVIRONMENT": "Development"
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Api/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "ConnectionStrings": {
10 | "DataModelConnectionString": "Server=db,1433;Database=app;User Id=sa;Password=pass123!;TrustServerCertificate=True;",
11 | "BackgroundJobConnectionString": "Server=db,1433;Database=jobs;User Id=sa;Password=pass123!;TrustServerCertificate=True;"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/BackgroundWorker/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.dockerignore
2 | **/.env
3 | **/.git
4 | **/.gitignore
5 | **/.project
6 | **/.settings
7 | **/.toolstarget
8 | **/.vs
9 | **/.vscode
10 | **/.idea
11 | **/*.*proj.user
12 | **/*.dbmdl
13 | **/*.jfm
14 | **/azds.yaml
15 | **/bin
16 | **/charts
17 | **/docker-compose*
18 | **/Dockerfile*
19 | **/node_modules
20 | **/npm-debug.log
21 | **/obj
22 | **/secrets.dev.yaml
23 | **/values.dev.yaml
24 | LICENSE
25 | README.md
--------------------------------------------------------------------------------
/BackgroundWorker/BackgroundWorker.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 | Linux
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/BackgroundWorker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
2 | WORKDIR /app
3 | EXPOSE 80
4 | EXPOSE 443
5 |
6 | FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
7 | WORKDIR /src
8 | COPY ["BackgroundWorker/BackgroundWorker.csproj", "BackgroundWorker/"]
9 | RUN dotnet restore "BackgroundWorker/BackgroundWorker.csproj"
10 | COPY . .
11 | WORKDIR "/src/BackgroundWorker"
12 | RUN dotnet build "BackgroundWorker.csproj" -c Release -o /app/build
13 |
14 | FROM build AS publish
15 | RUN dotnet publish "BackgroundWorker.csproj" -c Release -o /app/publish
16 |
17 | FROM base AS final
18 | WORKDIR /app
19 | COPY --from=publish /app/publish .
20 | ENTRYPOINT ["dotnet", "BackgroundWorker.dll"]
21 |
--------------------------------------------------------------------------------
/BackgroundWorker/Program.cs:
--------------------------------------------------------------------------------
1 | using DataModel;
2 | using EventHandlers;
3 | using Infrastructure.BackgroundJob;
4 | using Infrastructure.BackgroundJob.Hangfire;
5 | using Infrastructure.CQRS;
6 |
7 | var builder = WebApplication.CreateBuilder(args);
8 |
9 |
10 | var hangfireConnectionString = builder.Configuration.GetConnectionString(BackgroundJobOptions.ConnectionString) ??
11 | throw new Exception(
12 | $"{BackgroundJobOptions.ConnectionString} is not found in configuration");
13 |
14 | var dataModelConnectionString = builder.Configuration.GetConnectionString(DataModelOptions.ConnectionString) ??
15 | throw new Exception(
16 | $"{DataModelOptions.ConnectionString} is not found in configuration");
17 |
18 | builder.Services
19 | .AddDataModel(dataModelConnectionString)
20 | .AddHangfire(hangfireConnectionString)
21 | .AddCQRS()
22 | .AddEventHandlers()
23 | .AddHangfireWorker();
24 |
25 | var app = builder.Build();
26 |
27 | app
28 | .UseHangfireUI("/hangfire")
29 | .Run();
--------------------------------------------------------------------------------
/BackgroundWorker/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "iisSettings": {
3 | "windowsAuthentication": false,
4 | "anonymousAuthentication": true,
5 | "iisExpress": {
6 | "applicationUrl": "http://localhost:5706",
7 | "sslPort": 44323
8 | }
9 | },
10 | "profiles": {
11 | "http": {
12 | "commandName": "Project",
13 | "dotnetRunMessages": true,
14 | "launchBrowser": false,
15 | "applicationUrl": "http://localhost:5115",
16 | "environmentVariables": {
17 | "ASPNETCORE_ENVIRONMENT": "Development"
18 | }
19 | },
20 | "https": {
21 | "commandName": "Project",
22 | "dotnetRunMessages": true,
23 | "launchBrowser": false,
24 | "applicationUrl": "https://localhost:7021;http://localhost:5115",
25 | "environmentVariables": {
26 | "ASPNETCORE_ENVIRONMENT": "Development"
27 | }
28 | },
29 | "IIS Express": {
30 | "commandName": "IISExpress",
31 | "launchBrowser": false,
32 | "environmentVariables": {
33 | "ASPNETCORE_ENVIRONMENT": "Development"
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/BackgroundWorker/appsettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "Logging": {
3 | "LogLevel": {
4 | "Default": "Information",
5 | "Microsoft.AspNetCore": "Warning"
6 | }
7 | },
8 | "AllowedHosts": "*",
9 | "ConnectionStrings": {
10 | "DataModelConnectionString": "Server=db,1433;Database=app;User Id=sa;Password=pass123!;TrustServerCertificate=True;",
11 | "BackgroundJobConnectionString": "Server=db,1433;Database=jobs;User Id=sa;Password=pass123!;TrustServerCertificate=True;"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Components.UnitTests/BaseUnitTest.cs:
--------------------------------------------------------------------------------
1 | using DataModel;
2 | using Microsoft.EntityFrameworkCore;
3 | using Microsoft.EntityFrameworkCore.Diagnostics;
4 |
5 | namespace Components.UnitTests;
6 |
7 | public abstract class BaseUnitTest
8 | {
9 | protected readonly DbContextOptions DbContextOptions;
10 |
11 | public BaseUnitTest()
12 | {
13 | DbContextOptions = new DbContextOptionsBuilder()
14 | .UseInMemoryDatabase(Guid.NewGuid().ToString())
15 | .ConfigureWarnings(b => b.Ignore(InMemoryEventId.TransactionIgnoredWarning))
16 | .Options;
17 | }
18 | }
--------------------------------------------------------------------------------
/Components.UnitTests/Components.UnitTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net7.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | runtime; build; native; contentfiles; analyzers; buildtransitive
21 | all
22 |
23 |
24 | runtime; build; native; contentfiles; analyzers; buildtransitive
25 | all
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/Components.UnitTests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using Xunit;
--------------------------------------------------------------------------------
/Components.UnitTests/UserComponents/Queries/GetUserUnitTests.cs:
--------------------------------------------------------------------------------
1 | using Bogus;
2 | using Components.UserComponents.Queries;
3 | using DataModel;
4 | using DataModel.Models.Refs.UserStatusRefs;
5 | using DataModel.Models.Users;
6 | using FluentAssertions;
7 | using FluentValidation.TestHelper;
8 | using Infrastructure.ExceptionHandling.Exceptions;
9 |
10 | namespace Components.UnitTests.UserComponents.Queries;
11 |
12 | public class GetUserUnitTests : BaseUnitTest
13 | {
14 | public static IEnumerable