Skip to content

Commit e40cb5f

Browse files
2 parents fd48a39 + 09ed5f3 commit e40cb5f

File tree

90 files changed

+7438
-286
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+7438
-286
lines changed

API_DOCUMENTATION.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,30 @@ Content-Type: application/json
691691
# - Sold = 3
692692
```
693693

694+
### **21.1 PUT /api/vehicles/{id}/license-plate**
695+
**Update Vehicle License Plate** *(Dealer/Admin only)*
696+
697+
```bash
698+
# Request
699+
PUT /api/vehicles/507f1f77bcf86cd799439011/license-plate
700+
Authorization: Bearer <jwt-token>
701+
Content-Type: application/json
702+
703+
{
704+
"licensePlate": "XYZ-789"
705+
}
706+
707+
# Response (200 OK)
708+
{
709+
"message": "Vehicle license plate updated successfully"
710+
}
711+
712+
# Notes:
713+
# - Updates the license plate for the specified vehicle
714+
# - Can be used independently or combined with service order status updates
715+
# - License plate is automatically updated when completing service orders with licensePlate parameter
716+
```
717+
694718
### **22. DELETE /api/vehicles/{id}**
695719
**Delete Vehicle** *(Admin only)*
696720

@@ -974,13 +998,17 @@ Content-Type: application/json
974998
}
975999

9761000
# Service Order Status Values (numeric):
977-
# - InProgress = 1
978-
# - Completed = 2
979-
# - Cancelled = 3
1001+
# - Scheduled = 1
1002+
# - InProgress = 2
1003+
# - Completed = 3
1004+
# - Cancelled = 4
9801005

9811006
# On Completed:
9821007
# - Service order marked complete
983-
# - If licensePlate provided, vehicle updated
1008+
# - Billing document automatically created
1009+
# - If licensePlate provided, vehicle updated (for any status)
1010+
# - For PreDelivery: Order completed + Vehicle sold + Billing = Service Cost + Order Amount
1011+
# - For Maintenance/Repair: Billing = Service Cost Only
9841012
```
9851013
9861014
---

DOCKER_SETUP.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Vehicle Showroom Management API
2+
3+
## 🚀 Quick Start
4+
5+
### Using Docker Compose (Recommended)
6+
```bash
7+
# Build and run the application
8+
docker-compose up --build
9+
10+
# Run in background
11+
docker-compose up -d --build
12+
13+
# View logs
14+
docker-compose logs -f
15+
16+
# Stop the application
17+
docker-compose down
18+
```
19+
20+
### Using Docker directly
21+
```bash
22+
# Build the image
23+
docker build -t vehicleshowroom-api .
24+
25+
# Run the container
26+
docker run -p 10000:10000 \
27+
-e ASPNETCORE_ENVIRONMENT=Production \
28+
-e DOTNET_RUNNING_IN_CONTAINER=true \
29+
-v $(pwd)/data/keys:/app/keys \
30+
-v $(pwd)/logs:/app/logs \
31+
vehicleshowroom-api
32+
```
33+
34+
## 🔧 Configuration
35+
36+
### Environment Variables
37+
- `ASPNETCORE_ENVIRONMENT`: Set to `Production` for production deployment
38+
- `DOTNET_RUNNING_IN_CONTAINER`: Set to `true` when running in Docker
39+
- `PORT`: Port number (default: 10000)
40+
41+
### Data Protection Keys
42+
The application automatically creates and persists data protection keys in `/app/keys` directory when running in Docker. This ensures:
43+
- ✅ Keys persist across container restarts
44+
- ✅ No warnings about ephemeral key storage
45+
- ✅ Secure key management
46+
47+
### HTTPS Configuration
48+
- **Development**: HTTPS redirection is enabled
49+
- **Production/Docker**: HTTPS redirection is disabled (HTTP only)
50+
- **Health Check**: Available at `http://localhost:10000/health`
51+
52+
## 📊 Monitoring
53+
54+
### Health Check Endpoint
55+
```bash
56+
curl http://localhost:10000/health
57+
```
58+
59+
### API Documentation
60+
- **Swagger UI**: `http://localhost:10000/swagger`
61+
- **OpenAPI Spec**: `http://localhost:10000/swagger/v1/swagger.json`
62+
63+
## 🐛 Troubleshooting
64+
65+
### Common Issues Fixed
66+
1. **Data Protection Keys Warning**: ✅ Fixed with persistent key storage
67+
2. **HTTPS Redirect Warning**: ✅ Fixed with conditional HTTPS redirection
68+
3. **Port Configuration**: ✅ Properly configured for Docker
69+
70+
### Logs
71+
Application logs are written to:
72+
- Console output
73+
- `/app/logs/vehicleshowroom-*.txt` files
74+
75+
### Debugging
76+
```bash
77+
# View container logs
78+
docker logs <container-id>
79+
80+
# Access container shell
81+
docker exec -it <container-id> /bin/bash
82+
83+
# Check health status
84+
curl http://localhost:10000/health
85+
```
86+
87+
## 🔒 Security Notes
88+
89+
- Data protection keys are stored in `/app/keys` directory
90+
- Keys are automatically created with 90-day lifetime
91+
- Application name is set to "VehicleShowroomManagement"
92+
- CORS is configured for frontend integration

Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,7 @@ FROM base AS final
4141
WORKDIR /app
4242
COPY --from=publish /app/publish .
4343

