Skip to content

Commit 5c60010

Browse files
Merge pull request #2 from Mariomedhat899/Dev.SmtpConfigurations
Dev.smtp configurations
2 parents e682611 + 1bba40a commit 5c60010

14 files changed

Lines changed: 242 additions & 25 deletions

File tree

IMS.API/Controllers/TransactionsController.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
using IMS.API.Models.TransactionsDtos;
2+
using IMS.Core.Contracts;
23
using IMS.Core.Entities;
34
using IMS.Infrastructure.Data;
5+
using IMS.Infrastructure.Services;
46
using Microsoft.AspNetCore.Authorization;
57
using Microsoft.AspNetCore.Mvc;
68
using Microsoft.EntityFrameworkCore;
9+
using Microsoft.Extensions.Options;
710
using System.Security.Claims;
811

912

1013
namespace IMS.API.Controllers
1114
{
1215
[Route("api/[controller]")]
1316
[ApiController]
14-
public class TransactionsController(ApplicationDbContext _context) : ControllerBase
17+
public class TransactionsController(ApplicationDbContext _context
18+
, IEmailService emailService,
19+
IOptions<NotificationSettings> notificationSettings) : ControllerBase
1520
{
1621

22+
private readonly IEmailService _emailService = emailService;
23+
private readonly NotificationSettings _notificationSettings = notificationSettings.Value;
24+
1725
[HttpGet]
1826
[Authorize(Roles = "Admin,Manager")]
1927
public async Task<IActionResult> GetTransactions()
@@ -63,6 +71,23 @@ public async Task<IActionResult> RecordTransaction([FromBody] CreateTransactionD
6371
return BadRequest("Insufficient stock for this sale!!");
6472

6573
product.QuantityInStock -= model.Quantity;
74+
if (product.QuantityInStock <= _notificationSettings.LowStockThreshold)
75+
{
76+
var subject = $"Low Stock Alert: {product.Name}";
77+
var body = $"Product '{product.Name}'(ID: {product.Id}) has low stock. \n" +
78+
$"Current quantity: {product.QuantityInStock}\n" +
79+
$"Threshold: {_notificationSettings.LowStockThreshold}\n" +
80+
$"Please restock this item soon";
81+
82+
try
83+
{
84+
await _emailService.SendEmailAsync(_notificationSettings.AdminEmail, subject, body);
85+
}
86+
catch (Exception ex)
87+
{
88+
Console.WriteLine($"Failed to send low stock email: {ex.Message}");
89+
}
90+
}
6691
}
6792
else
6893
{

IMS.API/IMS.API.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
<TargetFramework>net8.0</TargetFramework>
55
<Nullable>enable</Nullable>
66
<ImplicitUsings>enable</ImplicitUsings>
7+
<UserSecretsId>3746ec4c-ff25-44f1-8235-94d8fb6bffb6</UserSecretsId>
78
</PropertyGroup>
89
<ItemGroup>
9-
<None Include="..\IMS.Infrastructure\Data\SeedData\**\*"
10-
Link="Data\SeedData\%(Filename)%(Extension)">
10+
<None Include="..\IMS.Infrastructure\Data\SeedData\**\*" Link="Data\SeedData\%(Filename)%(Extension)">
1111
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
1212
</None>
1313
</ItemGroup>

IMS.API/Models/ReportsDtos/InventoryReportDto.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ public class InventoryReportDto
66

77
public int TotalProducts { get; set; }
88

9-
public ReportSummaryDto Sales { get; set; }
9+
public ReportSummaryDto Sales { get; set; } = new ReportSummaryDto();
1010

11-
public ReportSummaryDto Purchases { get; set; }
11+
public ReportSummaryDto Purchases { get; set; } = new ReportSummaryDto();
1212

1313
public List<TopProductDto> TopSellingProducts { get; set; } = new List<TopProductDto>();
1414
}

IMS.API/Program.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using IMS.API.Services;
2+
using IMS.Core.Contracts;
23
using IMS.Core.Entities;
34
using IMS.Infrastructure.Data;
5+
using IMS.Infrastructure.Services;
46
using Microsoft.AspNetCore.Authentication.JwtBearer;
57
using Microsoft.AspNetCore.Identity;
68
using Microsoft.EntityFrameworkCore;
@@ -13,6 +15,7 @@
1315

1416

1517

18+
1619
var builder = WebApplication.CreateBuilder(args);
1720

1821
builder.Services.AddSingleton<CsvService>();
@@ -81,6 +84,12 @@
8184
});
8285
});
8386

