Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e8ab3e2

Browse files
authoredNov 25, 2024··
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

‎docker-mssql-compose.yml

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

‎documents/architecture.drawio

Lines changed: 134 additions & 1 deletion
Large diffs are not rendered by default.

‎documents/architecture.png

-162 KB
Loading

‎frontend/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
FROM node:22-alpine as builder
33

44
# Copy dependency definitions
5-
COPY package.json package-lock.json ./
5+
COPY package*.json ./
66

77
## installing and Storing node modules on a separate layer will prevent unnecessary npm installs at each build
88
## --legacy-peer-deps as ngx-bootstrap still depends on Angular 14

‎frontend/debug.dockerfile

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ FROM node:22-alpine
44
#RUN echo "nameserver 8.8.8.8" | tee /etc/resolv.conf > /dev/null
55
WORKDIR /app
66

7+
# RUN npm install -g @angular/cli@latest
78
# Copy dependency definitions
8-
COPY package*.json ./
9+
COPY package.json .
910

1011
## installing and Storing node modules on a separate layer will prevent unnecessary npm installs at each build
11-
RUN npm i
12+
RUN npm install --legacy-peer-deps
1213

13-
RUN npm install -g @angular/cli
14-
15-
COPY . /app/
14+
COPY . .
1615

1716
EXPOSE 4200 49153
1817

18+
# Start the application in debug mode
19+
# CMD ["npm", "run", "start:debug"]
20+

‎frontend/package-lock.json

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

‎frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"ng": "ng",
66
"start": "ng serve",
77
"serve": "ng serve --proxy-config proxy.conf.json",
8+
"start:debug": "ng serve --configuration development --proxy-config proxy.conf.json --host 0.0.0.0 --disable-host-check --poll 2000 --verbose",
89
"build": "ng build",
910
"watch": "ng build --watch --configuration development",
1011
"test": "ng test",

‎frontend/proxy.conf.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
22
"/api": {
3-
"target": "http://localhost:5217",
3+
"target": "http://api:5000",
44
"secure": false,
5+
"changeOrigin": true,
56
"logLevel": "debug"
67
}
78
}

‎frontend/src/app/@core/guards/auth.guard.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,33 @@ export class AuthGuard {
1515
if (typeof window !== 'undefined') {
1616
if (localStorage.getItem('currentUser')) {
1717
// logged in so return true
18-
return true;
18+
const userObject = JSON.parse(localStorage.getItem('currentUser'));
19+
20+
// Extract the token
21+
const token = userObject.token;
22+
23+
if (!token) {
24+
console.error('Token is missing');
25+
return this.InvalidSession(state);
26+
} else {
27+
const payload = JSON.parse(atob(token.split('.')[1])); // Decode the payload
28+
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
29+
if (payload.exp < currentTime){
30+
return this.InvalidSession(state);
31+
} else {
32+
return true;
33+
}
1934
}
2035
}
2136
// not logged in so redirect to login page with the return url
22-
this.router.navigate(['/login'], {
23-
queryParams: { returnUrl: state.url },
24-
});
25-
return false;
37+
return this.InvalidSession(state);
2638
}
39+
}
40+
41+
private InvalidSession(state: RouterStateSnapshot) {
42+
this.router.navigate(['/login'], {
43+
queryParams: { returnUrl: state.url },
44+
});
45+
return false;
46+
}
2747
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Pipe, PipeTransform } from '@angular/core';
2+
3+
@Pipe({
4+
name: 'phoneNumberFormat',
5+
standalone: true
6+
})
7+
export class PhoneNumberFormatPipe implements PipeTransform {
8+
transform(phone: number, countryCode: number): string {
9+
if (!phone) {
10+
return '';
11+
}
12+
13+
const formattedPhone = phone.toString().replace(/(\d{3})(\d{3})(\d{4})/, '$1-$2-$3');
14+
return `+${countryCode} ${formattedPhone}`;
15+
}
16+
}

‎frontend/src/app/feature/contact/contact-form/contact-form.component.html

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,33 @@ <h2 *ngIf="contactForm.get('id').value">
4141
formControlName="lastName"
4242
/>
4343
</div>
44+
<div class="col-sm-6 col-md-6">
45+
<label for="lastName" class="form-label"
46+
>Date Of Birth</label
47+
>
48+
<input
49+
type="date"
50+
class="form-control"
51+
formControlName="dateOfBirth"
52+
/>
53+
54+
<!-- <div class="input-group">
55+
<input
56+
type="text"
57+
class="form-control"
58+
formControlName="dateOfBirth"
59+
id="datePicker"
60+
class="form-control"
61+
placeholder="yyyy-mm-dd"
62+
63+
ngbDatepicker
64+
#dp="ngbDatepicker"
65+
/>
66+
<button class="btn btn-outline-secondary" (click)="dp.toggle()" type="button">
67+
<i class="bi bi-calendar"></i>
68+
</button>
69+
</div> -->
70+
</div>
4471
<div class="col-sm-6 col-md-6">
4572
<label for="email" class="form-label"
4673
>Email
@@ -55,11 +82,25 @@ <h2 *ngIf="contactForm.get('id').value">
5582
<label for="mobile" class="form-label"
5683
>Mobile #</label
5784
>
85+
<div class="input-group">
86+
<select
87+
class="form-select"
88+
89+
formControlName="countryCode"
90+
aria-label="Country code"
91+
style="max-width: 100px;"
92+
>
93+
<option value="1">+1</option>
94+
<option value="44">+44</option>
95+
<option value="91">+91</option>
96+
<option value="61">+61</option>
97+
</select>
5898
<input
5999
type="text"
60100
class="form-control"
61101
formControlName="mobile"
62102
/>
103+
</div>
63104
</div>
64105
<div class="col-sm-6 col-md-6">
65106
<label for="city" class="form-label"

