Skip to content

Commit e8ab3e2

Browse files
authored
Feature/architecture upgrade (#16)
Fixed the Dockerfile, seed data and additional fields in Contact Form
1 parent a9f4a6f commit e8ab3e2

29 files changed

+369
-15258
lines changed

.env.prod

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# .env
2+
ENVIRONMENT=Production
3+
4+
# SQL Server Connection
5+
SQL_SERVER=mssql
6+
SQL_DATABASE=Contacts
7+
SQL_USER=sa
8+
SQL_PASSWORD=PasYourPassword123
9+
10+
# JWT Settings
11+
JWT_SECRET=THIS USED TO SIGN AND VERIFY JWT TOKENS, REPLACE IT WITH YOUR OWN SECRET, IT CAN BE ANY STRING
12+
JWT_ISSUER=http://localhost
13+
JWT_AUDIENCE=http://localhost
14+
PASSWORD_RESET_URL=http://localhost/reset-password/
15+
16+
# SMTP Settings
17+
SMTP_SERVER=smtp.office365.com
18+
SMTP_PORT=587
19+
SMTP_USERNAME=nitin.singh21@hotmail.com
20+
SMTP_PASSWORD=Sh@kti009
21+
SMTP_FROM_EMAIL=nitin.singh21@hotmail.com
22+
SMTP_ENABLE_SSL=true

README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Stay tuned and watch this repository for the latest updates!
2020

2121
To understand this project in-depth, refer to our detailed series of articles on Clean Architecture. This series explains the architectural decisions, setup processes, and best practices used throughout this project.
2222

23+
# Work in Progress
24+
2325
1. [Clean Architecture: Introduction to the Project Structure]() - High-level structure and role of each layer.
2426
2. [Clean Architecture: Implementing AutoMapper for DTO Mapping and Audit Logging]() - Utilizing AutoMapper to handle data mapping and audit tracking.
2527
3. [Clean Architecture: Validating Inputs with FluentValidation]() - Ensuring robust input validation using FluentValidation.
@@ -58,20 +60,22 @@ A complete backend and frontend project structure to build on, with login, user
5860
- [ ] Docker Debug mode with hot reload for the API and UI
5961
- [ ] Docker Production version
6062

61-
6263
## Architecture
6364
The project is structured with Clean Architecture principles, separating the solution into distinct layers to ensure scalability, maintainability, and testability. This includes a modular design with API, Application, Domain, and Infrastructure layers.
6465

6566
![](documents/CleanArchitecture.png)
6667

68+
## Containers
69+
![](documents/architecture.png)
70+
6771
## Quick Start
6872
To quickly start the application, clone the repository and run Docker Compose:
6973

7074
```
7175
git clone https://github.com/nitin27may/clean-architecture-docker-dotnet-angular.git angular-dotnet
7276
cd angular-dotnet
7377
//rename .env.example to .env
74-
docker-compose -f docker-compose.yml -f docker-compose.override.yml up
78+
docker-compose up
7579
```
7680

7781
**Note** I have used SMTP send function, please update the account details
@@ -121,8 +125,8 @@ It contains sample for:
121125
8. Complete CRUD example for Contact
122126

123127

124-
**[Dockerfile for production](/Api/Dockerfile)**
125-
**[Dockerfile for development](/Api/debug.dockerfile)**
128+
**[Dockerfile for production](/backend/src/Dockerfile)**
129+
**[Dockerfile for development](/backend/src/Debug.Dockerfile)**
126130

127131

128132
## Getting started

backend/scripts/seed-data.sql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ BEGIN
193193
INSERT [dbo].[Operations]
194194
([Id], [Name], [Description], [CreatedOn], [CreatedBy], [UpdatedOn], [UpdatedBy])
195195
VALUES
196-
(N'09be3f29-6429-4089-a2a9-a17efe46cd7b', N'Write', N'Write', CAST(N'2024-08-24T02:56:23.6635113+00:00' AS DateTimeOffset), N'26402b6c-ebdd-44c3-9188-659a134819cb', NULL, NULL)
196+
(N'09be3f29-6429-4089-a2a9-a17efe46cd7b', N'Create', N'Create', CAST(N'2024-08-24T02:56:23.6635113+00:00' AS DateTimeOffset), N'26402b6c-ebdd-44c3-9188-659a134819cb', NULL, NULL)
197197

198198
END;
199199
GO
@@ -509,7 +509,7 @@ BEGIN
509509
INSERT [dbo].[Users]
510510
([Id], [FirstName], [LastName], [UserName], [Email], [Mobile], [Password], [CreatedOn], [Createdby], [UpdatedOn], [UpdatedBy])
511511
VALUES
512-
(N'424ffb80-05bf-43f8-8814-2772a5de2543', N'Sachin', N'Singh', N'sachin@gmail.com', N'sachin@gmail.com', 9833364, N'AQAAAAIAAYagAAAAEC1iNqNI7oqJKNcpJ+kYreWvBzjMxE/FWhfoDXzP5CoV60u6JHm5PwHIb3w7K7lWxw==', CAST(N'2024-09-05T14:48:17.0399044+00:00' AS DateTimeOffset), N'00000000-0000-0000-0000-000000000000', NULL, NULL)
512+
(N'424ffb80-05bf-43f8-8814-2772a5de2543', N'Sachin', N'Singh', N'reader@gmail.com', N'reader@gmail.com', 9833364, N'AQAAAAIAAYagAAAAEC1iNqNI7oqJKNcpJ+kYreWvBzjMxE/FWhfoDXzP5CoV60u6JHm5PwHIb3w7K7lWxw==', CAST(N'2024-09-05T14:48:17.0399044+00:00' AS DateTimeOffset), N'00000000-0000-0000-0000-000000000000', NULL, NULL)
513513

514514
INSERT [dbo].[Users]
515515
([Id], [FirstName], [LastName], [UserName], [Email], [Mobile], [Password], [CreatedOn], [Createdby], [UpdatedOn], [UpdatedBy])
@@ -519,7 +519,7 @@ BEGIN
519519
INSERT [dbo].[Users]
520520
([Id], [FirstName], [LastName], [UserName], [Email], [Mobile], [Password], [CreatedOn], [Createdby], [UpdatedOn], [UpdatedBy])
521521
VALUES
522-
(N'3aa35df1-2578-4ed3-a93b-8b8eb955499e', N'Vikram', N'Singh', N'vikram@gmail.com', N'vikram@gmail.com', 9833364, N'AQAAAAIAAYagAAAAEC1iNqNI7oqJKNcpJ+kYreWvBzjMxE/FWhfoDXzP5CoV60u6JHm5PwHIb3w7K7lWxw==', CAST(N'2024-08-28T20:29:02.2362893+00:00' AS DateTimeOffset), N'00000000-0000-0000-0000-000000000000', NULL, NULL)
522+
(N'3aa35df1-2578-4ed3-a93b-8b8eb955499e', N'Vikram', N'Singh', N'editor@gmail.com', N'editor@gmail.com', 9833364, N'AQAAAAIAAYagAAAAEC1iNqNI7oqJKNcpJ+kYreWvBzjMxE/FWhfoDXzP5CoV60u6JHm5PwHIb3w7K7lWxw==', CAST(N'2024-08-28T20:29:02.2362893+00:00' AS DateTimeOffset), N'00000000-0000-0000-0000-000000000000', NULL, NULL)
523523

524524
END;
525525
GO
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Contact.Api.Core.Authorization;
2+
using Contact.Application.Interfaces;
3+
using Microsoft.AspNetCore.Authorization;
4+
using Microsoft.Extensions.Options;
5+
6+
namespace Contact.Api.Core.Middleware;
7+
public class CustomAuthorizationPolicyProvider : IAuthorizationPolicyProvider
8+
{
9+
private readonly DefaultAuthorizationPolicyProvider _fallbackPolicyProvider;
10+
private readonly IServiceProvider _serviceProvider;
11+
12+
public CustomAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IServiceProvider serviceProvider)
13+
{
14+
_fallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
15+
_serviceProvider = serviceProvider;
16+
}
17+
18+
public async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
19+
{
20+
// Use a scope to resolve scoped services
21+
using (var scope = _serviceProvider.CreateScope())
22+
{
23+
var permissionService = scope.ServiceProvider.GetRequiredService<IPermissionService>();
24+
var permissionMappings = await permissionService.GetAllPageOperationMappingsAsync();
25+
26+
if (permissionMappings.Any(mapping => $"{mapping.PageName}.{mapping.OperationName}Policy" == policyName))
27+
{
28+
var policy = new AuthorizationPolicyBuilder();
29+
policy.AddRequirements(new PermissionRequirement(policyName));
30+
return policy.Build();
31+
}
32+
}
33+
34+
// Fallback to the default provider for other policies
35+
return await _fallbackPolicyProvider.GetPolicyAsync(policyName);
36+
}
37+
38+
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
39+
{
40+
return _fallbackPolicyProvider.GetDefaultPolicyAsync();
41+
}
42+
43+
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
44+
{
45+
return _fallbackPolicyProvider.GetFallbackPolicyAsync();
46+
}
47+
}

