feat: Add new WebClientBase Blazor application and MktZaloService API, and update WhatsAppService and MktXService infrastructure.
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WebClientBase.Shared;
|
||||
using WebClientBase.Shared.DTOs;
|
||||
|
||||
namespace WebClientBase.Server.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Authentication API controller.
|
||||
/// VI: Auth API controller.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// EN: Register a new user.
|
||||
/// VI: Đăng ký user mới.
|
||||
/// </summary>
|
||||
/// <param name="request">Registration data with validation</param>
|
||||
[HttpPost("register")]
|
||||
[ProducesResponseType(typeof(ApiResponse<UserProfileDto>), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public ActionResult<ApiResponse<UserProfileDto>> Register([FromBody] RegisterDto request)
|
||||
{
|
||||
// EN: [ApiController] validates before this code runs
|
||||
// VI: [ApiController] validate trước khi code này chạy
|
||||
|
||||
// EN: Demo - create user profile
|
||||
// VI: Demo - tạo user profile
|
||||
var profile = new UserProfileDto
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Email = request.Email,
|
||||
DisplayName = request.DisplayName,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
return CreatedAtAction(nameof(GetProfile), new { id = profile.Id }, ApiResponse<UserProfileDto>.Ok(profile));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Login with email and password.
|
||||
/// VI: Đăng nhập với email và mật khẩu.
|
||||
/// </summary>
|
||||
[HttpPost("login")]
|
||||
[ProducesResponseType(typeof(ApiResponse<UserProfileDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status401Unauthorized)]
|
||||
public ActionResult<ApiResponse<UserProfileDto>> Login([FromBody] LoginDto request)
|
||||
{
|
||||
// EN: Demo - always succeed with mock profile
|
||||
// VI: Demo - luôn thành công với profile giả
|
||||
var profile = new UserProfileDto
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Email = request.Email,
|
||||
DisplayName = "Demo User",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-30)
|
||||
};
|
||||
|
||||
return Ok(ApiResponse<UserProfileDto>.Ok(profile));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get user profile by ID.
|
||||
/// VI: Lấy user profile theo ID.
|
||||
/// </summary>
|
||||
[HttpGet("profile/{id:guid}")]
|
||||
[ProducesResponseType(typeof(ApiResponse<UserProfileDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ApiResponse), StatusCodes.Status404NotFound)]
|
||||
public ActionResult<ApiResponse<UserProfileDto>> GetProfile(Guid id)
|
||||
{
|
||||
// EN: Demo - return mock profile
|
||||
// VI: Demo - trả về profile giả
|
||||
var profile = new UserProfileDto
|
||||
{
|
||||
Id = id,
|
||||
Email = "demo@example.com",
|
||||
DisplayName = "Demo User",
|
||||
AvatarUrl = "https://example.com/avatar.jpg",
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-30)
|
||||
};
|
||||
|
||||
return Ok(ApiResponse<UserProfileDto>.Ok(profile));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WebClientBase.Shared;
|
||||
using WebClientBase.Shared.DTOs;
|
||||
|
||||
namespace WebClientBase.Server.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// EN: Products API controller with automatic ModelState validation.
|
||||
/// VI: Products API controller với validation ModelState tự động.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// EN: The [ApiController] attribute automatically validates ModelState before action executes.
|
||||
/// VI: Attribute [ApiController] tự động validate ModelState trước khi action được thực thi.
|
||||
/// </remarks>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class ProductsController : ControllerBase
|
||||
{
|
||||
// EN: In-memory storage for demo purposes
|
||||
// VI: Lưu trữ trong bộ nhớ cho mục đích demo
|
||||
private static readonly List<ProductDto> _products = new()
|
||||
{
|
||||
new ProductDto { Name = "MacBook Pro", Description = "Laptop cao cấp", Price = 2499.99m, Quantity = 10 },
|
||||
new ProductDto { Name = "iPhone 15 Pro", Description = "Điện thoại thông minh", Price = 1199.99m, Quantity = 50 }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// EN: Get all products.
|
||||
/// VI: Lấy tất cả sản phẩm.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(ApiResponse<List<ProductDto>>), StatusCodes.Status200OK)]
|
||||
public ActionResult<ApiResponse<List<ProductDto>>> GetAll()
|
||||
{
|
||||
return Ok(ApiResponse<List<ProductDto>>.Ok(_products));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Create a new product.
|
||||
/// VI: Tạo sản phẩm mới.
|
||||
/// </summary>
|
||||
/// <param name="request">Product data with validation</param>
|
||||
/// <remarks>
|
||||
/// EN: [ApiController] automatically returns 400 if ModelState is invalid.
|
||||
/// VI: [ApiController] tự động trả về 400 nếu ModelState không hợp lệ.
|
||||
/// No need for: if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||
/// </remarks>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(ApiResponse<ProductDto>), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public ActionResult<ApiResponse<ProductDto>> Create([FromBody] CreateProductDto request)
|
||||
{
|
||||
// EN: Validation is automatically done by [ApiController] before reaching here
|
||||
// VI: Validation được tự động thực hiện bởi [ApiController] trước khi đến đây
|
||||
|
||||
var product = new ProductDto
|
||||
{
|
||||
Name = request.Name,
|
||||
Description = request.Description,
|
||||
Price = request.Price,
|
||||
Quantity = request.Quantity
|
||||
};
|
||||
|
||||
_products.Add(product);
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetAll),
|
||||
ApiResponse<ProductDto>.Ok(product)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// EN: Update an existing product.
|
||||
/// VI: Cập nhật sản phẩm đã có.
|
||||
/// </summary>
|
||||
[HttpPut]
|
||||
[ProducesResponseType(typeof(ApiResponse<ProductDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public ActionResult<ApiResponse<ProductDto>> Update([FromBody] UpdateProductDto request)
|
||||
{
|
||||
// EN: Find and update (simplified for demo)
|
||||
// VI: Tìm và cập nhật (đơn giản hóa cho demo)
|
||||
var existing = _products.FirstOrDefault(p => p.Name == request.Name);
|
||||
if (existing == null)
|
||||
{
|
||||
return NotFound(ApiResponse<ProductDto>.Fail("Sản phẩm không tồn tại / Product not found"));
|
||||
}
|
||||
|
||||
existing.Description = request.Description;
|
||||
existing.Price = request.Price;
|
||||
existing.Quantity = request.Quantity;
|
||||
|
||||
return Ok(ApiResponse<ProductDto>.Ok(existing));
|
||||
}
|
||||
}
|
||||
73
apps/web-client-base-net/src/WebClientBase.Server/Program.cs
Normal file
73
apps/web-client-base-net/src/WebClientBase.Server/Program.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
/// <summary>
|
||||
/// EN: ASP.NET Core Server with Blazor WebAssembly hosting.
|
||||
/// VI: ASP.NET Core Server với Blazor WebAssembly hosting.
|
||||
/// </summary>
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// EN: Add services to the container
|
||||
// VI: Thêm các services vào container
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// EN: Add controllers with automatic ModelState validation via [ApiController]
|
||||
// VI: Thêm controllers với validation ModelState tự động qua [ApiController]
|
||||
builder.Services.AddControllers();
|
||||
|
||||
// EN: Add OpenAPI/Swagger support
|
||||
// VI: Thêm hỗ trợ OpenAPI/Swagger
|
||||
builder.Services.AddOpenApi();
|
||||
|
||||
// EN: Add CORS for Blazor WebAssembly client
|
||||
// VI: Thêm CORS cho Blazor WebAssembly client
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("BlazorClient", policy =>
|
||||
{
|
||||
policy.WithOrigins("https://localhost:5001", "http://localhost:5000")
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
// EN: Add health checks
|
||||
// VI: Thêm health checks
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
// EN: Configure the HTTP request pipeline
|
||||
// VI: Cấu hình HTTP request pipeline
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.MapOpenApi();
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
// EN: Enable CORS
|
||||
// VI: Kích hoạt CORS
|
||||
app.UseCors("BlazorClient");
|
||||
|
||||
// EN: Map health check endpoint
|
||||
// VI: Map endpoint health check
|
||||
app.MapHealthChecks("/health");
|
||||
|
||||
// EN: Map API controllers
|
||||
// VI: Map các API controllers
|
||||
app.MapControllers();
|
||||
|
||||
// EN: Serve Blazor WebAssembly static files (for hosted mode)
|
||||
// VI: Phục vụ static files của Blazor WebAssembly (cho hosted mode)
|
||||
app.UseBlazorFrameworkFiles();
|
||||
app.UseStaticFiles();
|
||||
|
||||
// EN: Fallback to index.html for SPA routing
|
||||
// VI: Fallback đến index.html cho SPA routing
|
||||
app.MapFallbackToFile("index.html");
|
||||
|
||||
app.Run();
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5091",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7228;http://localhost:5091",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\WebClientBase.Shared\WebClientBase.Shared.csproj" />
|
||||
<ProjectReference Include="..\WebClientBase.Client\WebClientBase.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
@WebClientBase.Server_HostAddress = http://localhost:5091
|
||||
|
||||
GET {{WebClientBase.Server_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in New Issue
Block a user