‎frontend/src/app/feature/contact/contact-form/contact-form.component.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ import {
88
} from '@angular/forms';
99
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
1010
import { ToastrService } from 'ngx-toastr';
11+
import { NgbCalendar, NgbDatepickerModule, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap';
1112
import { ValidationService } from '../../../@core/services/validation.service';
1213
import { ContactService } from '../contact.service';
1314
import { errorTailorImports } from "../../../@core/components/validation";
1415

1516
@Component({
1617
selector: 'app-contact-form',
17-
imports: [ReactiveFormsModule, RouterModule, CommonModule, errorTailorImports],
18+
imports: [ReactiveFormsModule, RouterModule, CommonModule, errorTailorImports, NgbDatepickerModule],
1819
templateUrl: './contact-form.component.html',
1920
styleUrl: './contact-form.component.css',
2021
providers: [ContactService]
@@ -49,10 +50,12 @@ export class ContactFormComponent implements OnInit {
4950
Validators.maxLength(35),
5051
],
5152
],
53+
dateOfBirth:[],
5254
email: [
5355
'',
5456
[Validators.required, this.validationService.emailValidator],
5557
],
58+
countryCode: ['', [Validators.required]],
5659
mobile: ['', [Validators.required]],
5760
city: ['', [Validators.required]],
5861
postalCode: ['', [Validators.required]],
@@ -113,6 +116,11 @@ export class ContactFormComponent implements OnInit {
113116
const contactDetails = this.activatedRoute.snapshot.data.contactDetails;
114117
if (contactDetails) {
115118
this.contactForm.patchValue(contactDetails);
119+
this.contactForm.controls.dateOfBirth.setValue(this.formatDate(contactDetails.dateOfBirth));
116120
}
117121
}
122+
private formatDate(jsonDate: string): string {
123+
const date = new Date(jsonDate);
124+
return date.toISOString().split('T')[0]; // yyyy-MM-dd format
125+
}
118126
}

‎frontend/src/app/feature/contact/contact-list/contact-list.component.html

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,8 @@ <h2>Contact List</h2>
6161
/>
6262
</td>
6363
<td>
64-
<ngb-highlight
65-
[result]="contact.mobile"
66-
[term]="filter.value"
67-
/>
68-
</td>
64+
{{ contact.mobile | phoneNumberFormat: contact.countryCode}}
65+
</td>
6966
<td>
7067
<ngb-highlight
7168
[result]="contact.city"

‎frontend/src/app/feature/contact/contact-list/contact-list.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
44
import { Router, RouterModule } from '@angular/router';
55
import { NgbHighlight, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
66
import { ContactService } from '../contact.service';
7+
import { PhoneNumberFormatPipe } from "../../../@core/pipes/phoneNumberFormat.pipe";
78

89
@Component({
910
selector: 'app-contact-list',
@@ -13,6 +14,7 @@ import { ContactService } from '../contact.service';
1314
ReactiveFormsModule,
1415
NgbHighlight,
1516
NgbPaginationModule,
17+
PhoneNumberFormatPipe
1618
],
1719
templateUrl: './contact-list.component.html',
1820
styleUrl: './contact-list.component.css',

‎frontend/src/app/feature/contact/contact.resolver.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,7 @@ import { inject } from '@angular/core';
22
import { ContactService } from "./contact.service";
33
import { ResolveFn } from '@angular/router';
44

5-
// export class ContactDetailsResolver {
6-
// constructor(private contactService: ContactService) {}
7-
8-
// export const userDetailsResolver: ResolveFn<UserDetails> = (route, state) => {
9-
// let userService = inject(UserService);
10-
// return userService.getUserDetails(+route.paramMap.get('id'));
11-
// };
12-
13-
// // resolve(route: ActivatedRouteSnapshot): any {
14-
// // return this.contactService.getById(route.paramMap.get("contactId")).pipe(
15-
// // map((result: any) => {
16-
// // return result;
17-
// // })
18-
// // );
19-
// // }
20-
// }
215
export const ContactDetailsResolver: ResolveFn<any> = (route, state) => {
22-
let userService = inject(ContactService);
23-
return userService.getById(route.paramMap.get('contactId'));
6+
let contactService = inject(ContactService);
7+
return contactService.getById(route.paramMap.get('contactId'));
248
};

‎frontend/src/app/feature/user/profile/profile.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ValidationService } from '../../../@core/services/validation.service';
1818
// changeDetection: ChangeDetectionStrategy.OnPush,
1919
imports: [NgbNavModule, ReactiveFormsModule],
2020
templateUrl: './profile.component.html',
21-
styleUrls: ['./profile.component.scss']
21+
styleUrls: ['./profile.component.css']
2222
})
2323
export class ProfileComponent implements OnInit {
2424
active = 1;

‎frontend/src/app/feature/user/user.routes.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export default [
1414
children: [
1515
{
1616
path: '',
17-
1817
component: HomeComponent,
1918
},
2019
{

‎loadbalancer/default.conf

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

‎loadbalancer/nginx.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ http {
1010
upstream api {
1111
# These are references to our backend containers, facilitated by
1212
# Compose, as defined in docker-compose.yml
13-
server api:5000;
13+
server api:8000;
1414
}
1515

1616

0 commit comments

Comments
 (0)
Please sign in to comment.