backend/src/Contact.Api/Program.cs

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,10 @@
5757
// },
5858
//};
5959
});
60-
builder.Services.AddAuthorization(options =>
61-
{
62-
// Dynamically add policies based on permissions
63-
var permissionService = builder.Services.BuildServiceProvider().GetRequiredService<IPermissionService>();
64-
var permissionMappings = permissionService.GetAllPageOperationMappingsAsync().Result;
6560

66-
foreach (var mapping in permissionMappings)
67-
{
68-
var policyName = $"{mapping.PageName}.{mapping.OperationName}Policy";
69-
options.AddPolicy(policyName, policy =>
70-
{
71-
policy.Requirements.Add(new PermissionRequirement(policyName));
72-
});
73-
}
74-
});
75-
builder.Services.AddSingleton<IAuthorizationHandler, PermissionHandler>();
61+
builder.Services.AddSingleton<IAuthorizationPolicyProvider, CustomAuthorizationPolicyProvider>();
62+
builder.Services.AddScoped<IAuthorizationHandler, PermissionHandler>();
63+
7664

7765

7866
builder.Services.AddControllers();

backend/src/Contact.Application/Services/UserService.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@ public async Task<User> Create(CreateUser createUser)
108108
}
109109

110110

111-
112111
public async Task<bool> Delete(Guid id)
113112
{
114113
return await _userRepository.Delete(id);
@@ -272,7 +271,6 @@ public async Task<bool> ForgotPassword(string email)
272271
return true;
273272
}
274273