44+
# Create keys directory for data protection
45+
RUN mkdir -p /app/keys && chmod 755 /app/keys
46+
4447
ENTRYPOINT ["dotnet", "VehicleShowroomManagement.WebAPI.dll"]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace VehicleShowroomManagement.Application.Common.Configuration
4+
{
5+
/// <summary>
6+
/// Cloudinary configuration settings with validation
7+
/// </summary>
8+
public class CloudinarySettings
9+
{
10+
[Required(ErrorMessage = "Cloudinary Cloud Name is required")]
11+
public string CloudName { get; set; } = string.Empty;
12+
13+
[Required(ErrorMessage = "Cloudinary API Key is required")]
14+
public string ApiKey { get; set; } = string.Empty;
15+
16+
[Required(ErrorMessage = "Cloudinary API Secret is required")]
17+
public string ApiSecret { get; set; } = string.Empty;
18+
}
19+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
using Microsoft.Extensions.Configuration;
2+
using Microsoft.Extensions.Logging;
3+
using MongoDB.Driver;
4+
using System.Text.RegularExpressions;
5+
6+
namespace VehicleShowroomManagement.Application.Common.Configuration
7+
{
8+
/// <summary>
9+
/// Validates application configuration at startup
10+
/// </summary>
11+
public static class ConfigurationValidator
12+
{
13+
/// <summary>
14+
/// Validates all required configuration settings
15+
/// </summary>
16+
public static async Task<bool> ValidateConfigurationAsync(IConfiguration configuration, ILogger logger)
17+
{
18+
var isValid = true;
19+
var errors = new List<string>();
20+
21+
try
22+
{
23+
// Validate MongoDB connection
24+
var mongoConnectionString = configuration.GetConnectionString("MongoDB");
25+
if (string.IsNullOrWhiteSpace(mongoConnectionString))
26+
{
27+
errors.Add("MongoDB connection string is missing");
28+
isValid = false;
29+
}
30+
else
31+
{
32+
// Test MongoDB connection
33+
try
34+
{
35+
var client = new MongoClient(mongoConnectionString);
36+
await client.ListDatabaseNamesAsync();
37+
logger.LogInformation("MongoDB connection validated successfully");
38+
}
39+
catch (Exception ex)
40+
{
41+
errors.Add($"MongoDB connection failed: {ex.Message}");
42+
isValid = false;
43+
}
44+
}
45+
46+
// Validate JWT settings
47+
var jwtKey = configuration["Jwt:Key"];
48+
if (string.IsNullOrWhiteSpace(jwtKey))
49+
{
50+
errors.Add("JWT Key is missing");
51+
isValid = false;
52+
}
53+
else if (jwtKey.Length < 32)
54+
{
55+
errors.Add("JWT Key must be at least 32 characters long");
56+
isValid = false;
57+
}
58+
59+
var jwtIssuer = configuration["Jwt:Issuer"];
60+
if (string.IsNullOrWhiteSpace(jwtIssuer))
61+
{
62+
errors.Add("JWT Issuer is missing");
63+
isValid = false;
64+
}
65+
66+
var jwtAudience = configuration["Jwt:Audience"];
67+
if (string.IsNullOrWhiteSpace(jwtAudience))
68+
{
69+
errors.Add("JWT Audience is missing");
70+
isValid = false;
71+
}
72+
73+
// Validate Email settings
74+
var smtpHost = configuration["EmailSettings:SmtpHost"];
75+
if (string.IsNullOrWhiteSpace(smtpHost))
76+
{
77+
errors.Add("Email SMTP Host is missing");
78+
isValid = false;
79+
}
80+
81+
var smtpPort = configuration["EmailSettings:SmtpPort"];
82+
if (!int.TryParse(smtpPort, out var port) || port < 1 || port > 65535)
83+
{
84+
errors.Add("Email SMTP Port must be a valid port number (1-65535)");
85+
isValid = false;
86+
}
87+
88+
var smtpUsername = configuration["EmailSettings:SmtpUsername"];
89+
if (string.IsNullOrWhiteSpace(smtpUsername))
90+
{
91+
errors.Add("Email SMTP Username is missing");
92+
isValid = false;
93+
}
94+
95+
var smtpPassword = configuration["EmailSettings:SmtpPassword"];
96+
if (string.IsNullOrWhiteSpace(smtpPassword))
97+
{
98+
errors.Add("Email SMTP Password is missing");
99+
isValid = false;
100+
}
101+
102+
var fromEmail = configuration["EmailSettings:FromEmail"];
103+
if (string.IsNullOrWhiteSpace(fromEmail))
104+
{
105+
errors.Add("Email From Email is missing");
106+
isValid = false;
107+
}
108+
else if (!IsValidEmail(fromEmail))
109+
{
110+
errors.Add("Email From Email is not a valid email address");
111+
isValid = false;
112+
}
113+
114+
// Validate Cloudinary settings
115+
var cloudName = configuration["CloudinarySettings:CloudName"];
116+
if (string.IsNullOrWhiteSpace(cloudName))
117+
{
118+
errors.Add("Cloudinary Cloud Name is missing");
119+
isValid = false;
120+
}
121+
122+
var apiKey = configuration["CloudinarySettings:ApiKey"];
123+
if (string.IsNullOrWhiteSpace(apiKey))
124+
{
125+
errors.Add("Cloudinary API Key is missing");
126+
isValid = false;
127+
}
128+
129+
var apiSecret = configuration["CloudinarySettings:ApiSecret"];
130+
if (string.IsNullOrWhiteSpace(apiSecret))
131+
{
132+
errors.Add("Cloudinary API Secret is missing");
133+
isValid = false;
134+
}
135+
136+
// Log validation results
137+
if (isValid)
138+
{
139+
logger.LogInformation("All configuration settings validated successfully");
140+
}
141+
else
142+
{
143+
logger.LogError("Configuration validation failed with {ErrorCount} errors", errors.Count);
144+
foreach (var error in errors)
145+
{
146+
logger.LogError("Configuration Error: {Error}", error);
147+
}
148+
}
149+
}
150+
catch (Exception ex)
151+
{
152+
logger.LogError(ex, "Unexpected error during configuration validation");
153+
isValid = false;
154+
}
155+
156+
return isValid;
157+
}
158+
159+
private static bool IsValidEmail(string email)
160+
{
161+
try
162+
{
163+
var regex = new Regex(@"^[^@\s]+@[^@\s]+\.[^@\s]+$");
164+
return regex.IsMatch(email);
165+
}
166+
catch
167+
{
168+
return false;
169+
}
170+
}
171+
}
172+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.ComponentModel.DataAnnotations;
2+
3+
namespace VehicleShowroomManagement.Application.Common.Configuration
4+
{
5+
/// <summary>
6+
/// Email configuration settings with validation
7+
/// </summary>
8+
public class EmailSettings
9+
{
10+
[Required(ErrorMessage = "SMTP Host is required")]
11+
public string SmtpHost { get; set; } = string.Empty;
12+
13+
[Range(1, 65535, ErrorMessage = "SMTP Port must be between 1 and 65535")]
14+
public int SmtpPort { get; set; } = 587;
15+
16+
[Required(ErrorMessage = "SMTP Username is required")]
17+
public string SmtpUsername { get; set; } = string.Empty;
18+
19+
[Required(ErrorMessage = "SMTP Password is required")]
20+
public string SmtpPassword { get; set; } = string.Empty;
21+
22+
public bool EnableSsl { get; set; } = true;
23+
24+
[Required(ErrorMessage = "From Email is required")]
25+
[EmailAddress(ErrorMessage = "From Email must be a valid email address")]
26+
public string FromEmail { get; set; } = string.Empty;
27+
28+
[Required(ErrorMessage = "From Name is required")]
29+
public string FromName { get; set; } = string.Empty;
30+
}
31+
}

0 commit comments

Comments
 (0)