Spaces:
Sleeping
Sleeping
Yuyuqt commited on
Commit ·
87c9973
1
Parent(s): c596926
feat: add user management, book management, categories management, membership management, and borrowing management
Browse files- Database/Models/Book.cs +7 -1
- Database/Models/LibraryManagementContext.cs +5 -2
- LibraryManagement.Backend/Features/Auth/AuthController.cs +15 -0
- LibraryManagement.Backend/Features/Auth/AuthService.cs +10 -1
- LibraryManagement.Backend/Features/Books/BookController.cs +67 -0
- LibraryManagement.Backend/Features/Books/BookService.cs +152 -0
- LibraryManagement.Backend/Features/Borrowing/BorrowingController.cs +72 -0
- LibraryManagement.Backend/Features/Borrowing/BorrowingModels.cs +24 -0
- LibraryManagement.Backend/Features/Borrowing/BorrowingService.cs +166 -0
- LibraryManagement.Backend/Features/Categories/CategoryController.cs +49 -0
- LibraryManagement.Backend/Features/Categories/CategoryService.cs +69 -0
- LibraryManagement.Backend/Features/Subscriptions/SubscriptionController.cs +59 -0
- LibraryManagement.Backend/Features/Subscriptions/SubscriptionModels.cs +32 -0
- LibraryManagement.Backend/Features/Subscriptions/SubscriptionService.cs +96 -0
- LibraryManagement.Backend/Features/Users/UserController.cs +57 -3
- LibraryManagement.Backend/Features/Users/UserService.cs +154 -8
- LibraryManagement.Backend/Program.cs +13 -3
Database/Models/Book.cs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
using System.Collections.Generic;
|
| 3 |
|
| 4 |
namespace Database.Models;
|
|
@@ -22,6 +22,12 @@ public partial class Book
|
|
| 22 |
public string? Description { get; set; }
|
| 23 |
|
| 24 |
public string? CoverUrl { get; set; }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
public virtual ICollection<Borrowing> Borrowings { get; set; } = new List<Borrowing>();
|
| 27 |
|
|
|
|
| 1 |
+
using System;
|
| 2 |
using System.Collections.Generic;
|
| 3 |
|
| 4 |
namespace Database.Models;
|
|
|
|
| 22 |
public string? Description { get; set; }
|
| 23 |
|
| 24 |
public string? CoverUrl { get; set; }
|
| 25 |
+
|
| 26 |
+
public int TotalCopies { get; set; }
|
| 27 |
+
|
| 28 |
+
public int AvailableCopies { get; set; }
|
| 29 |
+
|
| 30 |
+
public DateTime? UpdatedAt { get; set; }
|
| 31 |
|
| 32 |
public virtual ICollection<Borrowing> Borrowings { get; set; } = new List<Borrowing>();
|
| 33 |
|
Database/Models/LibraryManagementContext.cs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
|
| 2 |
using System.Collections.Generic;
|
| 3 |
using Microsoft.EntityFrameworkCore;
|
| 4 |
|
|
@@ -53,6 +53,9 @@ public partial class LibraryManagementContext : DbContext
|
|
| 53 |
.HasMaxLength(20)
|
| 54 |
.HasDefaultValue("Available");
|
| 55 |
entity.Property(e => e.Title).HasMaxLength(200);
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
entity.HasMany(d => d.Categories).WithMany(p => p.Books)
|
| 58 |
.UsingEntity<Dictionary<string, object>>(
|
|
@@ -130,7 +133,7 @@ public partial class LibraryManagementContext : DbContext
|
|
| 130 |
entity.Property(e => e.PhoneNumber).HasMaxLength(20);
|
| 131 |
entity.Property(e => e.Role)
|
| 132 |
.HasMaxLength(20)
|
| 133 |
-
.HasDefaultValue("
|
| 134 |
entity.Property(e => e.StudentId).HasMaxLength(20);
|
| 135 |
entity.Property(e => e.SuspensionEndDate).HasColumnType("datetime");
|
| 136 |
entity.Property(e => e.UpdatedAt).HasColumnType("datetime");
|
|
|
|
| 1 |
+
using System;
|
| 2 |
using System.Collections.Generic;
|
| 3 |
using Microsoft.EntityFrameworkCore;
|
| 4 |
|
|
|
|
| 53 |
.HasMaxLength(20)
|
| 54 |
.HasDefaultValue("Available");
|
| 55 |
entity.Property(e => e.Title).HasMaxLength(200);
|
| 56 |
+
entity.Property(e => e.TotalCopies).HasDefaultValue(1);
|
| 57 |
+
entity.Property(e => e.AvailableCopies).HasDefaultValue(1);
|
| 58 |
+
entity.Property(e => e.UpdatedAt).HasColumnType("datetime");
|
| 59 |
|
| 60 |
entity.HasMany(d => d.Categories).WithMany(p => p.Books)
|
| 61 |
.UsingEntity<Dictionary<string, object>>(
|
|
|
|
| 133 |
entity.Property(e => e.PhoneNumber).HasMaxLength(20);
|
| 134 |
entity.Property(e => e.Role)
|
| 135 |
.HasMaxLength(20)
|
| 136 |
+
.HasDefaultValue("Member");
|
| 137 |
entity.Property(e => e.StudentId).HasMaxLength(20);
|
| 138 |
entity.Property(e => e.SuspensionEndDate).HasColumnType("datetime");
|
| 139 |
entity.Property(e => e.UpdatedAt).HasColumnType("datetime");
|
LibraryManagement.Backend/Features/Auth/AuthController.cs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
|
|
| 1 |
using Microsoft.AspNetCore.Mvc;
|
|
|
|
| 2 |
|
| 3 |
namespace LibraryManagement.Backend.Features.Auth
|
| 4 |
{
|
|
@@ -40,5 +42,18 @@ namespace LibraryManagement.Backend.Features.Auth
|
|
| 40 |
return Unauthorized(new { message = ex.Message });
|
| 41 |
}
|
| 42 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
}
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
using System.Security.Claims;
|
| 4 |
|
| 5 |
namespace LibraryManagement.Backend.Features.Auth
|
| 6 |
{
|
|
|
|
| 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 |
}
|
LibraryManagement.Backend/Features/Auth/AuthService.cs
CHANGED
|
@@ -5,6 +5,8 @@ using System.IdentityModel.Tokens.Jwt;
|
|
| 5 |
using System.Security.Claims;
|
| 6 |
using System.Text;
|
| 7 |
|
|
|
|
|
|
|
| 8 |
namespace LibraryManagement.Backend.Features.Auth
|
| 9 |
{
|
| 10 |
public interface IAuthService
|
|
@@ -17,11 +19,13 @@ namespace LibraryManagement.Backend.Features.Auth
|
|
| 17 |
{
|
| 18 |
private readonly LibraryManagementContext _context;
|
| 19 |
private readonly IConfiguration _configuration;
|
|
|
|
| 20 |
|
| 21 |
-
public AuthService(LibraryManagementContext context, IConfiguration configuration)
|
| 22 |
{
|
| 23 |
_context = context;
|
| 24 |
_configuration = configuration;
|
|
|
|
| 25 |
}
|
| 26 |
|
| 27 |
public async Task<AuthResponse> Register(RegisterRequest request)
|
|
@@ -47,6 +51,11 @@ namespace LibraryManagement.Backend.Features.Auth
|
|
| 47 |
_context.Users.Add(user);
|
| 48 |
await _context.SaveChangesAsync();
|
| 49 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
return await AuthenticateUser(user);
|
| 51 |
}
|
| 52 |
|
|
|
|
| 5 |
using System.Security.Claims;
|
| 6 |
using System.Text;
|
| 7 |
|
| 8 |
+
using LibraryManagement.Backend.Features.Subscriptions;
|
| 9 |
+
|
| 10 |
namespace LibraryManagement.Backend.Features.Auth
|
| 11 |
{
|
| 12 |
public interface 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)
|
|
|
|
| 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 |
|
LibraryManagement.Backend/Features/Books/BookController.cs
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
|
| 4 |
+
namespace LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Books/BookService.cs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
|
| 4 |
+
namespace LibraryManagement.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 |
+
CoverUrl = request.CoverUrl,
|
| 53 |
+
TotalCopies = request.TotalCopies,
|
| 54 |
+
AvailableCopies = request.TotalCopies,
|
| 55 |
+
Status = request.TotalCopies > 0 ? "Available" : "Out Of Stock",
|
| 56 |
+
IsActive = true,
|
| 57 |
+
CreatedAt = DateTime.UtcNow
|
| 58 |
+
};
|
| 59 |
+
|
| 60 |
+
_context.Books.Add(book);
|
| 61 |
+
await _context.SaveChangesAsync();
|
| 62 |
+
|
| 63 |
+
return MapToDto(book);
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
public async Task<BookDto?> UpdateBookAsync(int id, BookUpdateRequest request)
|
| 67 |
+
{
|
| 68 |
+
var book = await _context.Books.FindAsync(id);
|
| 69 |
+
if (book == null || !book.IsActive) return null;
|
| 70 |
+
|
| 71 |
+
book.Title = request.Title;
|
| 72 |
+
book.Author = request.Author;
|
| 73 |
+
book.Description = request.Description;
|
| 74 |
+
book.CoverUrl = request.CoverUrl;
|
| 75 |
+
|
| 76 |
+
// Basic logic to sync availability when total copies change
|
| 77 |
+
int difference = request.TotalCopies - book.TotalCopies;
|
| 78 |
+
book.TotalCopies = request.TotalCopies;
|
| 79 |
+
book.AvailableCopies += difference;
|
| 80 |
+
|
| 81 |
+
if (book.AvailableCopies < 0) book.AvailableCopies = 0;
|
| 82 |
+
book.Status = book.AvailableCopies > 0 ? "Available" : "Out Of Stock";
|
| 83 |
+
|
| 84 |
+
book.UpdatedAt = DateTime.UtcNow;
|
| 85 |
+
|
| 86 |
+
await _context.SaveChangesAsync();
|
| 87 |
+
return MapToDto(book);
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
public async Task<bool> DeleteBookAsync(int id)
|
| 91 |
+
{
|
| 92 |
+
var book = await _context.Books.FindAsync(id);
|
| 93 |
+
if (book == null) return false;
|
| 94 |
+
|
| 95 |
+
book.IsActive = false;
|
| 96 |
+
book.UpdatedAt = DateTime.UtcNow;
|
| 97 |
+
|
| 98 |
+
await _context.SaveChangesAsync();
|
| 99 |
+
return true;
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
private static BookDto MapToDto(Book book)
|
| 103 |
+
{
|
| 104 |
+
return new BookDto
|
| 105 |
+
{
|
| 106 |
+
Id = book.Id,
|
| 107 |
+
Title = book.Title,
|
| 108 |
+
Isbn = book.Isbn,
|
| 109 |
+
Author = book.Author,
|
| 110 |
+
Status = book.Status,
|
| 111 |
+
Description = book.Description,
|
| 112 |
+
CoverUrl = book.CoverUrl,
|
| 113 |
+
TotalCopies = book.TotalCopies,
|
| 114 |
+
AvailableCopies = book.AvailableCopies,
|
| 115 |
+
CreatedAt = book.CreatedAt
|
| 116 |
+
};
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
public class BookDto
|
| 121 |
+
{
|
| 122 |
+
public int Id { get; set; }
|
| 123 |
+
public string Title { get; set; } = string.Empty;
|
| 124 |
+
public string Isbn { get; set; } = string.Empty;
|
| 125 |
+
public string Author { get; set; } = string.Empty;
|
| 126 |
+
public string Status { get; set; } = string.Empty;
|
| 127 |
+
public string? Description { get; set; }
|
| 128 |
+
public string? CoverUrl { get; set; }
|
| 129 |
+
public int TotalCopies { get; set; }
|
| 130 |
+
public int AvailableCopies { get; set; }
|
| 131 |
+
public DateTime CreatedAt { 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 string? CoverUrl { get; set; }
|
| 141 |
+
public int TotalCopies { get; set; }
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
public class BookUpdateRequest
|
| 145 |
+
{
|
| 146 |
+
public string Title { get; set; } = string.Empty;
|
| 147 |
+
public string Author { get; set; } = string.Empty;
|
| 148 |
+
public string? Description { get; set; }
|
| 149 |
+
public string? CoverUrl { get; set; }
|
| 150 |
+
public int TotalCopies { get; set; }
|
| 151 |
+
}
|
| 152 |
+
}
|
LibraryManagement.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 LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Borrowing/BorrowingModels.cs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System;
|
| 2 |
+
|
| 3 |
+
namespace LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Borrowing/BorrowingService.cs
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
|
| 4 |
+
namespace LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Categories/CategoryController.cs
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
|
| 4 |
+
namespace LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Categories/CategoryService.cs
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
|
| 4 |
+
namespace LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Subscriptions/SubscriptionController.cs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
+
using Microsoft.AspNetCore.Mvc;
|
| 3 |
+
using System.Security.Claims;
|
| 4 |
+
|
| 5 |
+
namespace LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Subscriptions/SubscriptionModels.cs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using System;
|
| 2 |
+
|
| 3 |
+
namespace LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Subscriptions/SubscriptionService.cs
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
|
| 4 |
+
namespace LibraryManagement.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 |
+
}
|
LibraryManagement.Backend/Features/Users/UserController.cs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
using Microsoft.AspNetCore.Mvc;
|
| 2 |
|
| 3 |
namespace LibraryManagement.Backend.Features.Users
|
|
@@ -13,11 +14,64 @@ namespace LibraryManagement.Backend.Features.Users
|
|
| 13 |
_userService = userService;
|
| 14 |
}
|
| 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
[HttpPost]
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
{
|
| 19 |
-
var
|
| 20 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
}
|
| 23 |
}
|
|
|
|
|
|
| 1 |
+
using Microsoft.AspNetCore.Authorization;
|
| 2 |
using Microsoft.AspNetCore.Mvc;
|
| 3 |
|
| 4 |
namespace LibraryManagement.Backend.Features.Users
|
|
|
|
| 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 |
+
|
LibraryManagement.Backend/Features/Users/UserService.cs
CHANGED
|
@@ -1,30 +1,176 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
namespace LibraryManagement.Backend.Features.Users
|
| 4 |
{
|
| 5 |
public interface IUserService
|
| 6 |
{
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
}
|
| 9 |
|
| 10 |
public class UserService : IUserService
|
| 11 |
{
|
| 12 |
private readonly LibraryManagementContext _context;
|
|
|
|
| 13 |
|
| 14 |
-
public UserService(LibraryManagementContext context
|
|
|
|
| 15 |
_context = context;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
}
|
| 17 |
|
| 18 |
-
|
| 19 |
{
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
}
|
| 22 |
}
|
| 23 |
|
| 24 |
-
public class
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
}
|
| 27 |
|
| 28 |
-
public class
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
}
|
| 30 |
}
|
|
|
|
| 1 |
+
using Database.Models;
|
| 2 |
+
using Microsoft.EntityFrameworkCore;
|
| 3 |
+
using LibraryManagement.Backend.Features.Subscriptions;
|
| 4 |
+
using System;
|
| 5 |
+
using System.Collections.Generic;
|
| 6 |
+
using System.Linq;
|
| 7 |
+
using System.Threading.Tasks;
|
| 8 |
|
| 9 |
namespace LibraryManagement.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 |
+
};
|
| 137 |
}
|
| 138 |
}
|
| 139 |
|
| 140 |
+
public class UserDto
|
| 141 |
+
{
|
| 142 |
+
public int Id { get; set; }
|
| 143 |
+
public string Name => FullName;
|
| 144 |
+
public string FullName { get; set; } = string.Empty;
|
| 145 |
+
public string Email { get; set; } = string.Empty;
|
| 146 |
+
public string? PhoneNumber { get; set; }
|
| 147 |
+
public string Role { get; set; } = string.Empty;
|
| 148 |
+
public bool IsActive { get; set; }
|
| 149 |
+
public string? StudentId { get; set; }
|
| 150 |
+
public string? Address { get; set; }
|
| 151 |
+
public DateTime CreatedAt { get; set; }
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
public class UserCreateRequest
|
| 155 |
+
{
|
| 156 |
+
public string FullName { get; set; } = string.Empty;
|
| 157 |
+
public string Email { get; set; } = string.Empty;
|
| 158 |
+
public string Password { get; set; } = string.Empty;
|
| 159 |
+
public string? PhoneNumber { get; set; }
|
| 160 |
+
public string? StudentId { get; set; }
|
| 161 |
+
public string? Address { get; set; }
|
| 162 |
}
|
| 163 |
|
| 164 |
+
public class UserUpdateRequest
|
| 165 |
+
{
|
| 166 |
+
public string FullName { get; set; } = string.Empty;
|
| 167 |
+
public string? PhoneNumber { get; set; }
|
| 168 |
+
public string? StudentId { get; set; }
|
| 169 |
+
public string? Address { get; set; }
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
public class UserRoleUpdateRequest
|
| 173 |
+
{
|
| 174 |
+
public string Role { get; set; } = string.Empty;
|
| 175 |
}
|
| 176 |
}
|
LibraryManagement.Backend/Program.cs
CHANGED
|
@@ -2,25 +2,31 @@ using System.Text;
|
|
| 2 |
using Database.Models;
|
| 3 |
using LibraryManagement.Backend.Features.Auth;
|
| 4 |
using LibraryManagement.Backend.Features.Users;
|
|
|
|
| 5 |
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
| 6 |
using Microsoft.EntityFrameworkCore;
|
| 7 |
using Microsoft.IdentityModel.Tokens;
|
| 8 |
using Microsoft.OpenApi.Models;
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
var builder = WebApplication.CreateBuilder(args);
|
| 11 |
|
| 12 |
builder.Services.AddControllers();
|
|
|
|
| 13 |
builder.Services.AddEndpointsApiExplorer();
|
| 14 |
builder.Services.AddSwaggerGen(c =>
|
| 15 |
{
|
| 16 |
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Library Management API", Version = "v1" });
|
| 17 |
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
| 18 |
{
|
| 19 |
-
Description = "JWT Authorization header using the Bearer scheme.
|
| 20 |
Name = "Authorization",
|
| 21 |
In = ParameterLocation.Header,
|
| 22 |
-
Type = SecuritySchemeType.
|
| 23 |
-
Scheme = "
|
|
|
|
| 24 |
});
|
| 25 |
c.AddSecurityRequirement(new OpenApiSecurityRequirement
|
| 26 |
{
|
|
@@ -44,6 +50,10 @@ builder.Services.AddDbContext<LibraryManagementContext>(options =>
|
|
| 44 |
// Register Services
|
| 45 |
builder.Services.AddScoped<IAuthService, AuthService>();
|
| 46 |
builder.Services.AddScoped<IUserService, UserService>();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
// Configure JWT Authentication
|
| 49 |
var key = Encoding.ASCII.GetBytes(builder.Configuration["Jwt:Secret"] ?? "YourSuperSecretKeyForLibraryManagementSystem_AtLeast32CharsLong");
|
|
|
|
| 2 |
using Database.Models;
|
| 3 |
using LibraryManagement.Backend.Features.Auth;
|
| 4 |
using LibraryManagement.Backend.Features.Users;
|
| 5 |
+
using LibraryManagement.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 LibraryManagement.Backend.Features.Subscriptions;
|
| 11 |
+
using LibraryManagement.Backend.Features.Categories;
|
| 12 |
+
using LibraryManagement.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 |
{
|
|
|
|
| 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");
|