275-
276274
public async Task<bool> ResetPassword(ResetPassword resetPasswordRequest)
277275
{
278276
// Step 1: Validate the reset token // token send as part of email

backend/src/Dockerfile

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ RUN groupadd -g 2000 dotnet \
99
&& useradd -m -u 2000 -g 2000 dotnet
1010
USER dotnet
1111

12-
EXPOSE 8081
13-
1412

1513
# This stage is used to build the service project
16-
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
14+
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
1715
ARG BUILD_CONFIGURATION=Release
16+
ARG DOTNET_SKIP_POLICY_LOADING=true
1817
WORKDIR /src
1918
COPY ["Contact.Api/Contact.Api.csproj", "Contact.Api/"]
2019
COPY ["Contact.Application/Contact.Application.csproj", "Contact.Application/"]
@@ -27,6 +26,8 @@ COPY . .
2726
WORKDIR "/src/Contact.Api"
2827
RUN dotnet build "./Contact.Api.csproj" -c $BUILD_CONFIGURATION -o /app/build
2928

29+
RUN ls /app/build
30+
3031
# This stage is used to publish the service project to be copied to the final stage
3132
FROM build AS publish
3233
ARG BUILD_CONFIGURATION=Release
@@ -35,5 +36,6 @@ RUN dotnet publish "./Contact.Api.csproj" -c $BUILD_CONFIGURATION -o /app/publis
3536
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
3637
FROM base AS final
3738
WORKDIR /app
39+
# COPY --from=publish /app/publish/Contact.Api.dll .
3840
COPY --from=publish /app/publish .
3941
ENTRYPOINT ["dotnet", "Contact.Api.dll"]

docker-compose-nginx.yml renamed to docker-compose.debug.yml

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,35 @@
1-
# Please refer https://aka.ms/HTTPSinContainer on how to setup an https developer certificate for your ASP.NET Core service.
2-
3-
version: '3.4'
41

52
services:
63
frontend:
74
build:
85
context: ./frontend
9-
dockerfile: Dockerfile
10-
# ports:
11-
# - 8080:8080
6+
dockerfile: Debug.Dockerfile
7+
command: ["npm", "run", "start:debug"]
8+
ports:
9+
- 4200:4200
10+
- 49153:49153
11+
volumes:
12+
- ./frontend:/app
13+
- /app/node_modules
14+
stdin_open: true
15+
tty: true
1216
depends_on:
1317
- api
1418
networks:
1519
- mssql_network
16-
20+
1721
api:
1822
build:
1923
context: ./backend/src
2024
dockerfile: Debug.Dockerfile
21-
args:
22-
- configuration=Debug
23-
# ports:
24-
# - 5000:5000
25+
command: ["dotnet", "watch", "--project", "Contact.Api/Contact.Api.csproj", "run", "--urls", "http://0.0.0.0:5000"]
26+
ports:
27+
- 5000:5000
28+
29+
2530
environment:
2631
- ASPNETCORE__ENVIRONMENT=${ENVIRONMENT}
32+
- DOTNET_SKIP_POLICY_LOADING=false
2733
- AppSettings__ConnectionStrings__DefaultConnection=Server=${SQL_SERVER};Database=${SQL_DATABASE};User ID=${SQL_USER};Password=${SQL_PASSWORD};Trusted_Connection=False;Encrypt=False;
2834
- AppSettings__Secret=${JWT_SECRET}
2935
- AppSettings__Issuer=${JWT_ISSUER}
@@ -63,19 +69,6 @@ services:
6369
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SQL_PASSWORD} -d master -i /scripts/seed-data.sql; wait
6470
networks:
6571
- mssql_network
66-
67-
nginx: #name of the fourth service
68-
build: loadbalancer # specify the directory of the Dockerfile
69-
container_name: nginx
70-
restart: always
71-
ports:
72-
- "80:80" #specify ports forewarding
73-
depends_on:
74-
- frontend
75-
- api
76-
networks:
77-
- mssql_network
78-
7972