87+
88+
89+
builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("SmtpSettings"));
90+
builder.Services.AddScoped<IEmailService, SmtpEmailService>();
91+
builder.Services.Configure<NotificationSettings>(builder.Configuration.GetSection("NotificationSettings"));
92+
8493
var app = builder.Build();
8594

8695
// Configure the HTTP request pipeline.
@@ -96,7 +105,17 @@
96105
app.UseAuthorization();
97106
app.MapControllers();
98107

99-
await RoleSeeder.SeedRolesAsync(app.Services);
100-
await DataSeeder.SeedDataAsync(app.Services);
108+
109+
110+
111+
using (var seedScope = app.Services.CreateScope())
112+
{
113+
114+
var dbContext = seedScope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
115+
await RoleSeeder.SeedRolesAsync(dbContext, seedScope.ServiceProvider);
116+
117+
await DataSeeder.SeedDataAsync(seedScope.ServiceProvider);
118+
}
119+
101120

102121
app.Run();

IMS.API/Services/CsvService.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,32 @@ public string ExportProductsToCsv(IEnumerable<Product> products)
1111
{
1212
using var writer = new StringWriter();
1313
using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
14-
15-
csv.WriteHeader<Product>();
14+
csv.WriteField("Id");
15+
csv.WriteField("Name");
16+
csv.WriteField("Descrption");
17+
csv.WriteField("Price");
18+
csv.WriteField("QuantityInStock");
19+
csv.WriteField("Supplier");
20+
csv.WriteField("CategoryId");
21+
csv.WriteField("CreatedAt");
22+
csv.WriteField("LastUpdatedAt");
1623
csv.NextRecord();
1724

25+
26+
1827
foreach (var product in products)
1928
{
20-
csv.WriteRecord(product);
29+
csv.WriteField(product.Id);
30+
csv.WriteField(product.Name);
31+
csv.WriteField(product.Description);
32+
csv.WriteField(product.Price);
33+
csv.WriteField(product.QuantityInStock);
34+
csv.WriteField(product.Supplier);
35+
csv.WriteField(product.CategoryId);
36+
csv.WriteField(product.CreatedAt);
37+
csv.WriteField(product.LastUpdatedAt);
2138
csv.NextRecord();
39+
2240
}
2341
;
2442

IMS.API/appsettings.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,21 @@
1616
"Audience": "InventoryManagementApi",
1717
"SecretKey": "YourSuperSecretKey123456789012345678901234567890",
1818
"ExpiryMinutes": 60
19+
},
20+
"SmtpSettings": {
21+
"Host": "smtp.gmail.com",
22+
"Port": 587,
23+
"Username": "",
24+
"Password": "",
25+
"EnableSsl": true,
26+
"FromEmail": "",
27+
"FromName": "IMS Inventory System"
28+
},
29+
"NotificationSettings": {
30+
"AdminEmail": "mariomedhat899@gmail.com",
31+
"LowStockThreshold" : 10
1932
}
2033

2134
}
35+
36+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace IMS.Core.Contracts
2+
{
3+
public interface IEmailService
4+
{
5+
public Task SendEmailAsync(string email, string subject, string body);
6+
}
7+
}

