Spaces:
Sleeping
Sleeping
Yuyuqt commited on
Commit ·
1a7e978
1
Parent(s): f81f07d
add: loyalty reward system
Browse files- Backend/Features/Borrowing/BorrowingService.cs +19 -1
- Backend/Features/Loyalty/LoyaltyController.cs +38 -0
- Backend/Features/Loyalty/LoyaltyService.cs +116 -0
- Backend/Features/Subscriptions/SubscriptionService.cs +20 -1
- Backend/Features/Users/UserService.cs +18 -1
- Backend/Program.cs +8 -0
- Frontend/Controllers/HomeController.cs +10 -1
- Frontend/Frontend.csproj +1 -0
- Frontend/Models/Dtos/LibraryDtos.cs +10 -0
- Frontend/Services/LibraryApiClient.cs +9 -0
- Frontend/Views/Home/Index.cshtml +24 -0
- LibraryManagement.slnx +1 -1
Backend/Features/Borrowing/BorrowingService.cs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
using Database.Models;
|
| 2 |
using Microsoft.EntityFrameworkCore;
|
|
|
|
| 3 |
|
| 4 |
namespace Backend.Features.Borrowings
|
| 5 |
{
|
|
@@ -14,11 +15,13 @@ namespace Backend.Features.Borrowings
|
|
| 14 |
public class BorrowingService : IBorrowingService
|
| 15 |
{
|
| 16 |
private readonly LibraryManagementContext _context;
|
|
|
|
| 17 |
private const decimal FinePerDay = 500;
|
| 18 |
|
| 19 |
-
public BorrowingService(LibraryManagementContext context)
|
| 20 |
{
|
| 21 |
_context = context;
|
|
|
|
| 22 |
}
|
| 23 |
|
| 24 |
public async Task<BorrowingDto> BorrowBookAsync(int userId, int bookId)
|
|
@@ -86,6 +89,21 @@ namespace Backend.Features.Borrowings
|
|
| 86 |
await _context.Entry(borrowing).Reference(b => b.Book).LoadAsync();
|
| 87 |
await _context.Entry(borrowing).Reference(b => b.User).LoadAsync();
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
return MapToDto(borrowing);
|
| 90 |
}
|
| 91 |
|
|
|
|
| 1 |
using Database.Models;
|
| 2 |
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
using Backend.Features.Loyalty;
|
| 4 |
|
| 5 |
namespace Backend.Features.Borrowings
|
| 6 |
{
|
|
|
|
| 15 |
public class BorrowingService : IBorrowingService
|
| 16 |
{
|
| 17 |
private readonly LibraryManagementContext _context;
|
| 18 |
+
private readonly ILoyaltyService _loyaltyService;
|
| 19 |
private const decimal FinePerDay = 500;
|
| 20 |
|
| 21 |
+
public BorrowingService(LibraryManagementContext context, ILoyaltyService loyaltyService)
|
| 22 |
{
|
| 23 |
_context = context;
|
| 24 |
+
_loyaltyService = loyaltyService;
|
| 25 |
}
|
| 26 |
|
| 27 |
public async Task<BorrowingDto> BorrowBookAsync(int userId, int bookId)
|
|
|
|
| 89 |
await _context.Entry(borrowing).Reference(b => b.Book).LoadAsync();
|
| 90 |
await _context.Entry(borrowing).Reference(b => b.User).LoadAsync();
|
| 91 |
|
| 92 |
+
// Loyalty Integration: Send BORROW event
|
| 93 |
+
string externalUserId = userId.ToString();
|
| 94 |
+
string userMobile = borrowing.User?.PhoneNumber ?? "0000000000";
|
| 95 |
+
string userEmail = borrowing.User?.Email ?? "No Email";
|
| 96 |
+
|
| 97 |
+
await _loyaltyService.ProcessEventAsync(
|
| 98 |
+
externalUserId: externalUserId,
|
| 99 |
+
eventKey: "BORROW",
|
| 100 |
+
eventValue: 0,
|
| 101 |
+
referenceId: $"BRW-{borrowing.Id}",
|
| 102 |
+
description: $"Borrowed Book: {borrowing.Book?.Title}",
|
| 103 |
+
email: userEmail,
|
| 104 |
+
mobile: userMobile
|
| 105 |
+
);
|
| 106 |
+
|
| 107 |
return MapToDto(borrowing);
|
| 108 |
}
|
| 109 |
|
Backend/Features/Loyalty/LoyaltyController.cs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
using System.Security.Claims;
|
| 4 |
+
using System.Threading.Tasks;
|
| 5 |
+
|
| 6 |
+
namespace Backend.Features.Loyalty
|
| 7 |
+
{
|
| 8 |
+
[ApiController]
|
| 9 |
+
[Route("api/[controller]")]
|
| 10 |
+
[Authorize]
|
| 11 |
+
public class LoyaltyController : ControllerBase
|
| 12 |
+
{
|
| 13 |
+
private readonly ILoyaltyService _loyaltyService;
|
| 14 |
+
|
| 15 |
+
public LoyaltyController(ILoyaltyService loyaltyService)
|
| 16 |
+
{
|
| 17 |
+
_loyaltyService = loyaltyService;
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
[HttpGet("my-account")]
|
| 21 |
+
public async Task<IActionResult> GetMyAccount()
|
| 22 |
+
{
|
| 23 |
+
var userIdStr = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
| 24 |
+
if (string.IsNullOrEmpty(userIdStr))
|
| 25 |
+
{
|
| 26 |
+
return Unauthorized();
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
var account = await _loyaltyService.GetUserAccountAsync(userIdStr);
|
| 30 |
+
if (account == null)
|
| 31 |
+
{
|
| 32 |
+
return NotFound("Loyalty account not found.");
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
return Ok(account);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
}
|
Backend/Features/Loyalty/LoyaltyService.cs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System;
|
| 2 |
+
using System.Net.Http;
|
| 3 |
+
using System.Net.Http.Json;
|
| 4 |
+
using System.Threading.Tasks;
|
| 5 |
+
using Microsoft.Extensions.Logging;
|
| 6 |
+
|
| 7 |
+
namespace Backend.Features.Loyalty
|
| 8 |
+
{
|
| 9 |
+
public interface ILoyaltyService
|
| 10 |
+
{
|
| 11 |
+
Task<bool> RegisterUserAsync(string externalUserId, string email, string mobile);
|
| 12 |
+
Task<bool> ProcessEventAsync(string externalUserId, string eventKey, double eventValue, string referenceId, string description, string email, string mobile);
|
| 13 |
+
Task<AccountLookupResponse?> GetUserAccountAsync(string externalUserId);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
public class LoyaltyService : ILoyaltyService
|
| 17 |
+
{
|
| 18 |
+
private readonly HttpClient _httpClient;
|
| 19 |
+
private readonly ILogger<LoyaltyService> _logger;
|
| 20 |
+
private const string SystemId = "THS-LMS";
|
| 21 |
+
|
| 22 |
+
public LoyaltyService(HttpClient httpClient, ILogger<LoyaltyService> logger)
|
| 23 |
+
{
|
| 24 |
+
_httpClient = httpClient;
|
| 25 |
+
_logger = logger;
|
| 26 |
+
_httpClient.DefaultRequestHeaders.Add("x-system-id", SystemId);
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
public async Task<bool> RegisterUserAsync(string externalUserId, string email, string mobile)
|
| 30 |
+
{
|
| 31 |
+
try
|
| 32 |
+
{
|
| 33 |
+
var payload = new
|
| 34 |
+
{
|
| 35 |
+
systemId = SystemId,
|
| 36 |
+
externalUserId = externalUserId,
|
| 37 |
+
email = email,
|
| 38 |
+
mobile = mobile,
|
| 39 |
+
tier = "Member" // Default
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
var response = await _httpClient.PostAsJsonAsync("/api/v1/accounts", payload);
|
| 43 |
+
if (!response.IsSuccessStatusCode)
|
| 44 |
+
{
|
| 45 |
+
var error = await response.Content.ReadAsStringAsync();
|
| 46 |
+
_logger.LogWarning($"Failed to register user in loyalty system: {error}");
|
| 47 |
+
return false;
|
| 48 |
+
}
|
| 49 |
+
return true;
|
| 50 |
+
}
|
| 51 |
+
catch (Exception ex)
|
| 52 |
+
{
|
| 53 |
+
_logger.LogError(ex, "Error registering user in loyalty system.");
|
| 54 |
+
return false;
|
| 55 |
+
}
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
public async Task<bool> ProcessEventAsync(string externalUserId, string eventKey, double eventValue, string referenceId, string description, string email, string mobile)
|
| 59 |
+
{
|
| 60 |
+
try
|
| 61 |
+
{
|
| 62 |
+
var payload = new
|
| 63 |
+
{
|
| 64 |
+
externalUserId = externalUserId,
|
| 65 |
+
eventKey = eventKey,
|
| 66 |
+
eventValue = eventValue,
|
| 67 |
+
referenceId = referenceId,
|
| 68 |
+
description = description,
|
| 69 |
+
email = email,
|
| 70 |
+
mobile = mobile
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
var response = await _httpClient.PostAsJsonAsync("/api/v1/events/process", payload);
|
| 74 |
+
if (!response.IsSuccessStatusCode)
|
| 75 |
+
{
|
| 76 |
+
var error = await response.Content.ReadAsStringAsync();
|
| 77 |
+
_logger.LogWarning($"Failed to process loyalty event {eventKey} for user {externalUserId}: {error}");
|
| 78 |
+
return false;
|
| 79 |
+
}
|
| 80 |
+
return true;
|
| 81 |
+
}
|
| 82 |
+
catch (Exception ex)
|
| 83 |
+
{
|
| 84 |
+
_logger.LogError(ex, $"Error processing loyalty event {eventKey}.");
|
| 85 |
+
return false;
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
public async Task<AccountLookupResponse?> GetUserAccountAsync(string externalUserId)
|
| 90 |
+
{
|
| 91 |
+
try
|
| 92 |
+
{
|
| 93 |
+
var response = await _httpClient.GetAsync($"/api/v1/accounts/lookup/{SystemId}/{externalUserId}");
|
| 94 |
+
if (response.IsSuccessStatusCode)
|
| 95 |
+
{
|
| 96 |
+
return await response.Content.ReadFromJsonAsync<AccountLookupResponse>();
|
| 97 |
+
}
|
| 98 |
+
return null;
|
| 99 |
+
}
|
| 100 |
+
catch (Exception ex)
|
| 101 |
+
{
|
| 102 |
+
_logger.LogError(ex, $"Error fetching loyalty account for user {externalUserId}.");
|
| 103 |
+
return null;
|
| 104 |
+
}
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
public class AccountLookupResponse
|
| 109 |
+
{
|
| 110 |
+
public string Id { get; set; } = string.Empty;
|
| 111 |
+
public string ExternalUserId { get; set; } = string.Empty;
|
| 112 |
+
public double CurrentBalance { get; set; }
|
| 113 |
+
public string Tier { get; set; } = string.Empty;
|
| 114 |
+
public double LifetimePoints { get; set; }
|
| 115 |
+
}
|
| 116 |
+
}
|
Backend/Features/Subscriptions/SubscriptionService.cs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
using Database.Models;
|
| 2 |
using Microsoft.EntityFrameworkCore;
|
|
|
|
| 3 |
|
| 4 |
namespace Backend.Features.Subscriptions
|
| 5 |
{
|
|
@@ -13,10 +14,12 @@ namespace Backend.Features.Subscriptions
|
|
| 13 |
public class SubscriptionService : ISubscriptionService
|
| 14 |
{
|
| 15 |
private readonly LibraryManagementContext _context;
|
|
|
|
| 16 |
|
| 17 |
-
public SubscriptionService(LibraryManagementContext context)
|
| 18 |
{
|
| 19 |
_context = context;
|
|
|
|
| 20 |
}
|
| 21 |
|
| 22 |
public async Task<IEnumerable<MembershipDto>> GetMembershipsAsync()
|
|
@@ -75,6 +78,22 @@ namespace Backend.Features.Subscriptions
|
|
| 75 |
|
| 76 |
// Explicitly load the membership data for the DTO mapping
|
| 77 |
await _context.Entry(subscription).Reference(s => s.Membership).LoadAsync();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
return MapToDto(subscription);
|
| 80 |
}
|
|
|
|
| 1 |
using Database.Models;
|
| 2 |
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
using Backend.Features.Loyalty;
|
| 4 |
|
| 5 |
namespace Backend.Features.Subscriptions
|
| 6 |
{
|
|
|
|
| 14 |
public class SubscriptionService : ISubscriptionService
|
| 15 |
{
|
| 16 |
private readonly LibraryManagementContext _context;
|
| 17 |
+
private readonly ILoyaltyService _loyaltyService;
|
| 18 |
|
| 19 |
+
public SubscriptionService(LibraryManagementContext context, ILoyaltyService loyaltyService)
|
| 20 |
{
|
| 21 |
_context = context;
|
| 22 |
+
_loyaltyService = loyaltyService;
|
| 23 |
}
|
| 24 |
|
| 25 |
public async Task<IEnumerable<MembershipDto>> GetMembershipsAsync()
|
|
|
|
| 78 |
|
| 79 |
// Explicitly load the membership data for the DTO mapping
|
| 80 |
await _context.Entry(subscription).Reference(s => s.Membership).LoadAsync();
|
| 81 |
+
await _context.Entry(subscription).Reference(s => s.User).LoadAsync();
|
| 82 |
+
|
| 83 |
+
// Loyalty Integration: Send SUBSCRIBE event
|
| 84 |
+
string externalUserId = userId.ToString();
|
| 85 |
+
string userMobile = subscription.User?.PhoneNumber ?? "0000000000";
|
| 86 |
+
string userEmail = subscription.User?.Email ?? "No Email";
|
| 87 |
+
|
| 88 |
+
await _loyaltyService.ProcessEventAsync(
|
| 89 |
+
externalUserId: externalUserId,
|
| 90 |
+
eventKey: "SUBSCRIBE",
|
| 91 |
+
eventValue: (double)membership.Price,
|
| 92 |
+
referenceId: $"SUB-{subscription.Id}",
|
| 93 |
+
description: $"Purchased Membership: {membership.Type}",
|
| 94 |
+
email: userEmail,
|
| 95 |
+
mobile: userMobile
|
| 96 |
+
);
|
| 97 |
|
| 98 |
return MapToDto(subscription);
|
| 99 |
}
|
Backend/Features/Users/UserService.cs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
using Database.Models;
|
| 2 |
using Microsoft.EntityFrameworkCore;
|
| 3 |
using Backend.Features.Subscriptions;
|
|
|
|
| 4 |
using System;
|
| 5 |
using System.Collections.Generic;
|
| 6 |
using System.Linq;
|
|
@@ -22,11 +23,13 @@ namespace Backend.Features.Users
|
|
| 22 |
{
|
| 23 |
private readonly LibraryManagementContext _context;
|
| 24 |
private readonly ISubscriptionService _subscriptionService;
|
|
|
|
| 25 |
|
| 26 |
-
public UserService(LibraryManagementContext context, ISubscriptionService subscriptionService)
|
| 27 |
{
|
| 28 |
_context = context;
|
| 29 |
_subscriptionService = subscriptionService;
|
|
|
|
| 30 |
}
|
| 31 |
|
| 32 |
public async Task<IEnumerable<UserDto>> GetAllUsersAsync()
|
|
@@ -69,6 +72,20 @@ namespace Backend.Features.Users
|
|
| 69 |
await _subscriptionService.SubscribeUserAsync(user.Id, 3); // 3 is "Basic Yearly"
|
| 70 |
}
|
| 71 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
return MapToDto(user);
|
| 73 |
}
|
| 74 |
|
|
|
|
| 1 |
using Database.Models;
|
| 2 |
using Microsoft.EntityFrameworkCore;
|
| 3 |
using Backend.Features.Subscriptions;
|
| 4 |
+
using Backend.Features.Loyalty;
|
| 5 |
using System;
|
| 6 |
using System.Collections.Generic;
|
| 7 |
using System.Linq;
|
|
|
|
| 23 |
{
|
| 24 |
private readonly LibraryManagementContext _context;
|
| 25 |
private readonly ISubscriptionService _subscriptionService;
|
| 26 |
+
private readonly ILoyaltyService _loyaltyService;
|
| 27 |
|
| 28 |
+
public UserService(LibraryManagementContext context, ISubscriptionService subscriptionService, ILoyaltyService loyaltyService)
|
| 29 |
{
|
| 30 |
_context = context;
|
| 31 |
_subscriptionService = subscriptionService;
|
| 32 |
+
_loyaltyService = loyaltyService;
|
| 33 |
}
|
| 34 |
|
| 35 |
public async Task<IEnumerable<UserDto>> GetAllUsersAsync()
|
|
|
|
| 72 |
await _subscriptionService.SubscribeUserAsync(user.Id, 3); // 3 is "Basic Yearly"
|
| 73 |
}
|
| 74 |
|
| 75 |
+
// Loyalty Integration: Register the new user and process SIGNUP event
|
| 76 |
+
string externalUserId = user.Id.ToString();
|
| 77 |
+
string userMobile = user.PhoneNumber ?? "0000000000";
|
| 78 |
+
await _loyaltyService.RegisterUserAsync(externalUserId, user.Email, userMobile);
|
| 79 |
+
await _loyaltyService.ProcessEventAsync(
|
| 80 |
+
externalUserId: externalUserId,
|
| 81 |
+
eventKey: "SIGNUP",
|
| 82 |
+
eventValue: 0,
|
| 83 |
+
referenceId: $"USR-{user.Id}",
|
| 84 |
+
description: "New User Registration",
|
| 85 |
+
email: user.Email,
|
| 86 |
+
mobile: userMobile
|
| 87 |
+
);
|
| 88 |
+
|
| 89 |
return MapToDto(user);
|
| 90 |
}
|
| 91 |
|
Backend/Program.cs
CHANGED
|
@@ -10,9 +10,11 @@ using Microsoft.OpenApi.Models;
|
|
| 10 |
using Backend.Features.Subscriptions;
|
| 11 |
using Backend.Features.Categories;
|
| 12 |
using Backend.Features.Borrowings;
|
|
|
|
| 13 |
|
| 14 |
var builder = WebApplication.CreateBuilder(args);
|
| 15 |
|
|
|
|
| 16 |
builder.Services.AddControllers();
|
| 17 |
builder.Services.AddAuthorization();
|
| 18 |
builder.Services.AddEndpointsApiExplorer();
|
|
@@ -55,6 +57,12 @@ builder.Services.AddScoped<ISubscriptionService, SubscriptionService>();
|
|
| 55 |
builder.Services.AddScoped<ICategoryService, CategoryService>();
|
| 56 |
builder.Services.AddScoped<IBorrowingService, BorrowingService>();
|
| 57 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
// Configure JWT Authentication
|
| 59 |
var key = Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Secret"] ?? "YourSuperSecretKeyForLibraryManagementSystem_AtLeast32CharsLong");
|
| 60 |
builder.Services.AddAuthentication(x =>
|
|
|
|
| 10 |
using Backend.Features.Subscriptions;
|
| 11 |
using Backend.Features.Categories;
|
| 12 |
using Backend.Features.Borrowings;
|
| 13 |
+
using Backend.Features.Loyalty;
|
| 14 |
|
| 15 |
var builder = WebApplication.CreateBuilder(args);
|
| 16 |
|
| 17 |
+
|
| 18 |
builder.Services.AddControllers();
|
| 19 |
builder.Services.AddAuthorization();
|
| 20 |
builder.Services.AddEndpointsApiExplorer();
|
|
|
|
| 57 |
builder.Services.AddScoped<ICategoryService, CategoryService>();
|
| 58 |
builder.Services.AddScoped<IBorrowingService, BorrowingService>();
|
| 59 |
|
| 60 |
+
// Register Loyalty API Client
|
| 61 |
+
builder.Services.AddHttpClient<ILoyaltyService, LoyaltyService>(client =>
|
| 62 |
+
{
|
| 63 |
+
client.BaseAddress = new Uri("http://150.95.88.91:4100");
|
| 64 |
+
});
|
| 65 |
+
|
| 66 |
// Configure JWT Authentication
|
| 67 |
var key = Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Secret"] ?? "YourSuperSecretKeyForLibraryManagementSystem_AtLeast32CharsLong");
|
| 68 |
builder.Services.AddAuthentication(x =>
|
Frontend/Controllers/HomeController.cs
CHANGED
|
@@ -24,6 +24,14 @@ namespace Frontend.Controllers
|
|
| 24 |
var borrowings = await _apiClient.GetMyBorrowingsAsync();
|
| 25 |
var members = await _apiClient.GetUsersAsync();
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
ViewBag.TotalBooks = books.Count();
|
| 28 |
ViewBag.ActiveLoans = borrowings.Count(b => !b.ReturnDate.HasValue);
|
| 29 |
ViewBag.OverdueLoans = borrowings.Count(b => !b.ReturnDate.HasValue && b.DueDate < DateTime.UtcNow);
|
|
@@ -37,9 +45,10 @@ namespace Frontend.Controllers
|
|
| 37 |
// Set default values if API fails
|
| 38 |
ViewBag.TotalBooks = 0;
|
| 39 |
ViewBag.ActiveLoans = 0;
|
| 40 |
-
ViewBag.OverdueLoans = 0;
|
| 41 |
ViewBag.TotalMembers = 0;
|
| 42 |
ViewBag.RecentBooks = Enumerable.Empty<Frontend.Models.Dtos.BookDto>();
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
|
| 45 |
return View();
|
|
|
|
| 24 |
var borrowings = await _apiClient.GetMyBorrowingsAsync();
|
| 25 |
var members = await _apiClient.GetUsersAsync();
|
| 26 |
|
| 27 |
+
// Fetch loyalty points (if user is logged in)
|
| 28 |
+
if (User.Identity?.IsAuthenticated == true)
|
| 29 |
+
{
|
| 30 |
+
var loyaltyAccount = await _apiClient.GetMyLoyaltyAccountAsync();
|
| 31 |
+
ViewBag.LoyaltyPoints = loyaltyAccount?.CurrentBalance ?? 0;
|
| 32 |
+
ViewBag.LoyaltyTier = loyaltyAccount?.Tier ?? "Member";
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
ViewBag.TotalBooks = books.Count();
|
| 36 |
ViewBag.ActiveLoans = borrowings.Count(b => !b.ReturnDate.HasValue);
|
| 37 |
ViewBag.OverdueLoans = borrowings.Count(b => !b.ReturnDate.HasValue && b.DueDate < DateTime.UtcNow);
|
|
|
|
| 45 |
// Set default values if API fails
|
| 46 |
ViewBag.TotalBooks = 0;
|
| 47 |
ViewBag.ActiveLoans = 0;
|
|
|
|
| 48 |
ViewBag.TotalMembers = 0;
|
| 49 |
ViewBag.RecentBooks = Enumerable.Empty<Frontend.Models.Dtos.BookDto>();
|
| 50 |
+
ViewBag.LoyaltyPoints = 0;
|
| 51 |
+
ViewBag.LoyaltyTier = "Member";
|
| 52 |
}
|
| 53 |
|
| 54 |
return View();
|
Frontend/Frontend.csproj
CHANGED
|
@@ -7,3 +7,4 @@
|
|
| 7 |
</PropertyGroup>
|
| 8 |
|
| 9 |
</Project>
|
|
|
|
|
|
| 7 |
</PropertyGroup>
|
| 8 |
|
| 9 |
</Project>
|
| 10 |
+
|
Frontend/Models/Dtos/LibraryDtos.cs
CHANGED
|
@@ -124,6 +124,16 @@ namespace Frontend.Models.Dtos
|
|
| 124 |
public int MembershipId { get; set; }
|
| 125 |
}
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
// User DTOs
|
| 128 |
public class UserDto
|
| 129 |
{
|
|
|
|
| 124 |
public int MembershipId { get; set; }
|
| 125 |
}
|
| 126 |
|
| 127 |
+
// Loyalty DTOs
|
| 128 |
+
public class LoyaltyAccountDto
|
| 129 |
+
{
|
| 130 |
+
public string Id { get; set; } = string.Empty;
|
| 131 |
+
public string ExternalUserId { get; set; } = string.Empty;
|
| 132 |
+
public double CurrentBalance { get; set; }
|
| 133 |
+
public string Tier { get; set; } = string.Empty;
|
| 134 |
+
public double LifetimePoints { get; set; }
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
// User DTOs
|
| 138 |
public class UserDto
|
| 139 |
{
|
Frontend/Services/LibraryApiClient.cs
CHANGED
|
@@ -158,6 +158,15 @@ namespace Frontend.Services
|
|
| 158 |
}
|
| 159 |
#endregion
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
#region Users
|
| 162 |
public async Task<IEnumerable<UserDto>> GetUsersAsync()
|
| 163 |
{
|
|
|
|
| 158 |
}
|
| 159 |
#endregion
|
| 160 |
|
| 161 |
+
#region Loyalty
|
| 162 |
+
public async Task<LoyaltyAccountDto?> GetMyLoyaltyAccountAsync()
|
| 163 |
+
{
|
| 164 |
+
try {
|
| 165 |
+
return await _httpClient.GetFromJsonAsync<LoyaltyAccountDto>("api/loyalty/my-account");
|
| 166 |
+
} catch { return null; }
|
| 167 |
+
}
|
| 168 |
+
#endregion
|
| 169 |
+
|
| 170 |
#region Users
|
| 171 |
public async Task<IEnumerable<UserDto>> GetUsersAsync()
|
| 172 |
{
|
Frontend/Views/Home/Index.cshtml
CHANGED
|
@@ -7,6 +7,30 @@
|
|
| 7 |
<p class="text-slate-500 font-medium">Here's what's happening with the library today.</p>
|
| 8 |
</div>
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
<!-- Stats Grid -->
|
| 11 |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
| 12 |
<div class="card p-6 flex flex-col gap-3 group hover:border-slate-300">
|
|
|
|
| 7 |
<p class="text-slate-500 font-medium">Here's what's happening with the library today.</p>
|
| 8 |
</div>
|
| 9 |
|
| 10 |
+
@if (User.Identity?.IsAuthenticated == true)
|
| 11 |
+
{
|
| 12 |
+
<!-- Loyalty Banner -->
|
| 13 |
+
<div class="bg-gradient-to-r from-amber-500 to-amber-700 rounded-2xl p-6 mb-10 text-white shadow-lg relative overflow-hidden group">
|
| 14 |
+
<div class="relative z-10 flex items-center justify-between">
|
| 15 |
+
<div>
|
| 16 |
+
<p class="text-amber-100 text-sm font-bold uppercase tracking-widest mb-1">Your Loyalty Status</p>
|
| 17 |
+
<div class="flex items-baseline gap-3">
|
| 18 |
+
<h2 class="text-4xl font-extrabold">@ViewBag.LoyaltyPoints</h2>
|
| 19 |
+
<span class="text-amber-200 font-medium tracking-wide">Points</span>
|
| 20 |
+
</div>
|
| 21 |
+
</div>
|
| 22 |
+
<div class="text-right">
|
| 23 |
+
<div class="inline-flex items-center gap-2 bg-black/20 backdrop-blur-sm px-4 py-2 rounded-full border border-white/10 shadow-inner">
|
| 24 |
+
<svg class="w-5 h-5 text-amber-200" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"></path></svg>
|
| 25 |
+
<span class="font-bold text-lg">@ViewBag.LoyaltyTier</span>
|
| 26 |
+
</div>
|
| 27 |
+
</div>
|
| 28 |
+
</div>
|
| 29 |
+
<!-- Decorative Background -->
|
| 30 |
+
<div class="absolute -right-10 -top-10 w-48 h-48 bg-white/10 rounded-full blur-2xl transform group-hover:scale-110 transition-transform duration-700"></div>
|
| 31 |
+
</div>
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
<!-- Stats Grid -->
|
| 35 |
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-12">
|
| 36 |
<div class="card p-6 flex flex-col gap-3 group hover:border-slate-300">
|
LibraryManagement.slnx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
<Solution>
|
|
|
|
| 2 |
<Project Path="Database/Database.csproj" Id="1ec23dc7-355d-4456-a503-cfd0fb81cd5c" />
|
| 3 |
<Project Path="Frontend/Frontend.csproj" Id="824d7fed-4fd3-41c5-8c5e-acd458ccbcdb" />
|
| 4 |
-
<Project Path="Backend/Backend.csproj" Id="82e311b1-ce76-4235-9d5f-0d06a942d038" />
|
| 5 |
</Solution>
|
|
|
|
| 1 |
<Solution>
|
| 2 |
+
<Project Path="Backend/Backend.csproj" Id="82e311b1-ce76-4235-9d5f-0d06a942d038" />
|
| 3 |
<Project Path="Database/Database.csproj" Id="1ec23dc7-355d-4456-a503-cfd0fb81cd5c" />
|
| 4 |
<Project Path="Frontend/Frontend.csproj" Id="824d7fed-4fd3-41c5-8c5e-acd458ccbcdb" />
|
|
|
|
| 5 |
</Solution>
|