8073
volumes:
8174
mssql_data: # Named volume to persist data

docker-compose.override.yml

Lines changed: 0 additions & 24 deletions
This file was deleted.

docker-compose.yml

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
1-
21
services:
32
frontend:
43
build:
54
context: ./frontend
65
dockerfile: Dockerfile
7-
ports:
8-
- 4000:80
9-
volumes:
10-
- ./frontend:/app
6+
# ports:
7+
# - 8080:8080
118
depends_on:
129
- api
1310
networks:
1411
- mssql_network
15-
12+
1613
api:
1714
build:
1815
context: ./backend/src
19-
dockerfile: Debug.Dockerfile
16+
dockerfile: Dockerfile
2017
args:
21-
- configuration=Debug
22-
ports:
23-
- 5000:5000
18+
- configuration=Release
19+
# ports:
20+
# - 8000:8000
2421
environment:
2522
- ASPNETCORE__ENVIRONMENT=${ENVIRONMENT}
2623
- AppSettings__ConnectionStrings__DefaultConnection=Server=${SQL_SERVER};Database=${SQL_DATABASE};User ID=${SQL_USER};Password=${SQL_PASSWORD};Trusted_Connection=False;Encrypt=False;
@@ -34,9 +31,6 @@ services:
3431
- SmtpSettings__Password=${SMTP_PASSWORD}
3532
- SmtpSettings__FromEmail=${SMTP_FROM_EMAIL}
3633
- SmtpSettings__EnableSsl=${SMTP_ENABLE_SSL}
37-
volumes:
38-
- ./backend/src:/app
39-
- ~/.vsdbg:/remote_debugger:rw
4034
depends_on:
4135
- mssql
4236
networks:
@@ -62,6 +56,19 @@ services:
6256
/opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P ${SQL_PASSWORD} -d master -i /scripts/seed-data.sql; wait
6357
networks:
6458
- mssql_network
59+
60+
nginx: #name of the fourth service
61+
build: loadbalancer # specify the directory of the Dockerfile
62+
container_name: nginx
63+
restart: always
64+
ports:
65+
- "80:80" #specify ports forewarding
66+
depends_on:
67+
- frontend
68+
- api
69+
networks:
70+
- mssql_network
71+
6572

6673
volumes:
6774
mssql_data: # Named volume to persist data

0 commit comments

Comments
 (0)