IMS.Core/Entities/Category.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
public class Category : BaseEntity
44
{
55

6-
public string Name { get; set; }
6+
public string Name { get; set; } = string.Empty;
77
public string? Description { get; set; }
88

99
public ICollection<Product>? Products { get; set; }

IMS.Infrastructure/Data/DataSeeder.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,55 @@ public static async Task SeedDataAsync(IServiceProvider service)
3737

3838
await context.SaveChangesAsync();
3939

40-
40+
var categoryMap = await context.categories
41+
.ToDictionaryAsync(c => c.Name!, c => c.Id);
4142

4243
var productPath = Path.Combine(AppContext.BaseDirectory, "Data", "SeedData", "Products.json");
4344

4445
var productsData = await File.ReadAllTextAsync(productPath);
46+
var productOptions = new JsonSerializerOptions
47+
{
48+
PropertyNameCaseInsensitive = true
49+
};
4550

46-
var products = JsonSerializer.Deserialize<List<Product>>(productsData);
51+
var products = JsonSerializer.Deserialize<List<ProductSeedDto>>(productsData, productOptions);
4752

4853
if (products is null) return;
54+
foreach (var product in products)
55+
{
56+
if (!categoryMap.TryGetValue(product.CategoryName, out int categoryID))
57+
continue;
58+
59+
context.Products.Add(new Product
60+
{
61+
Name = product.Name,
62+
Description = product.Description,
63+
Price = product.Price,
64+
QuantityInStock = product.QuantityInStock,
65+
Supplier = product.Supplier,
66+
CategoryId = categoryID,
67+
});
68+
69+
}
70+
;
4971

50-
await context.Products.AddRangeAsync(products);
5172

5273
await context.SaveChangesAsync();
5374

5475

5576

77+
}
78+
private class ProductSeedDto
79+
{
80+
81+
public string Name { get; set; } = string.Empty;
82+
public string Description { get; set; } = string.Empty;
83+
public decimal Price { get; set; }
84+
public int QuantityInStock { get; set; }
85+
public string Supplier { get; set; } = string.Empty;
86+
public string CategoryName { get; set; } = string.Empty;
87+
5688
}
5789
}
5890
}
91+

IMS.Infrastructure/Data/RoleSeeder.cs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
using IMS.Core.Entities;
22
using Microsoft.AspNetCore.Identity;
3+
using Microsoft.EntityFrameworkCore;
34
using Microsoft.Extensions.DependencyInjection;
45

56
namespace IMS.Infrastructure.Data
67
{
78
public class RoleSeeder
89
{
9-
public static async Task SeedRolesAsync(IServiceProvider serviceProvider)
10+
public static async Task SeedRolesAsync(ApplicationDbContext _context, IServiceProvider serviceProvider)
1011
{
12+
if (_context.Database.GetPendingMigrationsAsync().GetAwaiter().GetResult().Any())
13+
await _context.Database.MigrateAsync();
1114

1215
using var scope = serviceProvider.CreateScope();
1316
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
@@ -44,6 +47,38 @@ public static async Task SeedRolesAsync(IServiceProvider serviceProvider)
4447
await userManager.AddToRoleAsync(adminUser, "Admin");
4548
}
4649
}
50+
var managerEmail = "Manager@test.com";
51+
var managerUser = await userManager.FindByEmailAsync(managerEmail);
52+
if (managerUser is null)
53+
{
54+
managerUser = new ApplicationUser
55+
{
56+
UserName = "manager",
57+
Email = managerEmail,
58+
EmailConfirmed = true
59+
};
60+
61+
var managerResult = await userManager.CreateAsync(managerUser, "Manager@123");
62+
if (managerResult.Succeeded)
63+
{
64+
await userManager.AddToRoleAsync(managerUser, "Manager");
65+
}
66+
}
67+
68+
69+
var staffEmail = "staff@test.com";
70+
var staffUser = await userManager.FindByEmailAsync(staffEmail);
71+
if (staffUser is null)
72+
{
73+
staffUser = new ApplicationUser
74+
{
75+
UserName = "staff",
76+
Email = staffEmail,
77+
EmailConfirmed = true
78+
};
79+
var staffResult = await userManager.CreateAsync(staffUser, "Staff@123");
80+
if (staffResult.Succeeded) await userManager.AddToRoleAsync(staffUser, "Staff");
81+
}
4782

4883

4984

0 commit comments

Comments
 (0)