Spaces:
Sleeping
Sleeping
Commit ·
068485b
1
Parent(s): 97af519
feat: restructure backend project, add configuration files, implement authentication, user and book management, and enhance borrowing and subscription features
Browse files- Backend/Backend.csproj +20 -0
- Backend/Backend.http +6 -0
- Backend/Features/Auth/AuthController.cs +59 -0
- Backend/Features/Auth/AuthModels.cs +26 -0
- Backend/Features/Auth/AuthService.cs +110 -0
- Backend/Features/Books/BookController.cs +67 -0
- Backend/Features/Books/BookService.cs +150 -0
- Backend/Features/Borrowing/BorrowingController.cs +72 -0
- Backend/Features/Borrowing/BorrowingModels.cs +24 -0
- Backend/Features/Borrowing/BorrowingService.cs +166 -0
- Backend/Features/Categories/CategoryController.cs +49 -0
- Backend/Features/Categories/CategoryService.cs +69 -0
- Backend/Features/Subscriptions/SubscriptionController.cs +84 -0
- Backend/Features/Subscriptions/SubscriptionModels.cs +38 -0
- Backend/Features/Subscriptions/SubscriptionService.cs +96 -0
- Backend/Features/Users/UserController.cs +77 -0
- Backend/Features/Users/UserService.cs +182 -0
- Backend/Program.cs +91 -0
- Backend/Properties/launchSettings.json +41 -0
- Backend/WeatherForecast.cs +13 -0
- Backend/appsettings.Development.json +8 -0
- Backend/appsettings.json +18 -0
- LibraryManagement.slnx +1 -1
Backend/Backend.csproj
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<Project Sdk="Microsoft.NET.Sdk.Web">
|
| 2 |
+
|
| 3 |
+
<PropertyGroup>
|
| 4 |
+
<TargetFramework>net8.0</TargetFramework>
|
| 5 |
+
<Nullable>enable</Nullable>
|
| 6 |
+
<ImplicitUsings>enable</ImplicitUsings>
|
| 7 |
+
</PropertyGroup>
|
| 8 |
+
|
| 9 |
+
<ItemGroup>
|
| 10 |
+
<PackageReference Include="BCrypt.Net-Next" Version="4.1.0" />
|
| 11 |
+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.13" />
|
| 12 |
+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
|
| 13 |
+
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.17.0" />
|
| 14 |
+
</ItemGroup>
|
| 15 |
+
|
| 16 |
+
<ItemGroup>
|
| 17 |
+
<ProjectReference Include="..\Database\Database.csproj" />
|
| 18 |
+
</ItemGroup>
|
| 19 |
+
|
| 20 |
+
</Project>
|
Backend/Backend.http
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@Backend_HostAddress = http://localhost:5199
|
| 2 |
+
|
| 3 |
+
GET {{Backend_HostAddress}}/weatherforecast/
|
| 4 |
+
Accept: application/json
|
| 5 |
+
|
| 6 |
+
###
|
Backend/Features/Auth/AuthController.cs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
using System.Security.Claims;
|
| 4 |
+
|
| 5 |
+
namespace Backend.Features.Auth
|
| 6 |
+
{
|
| 7 |
+
[ApiController]
|
| 8 |
+
[Route("api/auth")]
|
| 9 |
+
public class AuthController : ControllerBase
|
| 10 |
+
{
|
| 11 |
+
private readonly IAuthService _authService;
|
| 12 |
+
|
| 13 |
+
public AuthController(IAuthService authService)
|
| 14 |
+
{
|
| 15 |
+
_authService = authService;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
[HttpPost("register")]
|
| 19 |
+
public async Task<ActionResult<AuthResponse>> Register([FromBody] RegisterRequest request)
|
| 20 |
+
{
|
| 21 |
+
try
|
| 22 |
+
{
|
| 23 |
+
var response = await _authService.Register(request);
|
| 24 |
+
return Ok(response);
|
| 25 |
+
}
|
| 26 |
+
catch (Exception ex)
|
| 27 |
+
{
|
| 28 |
+
return BadRequest(new { message = ex.Message });
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
[HttpPost("login")]
|
| 33 |
+
public async Task<ActionResult<AuthResponse>> Login([FromBody] LoginRequest request)
|
| 34 |
+
{
|
| 35 |
+
try
|
| 36 |
+
{
|
| 37 |
+
var response = await _authService.Login(request);
|
| 38 |
+
return Ok(response);
|
| 39 |
+
}
|
| 40 |
+
catch (Exception ex)
|
| 41 |
+
{
|
| 42 |
+
return Unauthorized(new { message = ex.Message });
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
[HttpGet("me")]
|
| 47 |
+
[Authorize]
|
| 48 |
+
public IActionResult GetCurrentUser()
|
| 49 |
+
{
|
| 50 |
+
var claims = User.Claims.Select(c => new { c.Type, c.Value }).ToList();
|
| 51 |
+
return Ok(new
|
| 52 |
+
{
|
| 53 |
+
IsAuthenticated = User.Identity?.IsAuthenticated,
|
| 54 |
+
Name = User.Identity?.Name,
|
| 55 |
+
Claims = claims
|
| 56 |
+
});
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
}
|
Backend/Features/Auth/AuthModels.cs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
namespace Backend.Features.Auth
|
| 2 |
+
{
|
| 3 |
+
public class RegisterRequest
|
| 4 |
+
{
|
| 5 |
+
public string FullName { get; set; } = string.Empty;
|
| 6 |
+
public string Email { get; set; } = string.Empty;
|
| 7 |
+
public string Password { get; set; } = string.Empty;
|
| 8 |
+
public string? PhoneNumber { get; set; }
|
| 9 |
+
public string? Address { get; set; }
|
| 10 |
+
public string? StudentId { get; set; }
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
public class LoginRequest
|
| 14 |
+
{
|
| 15 |
+
public string Email { get; set; } = string.Empty;
|
| 16 |
+
public string Password { get; set; } = string.Empty;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
public class AuthResponse
|
| 20 |
+
{
|
| 21 |
+
public string Token { get; set; } = string.Empty;
|
| 22 |
+
public string FullName { get; set; } = string.Empty;
|
| 23 |
+
public string Role { get; set; } = string.Empty;
|
| 24 |
+
public DateTime Expiry { get; set; }
|
| 25 |
+
}
|
| 26 |
+
}
|
Backend/Features/Auth/AuthService.cs
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
using Microsoft.IdentityModel.Tokens;
|
| 4 |
+
using System.IdentityModel.Tokens.Jwt;
|
| 5 |
+
using System.Security.Claims;
|
| 6 |
+
using System.Text;
|
| 7 |
+
|
| 8 |
+
using Backend.Features.Subscriptions;
|
| 9 |
+
|
| 10 |
+
namespace Backend.Features.Auth
|
| 11 |
+
{
|
| 12 |
+
public interface IAuthService
|
| 13 |
+
{
|
| 14 |
+
Task<AuthResponse> Register(RegisterRequest request);
|
| 15 |
+
Task<AuthResponse> Login(LoginRequest request);
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
public class AuthService : IAuthService
|
| 19 |
+
{
|
| 20 |
+
private readonly LibraryManagementContext _context;
|
| 21 |
+
private readonly IConfiguration _configuration;
|
| 22 |
+
private readonly ISubscriptionService _subscriptionService;
|
| 23 |
+
|
| 24 |
+
public AuthService(LibraryManagementContext context, IConfiguration configuration, ISubscriptionService subscriptionService)
|
| 25 |
+
{
|
| 26 |
+
_context = context;
|
| 27 |
+
_configuration = configuration;
|
| 28 |
+
_subscriptionService = subscriptionService;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
public async Task<AuthResponse> Register(RegisterRequest request)
|
| 32 |
+
{
|
| 33 |
+
if (await _context.Users.AnyAsync(u => u.Email == request.Email))
|
| 34 |
+
{
|
| 35 |
+
throw new Exception("User already exists with this email.");
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
var user = new User
|
| 39 |
+
{
|
| 40 |
+
FullName = request.FullName,
|
| 41 |
+
Email = request.Email,
|
| 42 |
+
PhoneNumber = request.PhoneNumber,
|
| 43 |
+
Address = request.Address,
|
| 44 |
+
StudentId = request.StudentId,
|
| 45 |
+
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
|
| 46 |
+
Role = "Member", // Default role
|
| 47 |
+
IsActive = true,
|
| 48 |
+
CreatedAt = DateTime.UtcNow
|
| 49 |
+
};
|
| 50 |
+
|
| 51 |
+
_context.Users.Add(user);
|
| 52 |
+
await _context.SaveChangesAsync();
|
| 53 |
+
|
| 54 |
+
if (!string.IsNullOrEmpty(user.StudentId))
|
| 55 |
+
{
|
| 56 |
+
await _subscriptionService.SubscribeUserAsync(user.Id, 3); // 3 is "Basic Yearly"
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
return await AuthenticateUser(user);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
public async Task<AuthResponse> Login(LoginRequest request)
|
| 63 |
+
{
|
| 64 |
+
var user = await _context.Users.FirstOrDefaultAsync(u => u.Email == request.Email);
|
| 65 |
+
|
| 66 |
+
if (user == null || !BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
|
| 67 |
+
{
|
| 68 |
+
throw new Exception("Invalid email or password.");
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
if (!user.IsActive)
|
| 72 |
+
{
|
| 73 |
+
throw new Exception("User account is deactivated.");
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
return await AuthenticateUser(user);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
private async Task<AuthResponse> AuthenticateUser(User user)
|
| 80 |
+
{
|
| 81 |
+
var tokenHandler = new JwtSecurityTokenHandler();
|
| 82 |
+
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Secret"] ?? "YourSuperSecretKeyForLibraryManagementSystem_AtLeast32CharsLong");
|
| 83 |
+
|
| 84 |
+
var tokenDescriptor = new SecurityTokenDescriptor
|
| 85 |
+
{
|
| 86 |
+
Subject = new ClaimsIdentity(new[]
|
| 87 |
+
{
|
| 88 |
+
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
| 89 |
+
new Claim(ClaimTypes.Email, user.Email),
|
| 90 |
+
new Claim(ClaimTypes.Name, user.FullName),
|
| 91 |
+
new Claim(ClaimTypes.Role, user.Role)
|
| 92 |
+
}),
|
| 93 |
+
Expires = DateTime.UtcNow.AddDays(7),
|
| 94 |
+
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature),
|
| 95 |
+
Issuer = _configuration["Jwt:Issuer"],
|
| 96 |
+
Audience = _configuration["Jwt:Audience"]
|
| 97 |
+
};
|
| 98 |
+
|
| 99 |
+
var token = tokenHandler.CreateToken(tokenDescriptor);
|
| 100 |
+
|
| 101 |
+
return new AuthResponse
|
| 102 |
+
{
|
| 103 |
+
Token = tokenHandler.WriteToken(token),
|
| 104 |
+
FullName = user.FullName,
|
| 105 |
+
Role = user.Role,
|
| 106 |
+
Expiry = tokenDescriptor.Expires.Value
|
| 107 |
+
};
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
}
|
Backend/Features/Books/BookController.cs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
|
| 4 |
+
namespace Backend.Features.Books
|
| 5 |
+
{
|
| 6 |
+
[ApiController]
|
| 7 |
+
[Route("api/books")]
|
| 8 |
+
public class BookController : ControllerBase
|
| 9 |
+
{
|
| 10 |
+
private readonly IBookService _bookService;
|
| 11 |
+
|
| 12 |
+
public BookController(IBookService bookService)
|
| 13 |
+
{
|
| 14 |
+
_bookService = bookService;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
[HttpGet]
|
| 18 |
+
//[Authorize]
|
| 19 |
+
public async Task<ActionResult<IEnumerable<BookDto>>> GetBooks()
|
| 20 |
+
{
|
| 21 |
+
var books = await _bookService.GetAllBooksAsync();
|
| 22 |
+
return Ok(books);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
[HttpGet("{id}")]
|
| 26 |
+
//[Authorize]
|
| 27 |
+
public async Task<ActionResult<BookDto>> GetBook(int id)
|
| 28 |
+
{
|
| 29 |
+
var book = await _bookService.GetBookByIdAsync(id);
|
| 30 |
+
if (book == null) return NotFound();
|
| 31 |
+
return Ok(book);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
[HttpPost]
|
| 35 |
+
//[Authorize(Roles = "Librarian")]
|
| 36 |
+
public async Task<ActionResult<BookDto>> CreateBook([FromBody] BookCreateRequest request)
|
| 37 |
+
{
|
| 38 |
+
try
|
| 39 |
+
{
|
| 40 |
+
var book = await _bookService.CreateBookAsync(request);
|
| 41 |
+
return CreatedAtAction(nameof(GetBook), new { id = book.Id }, book);
|
| 42 |
+
}
|
| 43 |
+
catch (Exception ex)
|
| 44 |
+
{
|
| 45 |
+
return BadRequest(new { message = ex.Message });
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
[HttpPut("{id}")]
|
| 50 |
+
//[Authorize(Roles = "Librarian")]
|
| 51 |
+
public async Task<ActionResult<BookDto>> UpdateBook(int id, [FromBody] BookUpdateRequest request)
|
| 52 |
+
{
|
| 53 |
+
var updatedBook = await _bookService.UpdateBookAsync(id, request);
|
| 54 |
+
if (updatedBook == null) return NotFound();
|
| 55 |
+
return Ok(updatedBook);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
[HttpDelete("{id}")]
|
| 59 |
+
//[Authorize(Roles = "Librarian")]
|
| 60 |
+
public async Task<IActionResult> DeleteBook(int id)
|
| 61 |
+
{
|
| 62 |
+
var success = await _bookService.DeleteBookAsync(id);
|
| 63 |
+
if (!success) return NotFound();
|
| 64 |
+
return NoContent();
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
}
|
Backend/Features/Books/BookService.cs
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
|
| 4 |
+
namespace Backend.Features.Books
|
| 5 |
+
{
|
| 6 |
+
public interface IBookService
|
| 7 |
+
{
|
| 8 |
+
Task<IEnumerable<BookDto>> GetAllBooksAsync();
|
| 9 |
+
Task<BookDto?> GetBookByIdAsync(int id);
|
| 10 |
+
Task<BookDto> CreateBookAsync(BookCreateRequest request);
|
| 11 |
+
Task<BookDto?> UpdateBookAsync(int id, BookUpdateRequest request);
|
| 12 |
+
Task<bool> DeleteBookAsync(int id);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
public class BookService : IBookService
|
| 16 |
+
{
|
| 17 |
+
private readonly LibraryManagementContext _context;
|
| 18 |
+
|
| 19 |
+
public BookService(LibraryManagementContext context)
|
| 20 |
+
{
|
| 21 |
+
_context = context;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
public async Task<IEnumerable<BookDto>> GetAllBooksAsync()
|
| 25 |
+
{
|
| 26 |
+
return await _context.Books
|
| 27 |
+
.Where(b => b.IsActive)
|
| 28 |
+
.Select(b => MapToDto(b))
|
| 29 |
+
.ToListAsync();
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
public async Task<BookDto?> GetBookByIdAsync(int id)
|
| 33 |
+
{
|
| 34 |
+
var book = await _context.Books.FindAsync(id);
|
| 35 |
+
if (book == null || !book.IsActive) return null;
|
| 36 |
+
return MapToDto(book);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
public async Task<BookDto> CreateBookAsync(BookCreateRequest request)
|
| 40 |
+
{
|
| 41 |
+
if (await _context.Books.AnyAsync(b => b.Isbn == request.Isbn))
|
| 42 |
+
{
|
| 43 |
+
throw new Exception("A book with this ISBN already exists.");
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
var book = new Book
|
| 47 |
+
{
|
| 48 |
+
Title = request.Title,
|
| 49 |
+
Isbn = request.Isbn,
|
| 50 |
+
Author = request.Author,
|
| 51 |
+
Description = request.Description,
|
| 52 |
+
TotalCopies = request.TotalCopies,
|
| 53 |
+
AvailableCopies = request.TotalCopies,
|
| 54 |
+
Status = request.TotalCopies > 0 ? "Available" : "Out Of Stock",
|
| 55 |
+
IsActive = true,
|
| 56 |
+
CreatedAt = DateTime.UtcNow
|
| 57 |
+
};
|
| 58 |
+
|
| 59 |
+
_context.Books.Add(book);
|
| 60 |
+
await _context.SaveChangesAsync();
|
| 61 |
+
|
| 62 |
+
return MapToDto(book);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
public async Task<BookDto?> UpdateBookAsync(int id, BookUpdateRequest request)
|
| 66 |
+
{
|
| 67 |
+
var book = await _context.Books.FindAsync(id);
|
| 68 |
+
if (book == null || !book.IsActive) return null;
|
| 69 |
+
|
| 70 |
+
book.Title = request.Title;
|
| 71 |
+
book.Author = request.Author;
|
| 72 |
+
book.Description = request.Description;
|
| 73 |
+
|
| 74 |
+
// Basic logic to sync availability when total copies change
|
| 75 |
+
int difference = request.TotalCopies - book.TotalCopies;
|
| 76 |
+
book.TotalCopies = request.TotalCopies;
|
| 77 |
+
book.AvailableCopies += difference;
|
| 78 |
+
|
| 79 |
+
if (book.AvailableCopies < 0) book.AvailableCopies = 0;
|
| 80 |
+
book.Status = book.AvailableCopies > 0 ? "Available" : "Out Of Stock";
|
| 81 |
+
|
| 82 |
+
book.UpdatedAt = DateTime.UtcNow;
|
| 83 |
+
|
| 84 |
+
await _context.SaveChangesAsync();
|
| 85 |
+
return MapToDto(book);
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
public async Task<bool> DeleteBookAsync(int id)
|
| 89 |
+
{
|
| 90 |
+
var book = await _context.Books.FindAsync(id);
|
| 91 |
+
if (book == null) return false;
|
| 92 |
+
|
| 93 |
+
book.IsActive = false;
|
| 94 |
+
book.UpdatedAt = DateTime.UtcNow;
|
| 95 |
+
|
| 96 |
+
await _context.SaveChangesAsync();
|
| 97 |
+
return true;
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
private static BookDto MapToDto(Book book)
|
| 101 |
+
{
|
| 102 |
+
return new BookDto
|
| 103 |
+
{
|
| 104 |
+
Id = book.Id,
|
| 105 |
+
Title = book.Title,
|
| 106 |
+
Isbn = book.Isbn,
|
| 107 |
+
Author = book.Author,
|
| 108 |
+
Status = book.Status,
|
| 109 |
+
IsActive = book.IsActive,
|
| 110 |
+
Description = book.Description,
|
| 111 |
+
TotalCopies = book.TotalCopies,
|
| 112 |
+
AvailableCopies = book.AvailableCopies,
|
| 113 |
+
CreatedAt = book.CreatedAt,
|
| 114 |
+
UpdatedAt = book.UpdatedAt
|
| 115 |
+
};
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
public class BookDto
|
| 120 |
+
{
|
| 121 |
+
public int Id { get; set; }
|
| 122 |
+
public string Title { get; set; } = string.Empty;
|
| 123 |
+
public string Isbn { get; set; } = string.Empty;
|
| 124 |
+
public string Author { get; set; } = string.Empty;
|
| 125 |
+
public string Status { get; set; } = string.Empty;
|
| 126 |
+
public bool IsActive { get; set; }
|
| 127 |
+
public string? Description { get; set; }
|
| 128 |
+
public int TotalCopies { get; set; }
|
| 129 |
+
public int AvailableCopies { get; set; }
|
| 130 |
+
public DateTime CreatedAt { get; set; }
|
| 131 |
+
public DateTime? UpdatedAt { get; set; }
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
public class BookCreateRequest
|
| 135 |
+
{
|
| 136 |
+
public string Title { get; set; } = string.Empty;
|
| 137 |
+
public string Isbn { get; set; } = string.Empty;
|
| 138 |
+
public string Author { get; set; } = string.Empty;
|
| 139 |
+
public string? Description { get; set; }
|
| 140 |
+
public int TotalCopies { get; set; }
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
public class BookUpdateRequest
|
| 144 |
+
{
|
| 145 |
+
public string Title { get; set; } = string.Empty;
|
| 146 |
+
public string Author { get; set; } = string.Empty;
|
| 147 |
+
public string? Description { get; set; }
|
| 148 |
+
public int TotalCopies { get; set; }
|
| 149 |
+
}
|
| 150 |
+
}
|
Backend/Features/Borrowing/BorrowingController.cs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
using System.Security.Claims;
|
| 4 |
+
|
| 5 |
+
namespace Backend.Features.Borrowings
|
| 6 |
+
{
|
| 7 |
+
[ApiController]
|
| 8 |
+
[Route("api/borrowings")]
|
| 9 |
+
public class BorrowingController : ControllerBase
|
| 10 |
+
{
|
| 11 |
+
private readonly IBorrowingService _borrowingService;
|
| 12 |
+
|
| 13 |
+
public BorrowingController(IBorrowingService borrowingService)
|
| 14 |
+
{
|
| 15 |
+
_borrowingService = borrowingService;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
[HttpPost("borrow")]
|
| 19 |
+
//[Authorize]
|
| 20 |
+
public async Task<ActionResult<BorrowingDto>> BorrowBook([FromBody] BorrowRequest request)
|
| 21 |
+
{
|
| 22 |
+
try
|
| 23 |
+
{
|
| 24 |
+
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
| 25 |
+
if (string.IsNullOrEmpty(userIdStr)) return Unauthorized();
|
| 26 |
+
|
| 27 |
+
var userId = int.Parse(userIdStr);
|
| 28 |
+
var borrowing = await _borrowingService.BorrowBookAsync(userId, request.BookId);
|
| 29 |
+
return Ok(borrowing);
|
| 30 |
+
}
|
| 31 |
+
catch (Exception ex)
|
| 32 |
+
{
|
| 33 |
+
return BadRequest(new { message = ex.Message });
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
[HttpPost("return/{id}")]
|
| 38 |
+
//[Authorize(Roles = "Librarian")]
|
| 39 |
+
public async Task<ActionResult<BorrowingDto>> ReturnBook(int id)
|
| 40 |
+
{
|
| 41 |
+
try
|
| 42 |
+
{
|
| 43 |
+
var borrowing = await _borrowingService.ReturnBookAsync(id);
|
| 44 |
+
return Ok(borrowing);
|
| 45 |
+
}
|
| 46 |
+
catch (Exception ex)
|
| 47 |
+
{
|
| 48 |
+
return BadRequest(new { message = ex.Message });
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
[HttpGet("me")]
|
| 53 |
+
//[Authorize]
|
| 54 |
+
public async Task<ActionResult<IEnumerable<BorrowingDto>>> GetMyBorrowings()
|
| 55 |
+
{
|
| 56 |
+
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
| 57 |
+
if (string.IsNullOrEmpty(userIdStr)) return Unauthorized();
|
| 58 |
+
|
| 59 |
+
var userId = int.Parse(userIdStr);
|
| 60 |
+
var borrowings = await _borrowingService.GetUserBorrowingsAsync(userId);
|
| 61 |
+
return Ok(borrowings);
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
[HttpGet]
|
| 65 |
+
//[Authorize(Roles = "Librarian")]
|
| 66 |
+
public async Task<ActionResult<IEnumerable<BorrowingDto>>> GetAllBorrowings()
|
| 67 |
+
{
|
| 68 |
+
var borrowings = await _borrowingService.GetAllBorrowingsAsync();
|
| 69 |
+
return Ok(borrowings);
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
}
|
Backend/Features/Borrowing/BorrowingModels.cs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System;
|
| 2 |
+
|
| 3 |
+
namespace Backend.Features.Borrowings
|
| 4 |
+
{
|
| 5 |
+
public class BorrowingDto
|
| 6 |
+
{
|
| 7 |
+
public int Id { get; set; }
|
| 8 |
+
public int UserId { get; set; }
|
| 9 |
+
public string UserEmail { get; set; } = string.Empty;
|
| 10 |
+
public int BookId { get; set; }
|
| 11 |
+
public string BookTitle { get; set; } = string.Empty;
|
| 12 |
+
public DateTime BorrowDate { get; set; }
|
| 13 |
+
public DateTime DueDate { get; set; }
|
| 14 |
+
public DateTime? ReturnDate { get; set; }
|
| 15 |
+
public string Status { get; set; } = string.Empty;
|
| 16 |
+
public decimal FineAmount { get; set; }
|
| 17 |
+
public bool IsFinePaid { get; set; }
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
public class BorrowRequest
|
| 21 |
+
{
|
| 22 |
+
public int BookId { get; set; }
|
| 23 |
+
}
|
| 24 |
+
}
|
Backend/Features/Borrowing/BorrowingService.cs
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
|
| 4 |
+
namespace Backend.Features.Borrowings
|
| 5 |
+
{
|
| 6 |
+
public interface IBorrowingService
|
| 7 |
+
{
|
| 8 |
+
Task<BorrowingDto> BorrowBookAsync(int userId, int bookId);
|
| 9 |
+
Task<BorrowingDto> ReturnBookAsync(int borrowingId);
|
| 10 |
+
Task<IEnumerable<BorrowingDto>> GetUserBorrowingsAsync(int userId);
|
| 11 |
+
Task<IEnumerable<BorrowingDto>> GetAllBorrowingsAsync();
|
| 12 |
+
}
|
| 13 |
+
|
| 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)
|
| 25 |
+
{
|
| 26 |
+
// 1. Check for Active Membership
|
| 27 |
+
var subscription = await _context.UserSubscriptions
|
| 28 |
+
.Include(s => s.Membership)
|
| 29 |
+
.Where(s => s.UserId == userId && s.IsActive && s.ExpiryDate > DateTime.UtcNow)
|
| 30 |
+
.FirstOrDefaultAsync();
|
| 31 |
+
|
| 32 |
+
if (subscription == null)
|
| 33 |
+
{
|
| 34 |
+
throw new Exception("Active membership required to borrow books.");
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
// 2. Check for Overdue Books or Unpaid Fines
|
| 38 |
+
var hasBlockers = await _context.Borrowings
|
| 39 |
+
.AnyAsync(b => b.UserId == userId &&
|
| 40 |
+
((b.Status == "Borrowed" && b.DueDate < DateTime.UtcNow) || (b.FineAmount > 0 && !b.IsFinePaid)));
|
| 41 |
+
|
| 42 |
+
if (hasBlockers)
|
| 43 |
+
{
|
| 44 |
+
throw new Exception("Borrowing blocked: You have overdue books or unpaid fines.");
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// 3. Check MaxBooks limit
|
| 48 |
+
var activeBorrowCount = await _context.Borrowings
|
| 49 |
+
.CountAsync(b => b.UserId == userId && b.Status == "Borrowed");
|
| 50 |
+
|
| 51 |
+
if (activeBorrowCount >= subscription.Membership.MaxBooks)
|
| 52 |
+
{
|
| 53 |
+
throw new Exception($"Borrowing limit reached: You can only borrow {subscription.Membership.MaxBooks} books at a time.");
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// 4. Check Book Availability
|
| 57 |
+
var book = await _context.Books.FindAsync(bookId);
|
| 58 |
+
if (book == null || !book.IsActive || book.AvailableCopies <= 0)
|
| 59 |
+
{
|
| 60 |
+
throw new Exception("Book is currently unavailable.");
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// 5. Create Borrowing Record
|
| 64 |
+
var borrowing = new Borrowing
|
| 65 |
+
{
|
| 66 |
+
UserId = userId,
|
| 67 |
+
BookId = bookId,
|
| 68 |
+
BorrowDate = DateTime.UtcNow,
|
| 69 |
+
DueDate = DateTime.UtcNow.AddDays(subscription.Membership.BorrowingDays),
|
| 70 |
+
Status = "Borrowed",
|
| 71 |
+
FineAmount = 0,
|
| 72 |
+
IsFinePaid = false
|
| 73 |
+
};
|
| 74 |
+
|
| 75 |
+
// 6. Update Book Stock
|
| 76 |
+
book.AvailableCopies--;
|
| 77 |
+
if (book.AvailableCopies == 0)
|
| 78 |
+
{
|
| 79 |
+
book.Status = "Out Of Stock";
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
_context.Borrowings.Add(borrowing);
|
| 83 |
+
await _context.SaveChangesAsync();
|
| 84 |
+
|
| 85 |
+
// Load relations for mapping
|
| 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 |
+
|
| 92 |
+
public async Task<BorrowingDto> ReturnBookAsync(int borrowingId)
|
| 93 |
+
{
|
| 94 |
+
var borrowing = await _context.Borrowings
|
| 95 |
+
.Include(b => b.Book)
|
| 96 |
+
.Include(b => b.User)
|
| 97 |
+
.FirstOrDefaultAsync(b => b.Id == borrowingId);
|
| 98 |
+
|
| 99 |
+
if (borrowing == null) throw new Exception("Borrowing record not found.");
|
| 100 |
+
if (borrowing.Status == "Returned") throw new Exception("Book has already been returned.");
|
| 101 |
+
|
| 102 |
+
var now = DateTime.UtcNow;
|
| 103 |
+
borrowing.ReturnDate = now;
|
| 104 |
+
borrowing.Status = "Returned";
|
| 105 |
+
|
| 106 |
+
// Calculate Fine
|
| 107 |
+
if (now > borrowing.DueDate)
|
| 108 |
+
{
|
| 109 |
+
var overdueDays = (now.Date - borrowing.DueDate.Date).Days;
|
| 110 |
+
if (overdueDays > 0)
|
| 111 |
+
{
|
| 112 |
+
borrowing.FineAmount = overdueDays * FinePerDay;
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
// Update Book Stock
|
| 117 |
+
var book = borrowing.Book;
|
| 118 |
+
book.AvailableCopies++;
|
| 119 |
+
book.Status = "Available";
|
| 120 |
+
|
| 121 |
+
await _context.SaveChangesAsync();
|
| 122 |
+
return MapToDto(borrowing);
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
public async Task<IEnumerable<BorrowingDto>> GetUserBorrowingsAsync(int userId)
|
| 126 |
+
{
|
| 127 |
+
var borrowings = await _context.Borrowings
|
| 128 |
+
.Include(b => b.Book)
|
| 129 |
+
.Include(b => b.User)
|
| 130 |
+
.Where(b => b.UserId == userId)
|
| 131 |
+
.OrderByDescending(b => b.BorrowDate)
|
| 132 |
+
.ToListAsync();
|
| 133 |
+
|
| 134 |
+
return borrowings.Select(MapToDto);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
public async Task<IEnumerable<BorrowingDto>> GetAllBorrowingsAsync()
|
| 138 |
+
{
|
| 139 |
+
var borrowings = await _context.Borrowings
|
| 140 |
+
.Include(b => b.Book)
|
| 141 |
+
.Include(b => b.User)
|
| 142 |
+
.OrderByDescending(b => b.BorrowDate)
|
| 143 |
+
.ToListAsync();
|
| 144 |
+
|
| 145 |
+
return borrowings.Select(MapToDto);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
private static BorrowingDto MapToDto(Borrowing b)
|
| 149 |
+
{
|
| 150 |
+
return new BorrowingDto
|
| 151 |
+
{
|
| 152 |
+
Id = b.Id,
|
| 153 |
+
UserId = b.UserId,
|
| 154 |
+
UserEmail = b.User?.Email ?? "Unknown",
|
| 155 |
+
BookId = b.BookId,
|
| 156 |
+
BookTitle = b.Book?.Title ?? "Unknown",
|
| 157 |
+
BorrowDate = b.BorrowDate,
|
| 158 |
+
DueDate = b.DueDate,
|
| 159 |
+
ReturnDate = b.ReturnDate,
|
| 160 |
+
Status = b.Status,
|
| 161 |
+
FineAmount = b.FineAmount,
|
| 162 |
+
IsFinePaid = b.IsFinePaid
|
| 163 |
+
};
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
}
|
Backend/Features/Categories/CategoryController.cs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
|
| 4 |
+
namespace Backend.Features.Categories
|
| 5 |
+
{
|
| 6 |
+
[ApiController]
|
| 7 |
+
[Route("api/categories")]
|
| 8 |
+
public class CategoryController : ControllerBase
|
| 9 |
+
{
|
| 10 |
+
private readonly ICategoryService _categoryService;
|
| 11 |
+
|
| 12 |
+
public CategoryController(ICategoryService categoryService)
|
| 13 |
+
{
|
| 14 |
+
_categoryService = categoryService;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
[HttpGet]
|
| 18 |
+
//[Authorize] // Accessible by both Librarian and Member
|
| 19 |
+
public async Task<ActionResult<IEnumerable<CategoryDto>>> GetCategories()
|
| 20 |
+
{
|
| 21 |
+
var categories = await _categoryService.GetAllCategoriesAsync();
|
| 22 |
+
return Ok(categories);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
[HttpPost]
|
| 26 |
+
//[Authorize(Roles = "Librarian")]
|
| 27 |
+
public async Task<ActionResult<CategoryDto>> CreateCategory([FromBody] CategoryCreateRequest request)
|
| 28 |
+
{
|
| 29 |
+
try
|
| 30 |
+
{
|
| 31 |
+
var category = await _categoryService.CreateCategoryAsync(request);
|
| 32 |
+
return CreatedAtAction(nameof(GetCategories), category);
|
| 33 |
+
}
|
| 34 |
+
catch (Exception ex)
|
| 35 |
+
{
|
| 36 |
+
return BadRequest(new { message = ex.Message });
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
[HttpDelete("{id}")]
|
| 41 |
+
//[Authorize(Roles = "Librarian")]
|
| 42 |
+
public async Task<IActionResult> DeleteCategory(int id)
|
| 43 |
+
{
|
| 44 |
+
var success = await _categoryService.DeleteCategoryAsync(id);
|
| 45 |
+
if (!success) return NotFound();
|
| 46 |
+
return NoContent();
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
}
|
Backend/Features/Categories/CategoryService.cs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
|
| 4 |
+
namespace Backend.Features.Categories
|
| 5 |
+
{
|
| 6 |
+
public interface ICategoryService
|
| 7 |
+
{
|
| 8 |
+
Task<IEnumerable<CategoryDto>> GetAllCategoriesAsync();
|
| 9 |
+
Task<CategoryDto> CreateCategoryAsync(CategoryCreateRequest request);
|
| 10 |
+
Task<bool> DeleteCategoryAsync(int id);
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
public class CategoryService : ICategoryService
|
| 14 |
+
{
|
| 15 |
+
private readonly LibraryManagementContext _context;
|
| 16 |
+
|
| 17 |
+
public CategoryService(LibraryManagementContext context)
|
| 18 |
+
{
|
| 19 |
+
_context = context;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
public async Task<IEnumerable<CategoryDto>> GetAllCategoriesAsync()
|
| 23 |
+
{
|
| 24 |
+
return await _context.Categories
|
| 25 |
+
.Select(c => new CategoryDto { Id = c.Id, Name = c.Name })
|
| 26 |
+
.ToListAsync();
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
public async Task<CategoryDto> CreateCategoryAsync(CategoryCreateRequest request)
|
| 30 |
+
{
|
| 31 |
+
if (await _context.Categories.AnyAsync(c => c.Name == request.Name))
|
| 32 |
+
{
|
| 33 |
+
throw new Exception("Category with this name already exists.");
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
var category = new Category
|
| 37 |
+
{
|
| 38 |
+
Name = request.Name
|
| 39 |
+
};
|
| 40 |
+
|
| 41 |
+
_context.Categories.Add(category);
|
| 42 |
+
await _context.SaveChangesAsync();
|
| 43 |
+
|
| 44 |
+
return new CategoryDto { Id = category.Id, Name = category.Name };
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
public async Task<bool> DeleteCategoryAsync(int id)
|
| 48 |
+
{
|
| 49 |
+
var category = await _context.Categories.FindAsync(id);
|
| 50 |
+
if (category == null) return false;
|
| 51 |
+
|
| 52 |
+
// Note: EF automatically handles removing associations in many-to-many join tables
|
| 53 |
+
_context.Categories.Remove(category);
|
| 54 |
+
await _context.SaveChangesAsync();
|
| 55 |
+
return true;
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
public class CategoryDto
|
| 60 |
+
{
|
| 61 |
+
public int Id { get; set; }
|
| 62 |
+
public string Name { get; set; } = string.Empty;
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
public class CategoryCreateRequest
|
| 66 |
+
{
|
| 67 |
+
public string Name { get; set; } = string.Empty;
|
| 68 |
+
}
|
| 69 |
+
}
|
Backend/Features/Subscriptions/SubscriptionController.cs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
using System.Security.Claims;
|
| 4 |
+
|
| 5 |
+
namespace Backend.Features.Subscriptions
|
| 6 |
+
{
|
| 7 |
+
[ApiController]
|
| 8 |
+
[Route("api")]
|
| 9 |
+
public class SubscriptionController : ControllerBase
|
| 10 |
+
{
|
| 11 |
+
private readonly ISubscriptionService _subscriptionService;
|
| 12 |
+
|
| 13 |
+
public SubscriptionController(ISubscriptionService subscriptionService)
|
| 14 |
+
{
|
| 15 |
+
_subscriptionService = subscriptionService;
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
[HttpGet("memberships")]
|
| 19 |
+
[AllowAnonymous]
|
| 20 |
+
public async Task<ActionResult<IEnumerable<MembershipDto>>> GetMemberships()
|
| 21 |
+
{
|
| 22 |
+
return Ok(await _subscriptionService.GetMembershipsAsync());
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
[HttpGet("subscriptions/me")]
|
| 26 |
+
[Authorize]
|
| 27 |
+
public async Task<ActionResult<SubscriptionDto>> GetMySubscription()
|
| 28 |
+
{
|
| 29 |
+
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
| 30 |
+
if (string.IsNullOrEmpty(userIdStr)) return Unauthorized();
|
| 31 |
+
|
| 32 |
+
var userId = int.Parse(userIdStr);
|
| 33 |
+
var subscription = await _subscriptionService.GetUserSubscriptionAsync(userId);
|
| 34 |
+
|
| 35 |
+
if (subscription == null) return NotFound(new { message = "No active subscription found." });
|
| 36 |
+
|
| 37 |
+
return Ok(subscription);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
[HttpPost("subscriptions/subscribe")]
|
| 41 |
+
[Authorize]
|
| 42 |
+
public async Task<ActionResult<SubscriptionDto>> Subscribe([FromBody] SubscribeRequest request)
|
| 43 |
+
{
|
| 44 |
+
try
|
| 45 |
+
{
|
| 46 |
+
var userIdStr = User.FindFirstValue(ClaimTypes.NameIdentifier);
|
| 47 |
+
if (string.IsNullOrEmpty(userIdStr)) return Unauthorized();
|
| 48 |
+
|
| 49 |
+
var userId = int.Parse(userIdStr);
|
| 50 |
+
var subscription = await _subscriptionService.SubscribeUserAsync(userId, request.MembershipId);
|
| 51 |
+
return Ok(subscription);
|
| 52 |
+
}
|
| 53 |
+
catch (Exception ex)
|
| 54 |
+
{
|
| 55 |
+
return BadRequest(new { message = ex.Message });
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
[HttpGet("subscriptions/user/{userId}")]
|
| 60 |
+
[Authorize(Roles = "Admin")]
|
| 61 |
+
public async Task<ActionResult<SubscriptionDto>> GetUserSubscription(int userId)
|
| 62 |
+
{
|
| 63 |
+
var subscription = await _subscriptionService.GetUserSubscriptionAsync(userId);
|
| 64 |
+
if (subscription == null) return NotFound(new { message = "No active subscription found." });
|
| 65 |
+
|
| 66 |
+
return Ok(subscription);
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
[HttpPost("subscriptions/admin-subscribe")]
|
| 70 |
+
[Authorize(Roles = "Admin")]
|
| 71 |
+
public async Task<ActionResult<SubscriptionDto>> AdminSubscribe([FromBody] AdminSubscribeRequest request)
|
| 72 |
+
{
|
| 73 |
+
try
|
| 74 |
+
{
|
| 75 |
+
var subscription = await _subscriptionService.SubscribeUserAsync(request.UserId, request.MembershipId);
|
| 76 |
+
return Ok(subscription);
|
| 77 |
+
}
|
| 78 |
+
catch (Exception ex)
|
| 79 |
+
{
|
| 80 |
+
return BadRequest(new { message = ex.Message });
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
}
|
Backend/Features/Subscriptions/SubscriptionModels.cs
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System;
|
| 2 |
+
|
| 3 |
+
namespace Backend.Features.Subscriptions
|
| 4 |
+
{
|
| 5 |
+
public class MembershipDto
|
| 6 |
+
{
|
| 7 |
+
public int Id { get; set; }
|
| 8 |
+
public string Type { get; set; } = string.Empty;
|
| 9 |
+
public int MaxBooks { get; set; }
|
| 10 |
+
public int BorrowingDays { get; set; }
|
| 11 |
+
public decimal Price { get; set; }
|
| 12 |
+
public string Currency { get; set; } = "MMK";
|
| 13 |
+
public int DurationMonths { get; set; }
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
public class SubscriptionDto
|
| 17 |
+
{
|
| 18 |
+
public int Id { get; set; }
|
| 19 |
+
public int UserId { get; set; }
|
| 20 |
+
public int MembershipId { get; set; }
|
| 21 |
+
public string MembershipType { get; set; } = string.Empty;
|
| 22 |
+
public DateTime StartDate { get; set; }
|
| 23 |
+
public DateTime ExpiryDate { get; set; }
|
| 24 |
+
public bool IsActive { get; set; }
|
| 25 |
+
public bool IsExpired => DateTime.UtcNow > ExpiryDate;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
public class SubscribeRequest
|
| 29 |
+
{
|
| 30 |
+
public int MembershipId { get; set; }
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
public class AdminSubscribeRequest
|
| 34 |
+
{
|
| 35 |
+
public int UserId { get; set; }
|
| 36 |
+
public int MembershipId { get; set; }
|
| 37 |
+
}
|
| 38 |
+
}
|
Backend/Features/Subscriptions/SubscriptionService.cs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
|
| 4 |
+
namespace Backend.Features.Subscriptions
|
| 5 |
+
{
|
| 6 |
+
public interface ISubscriptionService
|
| 7 |
+
{
|
| 8 |
+
Task<IEnumerable<MembershipDto>> GetMembershipsAsync();
|
| 9 |
+
Task<SubscriptionDto?> GetUserSubscriptionAsync(int userId);
|
| 10 |
+
Task<SubscriptionDto> SubscribeUserAsync(int userId, int membershipId);
|
| 11 |
+
}
|
| 12 |
+
|
| 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()
|
| 23 |
+
{
|
| 24 |
+
return await _context.Memberships
|
| 25 |
+
.Select(m => new MembershipDto
|
| 26 |
+
{
|
| 27 |
+
Id = m.Id,
|
| 28 |
+
Type = m.Type,
|
| 29 |
+
MaxBooks = m.MaxBooks,
|
| 30 |
+
BorrowingDays = m.BorrowingDays,
|
| 31 |
+
Price = m.Price,
|
| 32 |
+
DurationMonths = m.DurationMonths
|
| 33 |
+
}).ToListAsync();
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
public async Task<SubscriptionDto?> GetUserSubscriptionAsync(int userId)
|
| 37 |
+
{
|
| 38 |
+
var subscription = await _context.UserSubscriptions
|
| 39 |
+
.Include(s => s.Membership)
|
| 40 |
+
.Where(s => s.UserId == userId && s.IsActive)
|
| 41 |
+
.OrderByDescending(s => s.StartDate)
|
| 42 |
+
.FirstOrDefaultAsync();
|
| 43 |
+
|
| 44 |
+
if (subscription == null) return null;
|
| 45 |
+
|
| 46 |
+
return MapToDto(subscription);
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
public async Task<SubscriptionDto> SubscribeUserAsync(int userId, int membershipId)
|
| 50 |
+
{
|
| 51 |
+
var membership = await _context.Memberships.FindAsync(membershipId);
|
| 52 |
+
if (membership == null) throw new Exception("Membership plan not found.");
|
| 53 |
+
|
| 54 |
+
// Deactivate any existing active subscriptions for this user
|
| 55 |
+
var activeSubscriptions = await _context.UserSubscriptions
|
| 56 |
+
.Where(s => s.UserId == userId && s.IsActive)
|
| 57 |
+
.ToListAsync();
|
| 58 |
+
|
| 59 |
+
foreach (var s in activeSubscriptions)
|
| 60 |
+
{
|
| 61 |
+
s.IsActive = false;
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
var subscription = new UserSubscription
|
| 65 |
+
{
|
| 66 |
+
UserId = userId,
|
| 67 |
+
MembershipId = membershipId,
|
| 68 |
+
StartDate = DateTime.UtcNow,
|
| 69 |
+
ExpiryDate = DateTime.UtcNow.AddMonths(membership.DurationMonths),
|
| 70 |
+
IsActive = true
|
| 71 |
+
};
|
| 72 |
+
|
| 73 |
+
_context.UserSubscriptions.Add(subscription);
|
| 74 |
+
await _context.SaveChangesAsync();
|
| 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 |
+
}
|
| 81 |
+
|
| 82 |
+
private static SubscriptionDto MapToDto(UserSubscription subscription)
|
| 83 |
+
{
|
| 84 |
+
return new SubscriptionDto
|
| 85 |
+
{
|
| 86 |
+
Id = subscription.Id,
|
| 87 |
+
UserId = subscription.UserId,
|
| 88 |
+
MembershipId = subscription.MembershipId,
|
| 89 |
+
MembershipType = subscription.Membership.Type,
|
| 90 |
+
StartDate = subscription.StartDate,
|
| 91 |
+
ExpiryDate = subscription.ExpiryDate,
|
| 92 |
+
IsActive = subscription.IsActive
|
| 93 |
+
};
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
}
|
Backend/Features/Users/UserController.cs
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
|
| 4 |
+
namespace Backend.Features.Users
|
| 5 |
+
{
|
| 6 |
+
[ApiController]
|
| 7 |
+
[Route("api/users")]
|
| 8 |
+
public class UserController : ControllerBase
|
| 9 |
+
{
|
| 10 |
+
private readonly IUserService _userService;
|
| 11 |
+
|
| 12 |
+
public UserController(IUserService userService)
|
| 13 |
+
{
|
| 14 |
+
_userService = userService;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
[HttpGet]
|
| 18 |
+
//[Authorize(Roles = "Librarian")]
|
| 19 |
+
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers()
|
| 20 |
+
{
|
| 21 |
+
var users = await _userService.GetAllUsersAsync();
|
| 22 |
+
return Ok(users);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
[HttpGet("{id}")]
|
| 26 |
+
//[Authorize(Roles = "Librarian")]
|
| 27 |
+
public async Task<ActionResult<UserDto>> GetUser(int id)
|
| 28 |
+
{
|
| 29 |
+
var user = await _userService.GetUserByIdAsync(id);
|
| 30 |
+
if (user == null) return NotFound();
|
| 31 |
+
return Ok(user);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
[HttpPost]
|
| 35 |
+
[AllowAnonymous]
|
| 36 |
+
public async Task<ActionResult<UserDto>> CreateUser([FromBody] UserCreateRequest request)
|
| 37 |
+
{
|
| 38 |
+
try
|
| 39 |
+
{
|
| 40 |
+
var response = await _userService.CreateUserAsync(request);
|
| 41 |
+
return CreatedAtAction(nameof(GetUser), new { id = response.Id }, response);
|
| 42 |
+
}
|
| 43 |
+
catch (Exception ex)
|
| 44 |
+
{
|
| 45 |
+
return BadRequest(new { message = ex.Message });
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
[HttpPut("{id}")]
|
| 50 |
+
//[Authorize(Roles = "Librarian")]
|
| 51 |
+
public async Task<ActionResult<UserDto>> UpdateUser(int id, [FromBody] UserUpdateRequest request)
|
| 52 |
+
{
|
| 53 |
+
var updatedUser = await _userService.UpdateUserAsync(id, request);
|
| 54 |
+
if (updatedUser == null) return NotFound();
|
| 55 |
+
return Ok(updatedUser);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
[HttpPatch("{id}/role")]
|
| 59 |
+
//[Authorize(Roles = "Librarian")]
|
| 60 |
+
public async Task<IActionResult> UpdateUserRole(int id, [FromBody] UserRoleUpdateRequest request)
|
| 61 |
+
{
|
| 62 |
+
var success = await _userService.UpdateUserRoleAsync(id, request.Role);
|
| 63 |
+
if (!success) return NotFound();
|
| 64 |
+
return NoContent();
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
[HttpDelete("{id}")]
|
| 68 |
+
//[Authorize(Roles = "Librarian")]
|
| 69 |
+
public async Task<IActionResult> DeleteUser(int id)
|
| 70 |
+
{
|
| 71 |
+
var success = await _userService.DeleteUserAsync(id);
|
| 72 |
+
if (!success) return NotFound();
|
| 73 |
+
return NoContent();
|
| 74 |
+
}
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
Backend/Features/Users/UserService.cs
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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;
|
| 7 |
+
using System.Threading.Tasks;
|
| 8 |
+
|
| 9 |
+
namespace Backend.Features.Users
|
| 10 |
+
{
|
| 11 |
+
public interface IUserService
|
| 12 |
+
{
|
| 13 |
+
Task<IEnumerable<UserDto>> GetAllUsersAsync();
|
| 14 |
+
Task<UserDto?> GetUserByIdAsync(int id);
|
| 15 |
+
Task<UserDto> CreateUserAsync(UserCreateRequest request);
|
| 16 |
+
Task<UserDto?> UpdateUserAsync(int id, UserUpdateRequest request);
|
| 17 |
+
Task<bool> UpdateUserRoleAsync(int id, string role);
|
| 18 |
+
Task<bool> DeleteUserAsync(int id);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
public class UserService : IUserService
|
| 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()
|
| 33 |
+
{
|
| 34 |
+
var users = await _context.Users.ToListAsync();
|
| 35 |
+
return users.Select(MapToDto);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
public async Task<UserDto?> GetUserByIdAsync(int id)
|
| 39 |
+
{
|
| 40 |
+
var user = await _context.Users.FindAsync(id);
|
| 41 |
+
return user == null ? null : MapToDto(user);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
public async Task<UserDto> CreateUserAsync(UserCreateRequest request)
|
| 45 |
+
{
|
| 46 |
+
if (await _context.Users.AnyAsync(u => u.Email == request.Email))
|
| 47 |
+
{
|
| 48 |
+
throw new Exception("Email already registered.");
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
var user = new User
|
| 52 |
+
{
|
| 53 |
+
FullName = request.FullName,
|
| 54 |
+
Email = request.Email,
|
| 55 |
+
PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password),
|
| 56 |
+
PhoneNumber = request.PhoneNumber,
|
| 57 |
+
Address = request.Address,
|
| 58 |
+
StudentId = request.StudentId,
|
| 59 |
+
Role = "Member", // Default role
|
| 60 |
+
IsActive = true,
|
| 61 |
+
CreatedAt = DateTime.UtcNow
|
| 62 |
+
};
|
| 63 |
+
|
| 64 |
+
_context.Users.Add(user);
|
| 65 |
+
await _context.SaveChangesAsync();
|
| 66 |
+
|
| 67 |
+
if (!string.IsNullOrEmpty(user.StudentId))
|
| 68 |
+
{
|
| 69 |
+
await _subscriptionService.SubscribeUserAsync(user.Id, 3); // 3 is "Basic Yearly"
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
return MapToDto(user);
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
public async Task<UserDto?> UpdateUserAsync(int id, UserUpdateRequest request)
|
| 76 |
+
{
|
| 77 |
+
var user = await _context.Users.FindAsync(id);
|
| 78 |
+
if (user == null) return null;
|
| 79 |
+
|
| 80 |
+
bool isNewlyStudent = string.IsNullOrEmpty(user.StudentId) && !string.IsNullOrEmpty(request.StudentId);
|
| 81 |
+
|
| 82 |
+
user.FullName = request.FullName;
|
| 83 |
+
user.PhoneNumber = request.PhoneNumber;
|
| 84 |
+
user.Address = request.Address;
|
| 85 |
+
user.StudentId = request.StudentId;
|
| 86 |
+
user.UpdatedAt = DateTime.UtcNow;
|
| 87 |
+
|
| 88 |
+
await _context.SaveChangesAsync();
|
| 89 |
+
|
| 90 |
+
if (isNewlyStudent)
|
| 91 |
+
{
|
| 92 |
+
await _subscriptionService.SubscribeUserAsync(user.Id, 3);
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
return MapToDto(user);
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
public async Task<bool> UpdateUserRoleAsync(int id, string role)
|
| 99 |
+
{
|
| 100 |
+
var user = await _context.Users.FindAsync(id);
|
| 101 |
+
if (user == null) return false;
|
| 102 |
+
|
| 103 |
+
user.Role = role;
|
| 104 |
+
user.UpdatedAt = DateTime.UtcNow;
|
| 105 |
+
|
| 106 |
+
await _context.SaveChangesAsync();
|
| 107 |
+
return true;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
public async Task<bool> DeleteUserAsync(int id)
|
| 111 |
+
{
|
| 112 |
+
var user = await _context.Users.FindAsync(id);
|
| 113 |
+
if (user == null) return false;
|
| 114 |
+
|
| 115 |
+
// Soft delete
|
| 116 |
+
user.IsActive = false;
|
| 117 |
+
user.UpdatedAt = DateTime.UtcNow;
|
| 118 |
+
|
| 119 |
+
await _context.SaveChangesAsync();
|
| 120 |
+
return true;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
private static UserDto MapToDto(User user)
|
| 124 |
+
{
|
| 125 |
+
return new UserDto
|
| 126 |
+
{
|
| 127 |
+
Id = user.Id,
|
| 128 |
+
FullName = user.FullName,
|
| 129 |
+
Email = user.Email,
|
| 130 |
+
PhoneNumber = user.PhoneNumber,
|
| 131 |
+
Role = user.Role,
|
| 132 |
+
IsActive = user.IsActive,
|
| 133 |
+
StudentId = user.StudentId,
|
| 134 |
+
Address = user.Address,
|
| 135 |
+
CreatedAt = user.CreatedAt,
|
| 136 |
+
UpdatedAt = user.UpdatedAt,
|
| 137 |
+
BanStatus = user.BanStatus,
|
| 138 |
+
SuspensionEndDate = user.SuspensionEndDate
|
| 139 |
+
};
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
public class UserDto
|
| 144 |
+
{
|
| 145 |
+
public int Id { get; set; }
|
| 146 |
+
public string Name => FullName;
|
| 147 |
+
public string FullName { get; set; } = string.Empty;
|
| 148 |
+
public string Email { get; set; } = string.Empty;
|
| 149 |
+
public string? PhoneNumber { get; set; }
|
| 150 |
+
public string Role { get; set; } = string.Empty;
|
| 151 |
+
public bool IsActive { get; set; }
|
| 152 |
+
public string? StudentId { get; set; }
|
| 153 |
+
public string? Address { get; set; }
|
| 154 |
+
public DateTime CreatedAt { get; set; }
|
| 155 |
+
public DateTime? UpdatedAt { get; set; }
|
| 156 |
+
public bool? BanStatus { get; set; }
|
| 157 |
+
public DateTime? SuspensionEndDate { get; set; }
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
public class UserCreateRequest
|
| 161 |
+
{
|
| 162 |
+
public string FullName { get; set; } = string.Empty;
|
| 163 |
+
public string Email { get; set; } = string.Empty;
|
| 164 |
+
public string Password { get; set; } = string.Empty;
|
| 165 |
+
public string? PhoneNumber { get; set; }
|
| 166 |
+
public string? StudentId { get; set; }
|
| 167 |
+
public string? Address { get; set; }
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
public class UserUpdateRequest
|
| 171 |
+
{
|
| 172 |
+
public string FullName { get; set; } = string.Empty;
|
| 173 |
+
public string? PhoneNumber { get; set; }
|
| 174 |
+
public string? StudentId { get; set; }
|
| 175 |
+
public string? Address { get; set; }
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
public class UserRoleUpdateRequest
|
| 179 |
+
{
|
| 180 |
+
public string Role { get; set; } = string.Empty;
|
| 181 |
+
}
|
| 182 |
+
}
|
Backend/Program.cs
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System.Text;
|
| 2 |
+
using Database.Models;
|
| 3 |
+
using Backend.Features.Auth;
|
| 4 |
+
using Backend.Features.Users;
|
| 5 |
+
using Backend.Features.Books;
|
| 6 |
+
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
| 7 |
+
using Microsoft.EntityFrameworkCore;
|
| 8 |
+
using Microsoft.IdentityModel.Tokens;
|
| 9 |
+
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();
|
| 19 |
+
builder.Services.AddSwaggerGen(c =>
|
| 20 |
+
{
|
| 21 |
+
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Library Management API", Version = "v1" });
|
| 22 |
+
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
| 23 |
+
{
|
| 24 |
+
Description = "JWT Authorization header using the Bearer scheme.",
|
| 25 |
+
Name = "Authorization",
|
| 26 |
+
In = ParameterLocation.Header,
|
| 27 |
+
Type = SecuritySchemeType.Http,
|
| 28 |
+
Scheme = "bearer",
|
| 29 |
+
BearerFormat = "JWT"
|
| 30 |
+
});
|
| 31 |
+
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
| 32 |
+
{
|
| 33 |
+
{
|
| 34 |
+
new OpenApiSecurityScheme
|
| 35 |
+
{
|
| 36 |
+
Reference = new OpenApiReference
|
| 37 |
+
{
|
| 38 |
+
Type = ReferenceType.SecurityScheme,
|
| 39 |
+
Id = "Bearer"
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
new string[] {}
|
| 43 |
+
}
|
| 44 |
+
});
|
| 45 |
+
});
|
| 46 |
+
|
| 47 |
+
builder.Services.AddDbContext<LibraryManagementContext>(options =>
|
| 48 |
+
options.UseSqlServer(builder.Configuration.GetConnectionString("MssqlConnection")));
|
| 49 |
+
|
| 50 |
+
// Register Services
|
| 51 |
+
builder.Services.AddScoped<IAuthService, AuthService>();
|
| 52 |
+
builder.Services.AddScoped<IUserService, UserService>();
|
| 53 |
+
builder.Services.AddScoped<IBookService, BookService>();
|
| 54 |
+
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 =>
|
| 61 |
+
{
|
| 62 |
+
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
| 63 |
+
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
| 64 |
+
})
|
| 65 |
+
.AddJwtBearer(x =>
|
| 66 |
+
{
|
| 67 |
+
x.RequireHttpsMetadata = false;
|
| 68 |
+
x.SaveToken = true;
|
| 69 |
+
x.TokenValidationParameters = new TokenValidationParameters
|
| 70 |
+
{
|
| 71 |
+
ValidateIssuerSigningKey = true,
|
| 72 |
+
IssuerSigningKey = new SymmetricSecurityKey(key),
|
| 73 |
+
ValidateIssuer = true,
|
| 74 |
+
ValidateAudience = true,
|
| 75 |
+
ValidIssuer = builder.Configuration["Jwt:Issuer"],
|
| 76 |
+
ValidAudience = builder.Configuration["Jwt:Audience"],
|
| 77 |
+
ClockSkew = TimeSpan.Zero
|
| 78 |
+
};
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
var app = builder.Build();
|
| 82 |
+
|
| 83 |
+
app.UseSwagger();
|
| 84 |
+
app.UseSwaggerUI();
|
| 85 |
+
app.UseAuthentication();
|
| 86 |
+
app.UseAuthorization();
|
| 87 |
+
|
| 88 |
+
app.MapControllers();
|
| 89 |
+
|
| 90 |
+
app.Run();
|
| 91 |
+
|
Backend/Properties/launchSettings.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "http://json.schemastore.org/launchsettings.json",
|
| 3 |
+
"iisSettings": {
|
| 4 |
+
"windowsAuthentication": false,
|
| 5 |
+
"anonymousAuthentication": true,
|
| 6 |
+
"iisExpress": {
|
| 7 |
+
"applicationUrl": "http://localhost:13110",
|
| 8 |
+
"sslPort": 44374
|
| 9 |
+
}
|
| 10 |
+
},
|
| 11 |
+
"profiles": {
|
| 12 |
+
"http": {
|
| 13 |
+
"commandName": "Project",
|
| 14 |
+
"dotnetRunMessages": true,
|
| 15 |
+
"launchBrowser": true,
|
| 16 |
+
"launchUrl": "swagger",
|
| 17 |
+
"applicationUrl": "http://localhost:5001",
|
| 18 |
+
"environmentVariables": {
|
| 19 |
+
"ASPNETCORE_ENVIRONMENT": "Development"
|
| 20 |
+
}
|
| 21 |
+
},
|
| 22 |
+
"https": {
|
| 23 |
+
"commandName": "Project",
|
| 24 |
+
"dotnetRunMessages": true,
|
| 25 |
+
"launchBrowser": true,
|
| 26 |
+
"launchUrl": "swagger",
|
| 27 |
+
"applicationUrl": "https://localhost:7028;http://localhost:5199",
|
| 28 |
+
"environmentVariables": {
|
| 29 |
+
"ASPNETCORE_ENVIRONMENT": "Development"
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
"IIS Express": {
|
| 33 |
+
"commandName": "IISExpress",
|
| 34 |
+
"launchBrowser": true,
|
| 35 |
+
"launchUrl": "swagger",
|
| 36 |
+
"environmentVariables": {
|
| 37 |
+
"ASPNETCORE_ENVIRONMENT": "Development"
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|
Backend/WeatherForecast.cs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
namespace Backend
|
| 2 |
+
{
|
| 3 |
+
public class WeatherForecast
|
| 4 |
+
{
|
| 5 |
+
public DateOnly Date { get; set; }
|
| 6 |
+
|
| 7 |
+
public int TemperatureC { get; set; }
|
| 8 |
+
|
| 9 |
+
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
|
| 10 |
+
|
| 11 |
+
public string? Summary { get; set; }
|
| 12 |
+
}
|
| 13 |
+
}
|
Backend/appsettings.Development.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"Logging": {
|
| 3 |
+
"LogLevel": {
|
| 4 |
+
"Default": "Information",
|
| 5 |
+
"Microsoft.AspNetCore": "Warning"
|
| 6 |
+
}
|
| 7 |
+
}
|
| 8 |
+
}
|
Backend/appsettings.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"Logging": {
|
| 3 |
+
"LogLevel": {
|
| 4 |
+
"Default": "Information",
|
| 5 |
+
"Microsoft.AspNetCore": "Warning"
|
| 6 |
+
}
|
| 7 |
+
},
|
| 8 |
+
"AllowedHosts": "*",
|
| 9 |
+
"ConnectionStrings": {
|
| 10 |
+
//"MssqlConnection": "Server=DESKTOP-BP9A061;Database=LibraryManagement;User Id=sa;Password=sasa@123;TrustServerCertificate=True;"
|
| 11 |
+
"MssqlConnection": "Server=.;Database=LibraryManagement;User Id=sa;Password=sasa@123;TrustServerCertificate=True;"
|
| 12 |
+
},
|
| 13 |
+
"Jwt": {
|
| 14 |
+
"Secret": "YourSuperSecretKeyForLibraryManagementSystem_AtLeast32CharsLong",
|
| 15 |
+
"Issuer": "LibraryManagementSystem",
|
| 16 |
+
"Audience": "LibraryManagementUsers"
|
| 17 |
+
}
|
| 18 |
+
}
|
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="
|
| 5 |
</Solution>
|
|
|
|
| 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>
|