feat(tpos): add BFF data endpoints with Npgsql + Dapper
- Add Npgsql 9.0.3 and Dapper 2.1.66 packages to Server project
- Create BffDataController with read-only endpoints:
GET /api/bff/shops
GET /api/bff/shops/{shopId}/products
GET /api/bff/shops/{shopId}/categories
GET /api/bff/shops/{shopId}/tables
GET /api/bff/shops/{shopId}/appointments
GET /api/bff/shops/{shopId}/resources
- Register MVC controllers in Program.cs (AddControllers + MapControllers)
Co-authored-by: Velik <hongochai10@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Npgsql;
|
||||
using Dapper;
|
||||
|
||||
namespace WebClientTpos.Server.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/bff")]
|
||||
public class BffDataController : ControllerBase
|
||||
{
|
||||
private static string ConnStr(string db) =>
|
||||
$"Host=localhost;Port=5432;Database={db};Username=goodgo;Password=goodgo_dev_2024";
|
||||
|
||||
[HttpGet("shops")]
|
||||
public async Task<IActionResult> GetShops()
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("merchant_service"));
|
||||
var shops = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT s.id, s.name, s.slug, s.description, s.phone, s.email,
|
||||
s.open_time, s.close_time, s.features_config,
|
||||
bc.name as category, st.name as status
|
||||
FROM shops s
|
||||
JOIN business_categories bc ON s.category_id = bc.id
|
||||
JOIN shop_statuses st ON s.status_id = st.id
|
||||
WHERE s.is_deleted = false
|
||||
ORDER BY s.name");
|
||||
return Ok(shops);
|
||||
}
|
||||
|
||||
[HttpGet("shops/{shopId}/products")]
|
||||
public async Task<IActionResult> GetProducts(Guid shopId)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("catalog_service"));
|
||||
var products = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT id, name, price, sku, description, image_url, is_active,
|
||||
attributes->>'category' as category,
|
||||
(attributes->>'duration')::int as duration_minutes
|
||||
FROM products
|
||||
WHERE shop_id = @ShopId AND is_active = true
|
||||
ORDER BY name",
|
||||
new { ShopId = shopId });
|
||||
return Ok(products);
|
||||
}
|
||||
|
||||
[HttpGet("shops/{shopId}/categories")]
|
||||
public async Task<IActionResult> GetCategories(Guid shopId)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("catalog_service"));
|
||||
var categories = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT id, name, description, display_order
|
||||
FROM categories
|
||||
WHERE shop_id = @ShopId AND is_active = true
|
||||
ORDER BY display_order",
|
||||
new { ShopId = shopId });
|
||||
return Ok(categories);
|
||||
}
|
||||
|
||||
[HttpGet("shops/{shopId}/tables")]
|
||||
public async Task<IActionResult> GetTables(Guid shopId)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("fnb_engine"));
|
||||
var tables = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT t.id, t.table_number, t.capacity, t.zone,
|
||||
CASE t.status_id
|
||||
WHEN 1 THEN 'available'
|
||||
WHEN 2 THEN 'occupied'
|
||||
WHEN 3 THEN 'reserved'
|
||||
WHEN 4 THEN 'cleaning'
|
||||
END as status,
|
||||
s.id as session_id, s.guest_count, s.started_at
|
||||
FROM tables t
|
||||
LEFT JOIN sessions s ON s.table_id = t.id AND s.status = 'Active'
|
||||
WHERE t.shop_id = @ShopId
|
||||
ORDER BY t.table_number",
|
||||
new { ShopId = shopId });
|
||||
return Ok(tables);
|
||||
}
|
||||
|
||||
[HttpGet("shops/{shopId}/appointments")]
|
||||
public async Task<IActionResult> GetAppointments(Guid shopId)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("booking_service"));
|
||||
var appointments = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT a.id, a.customer_id, a.staff_id, a.resource_id,
|
||||
a.service_id, a.start_time, a.end_time, a.status,
|
||||
r.name as resource_name
|
||||
FROM appointments a
|
||||
LEFT JOIN resources r ON a.resource_id = r.id
|
||||
WHERE a.shop_id = @ShopId
|
||||
ORDER BY a.start_time",
|
||||
new { ShopId = shopId });
|
||||
return Ok(appointments);
|
||||
}
|
||||
|
||||
[HttpGet("shops/{shopId}/resources")]
|
||||
public async Task<IActionResult> GetResources(Guid shopId)
|
||||
{
|
||||
await using var conn = new NpgsqlConnection(ConnStr("booking_service"));
|
||||
var resources = await conn.QueryAsync<dynamic>(
|
||||
@"SELECT id, name, resource_type, capacity, is_active
|
||||
FROM resources
|
||||
WHERE shop_id = @ShopId AND is_active = true
|
||||
ORDER BY name",
|
||||
new { ShopId = shopId });
|
||||
return Ok(resources);
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,10 @@ builder.Services.AddCors(options =>
|
||||
// VI: Thêm health checks
|
||||
builder.Services.AddHealthChecks();
|
||||
|
||||
// EN: Add MVC controllers for BFF data endpoints
|
||||
// VI: Thêm MVC controllers cho BFF data endpoints
|
||||
builder.Services.AddControllers();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -116,6 +120,10 @@ app.Map("{culture:regex(^(en-US|vi-VN)$)}/{**slug}", async (string culture, Http
|
||||
return Results.Content(modifiedHtml, "text/html");
|
||||
});
|
||||
|
||||
// EN: Map BFF API controllers
|
||||
// VI: Map BFF API controllers
|
||||
app.MapControllers();
|
||||
|
||||
// EN: Fallback to index.html for SPA routing (default culture)
|
||||
// VI: Fallback đến index.html cho SPA routing (ngôn ngữ mặc định)
|
||||
app.MapFallbackToFile("index.html");
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.1" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
<PackageReference Include="Yarp.ReverseProxy" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user