From f063a5a1de1d8dfd4179a317f215175eecc18152 Mon Sep 17 00:00:00 2001 From: Ahmad Wael Date: Sat, 27 Dec 2025 10:55:12 +0200 Subject: [PATCH 1/3] docs: Update ER diagram and Screen Logic Description --- docs/ERDdiagram.svg | 718 ---------------------------- docs/UI_Screen_Logic_Description.md | 254 +++++++++- docs/chen_er_diagram.svg | 704 +++++++++++++++++++++++++++ 3 files changed, 953 insertions(+), 723 deletions(-) delete mode 100644 docs/ERDdiagram.svg create mode 100644 docs/chen_er_diagram.svg diff --git a/docs/ERDdiagram.svg b/docs/ERDdiagram.svg deleted file mode 100644 index 60410fa..0000000 --- a/docs/ERDdiagram.svg +++ /dev/null @@ -1,718 +0,0 @@ - - - - - - -BookstoreMySQL - - - -Publisher - -Publisher - - - -pub_id - -<u>publisher_id</u> - - - -Publisher->pub_id - - - - -pub_name - -publisher_name - - - -Publisher->pub_name - - - - -pub_address - -address - - - -Publisher->pub_address - - - - -Published_By - -Published_By - - - -Publisher->Published_By - -1 - - - -Publisher_HasPhone - -Has - - - -Publisher->Publisher_HasPhone - -1 - - - -Author - -Author - - - -auth_id - -<u>author_id</u> - - - -Author->auth_id - - - - -auth_name - -author_name - - - -Author->auth_name - - - - -Written_By - -Written_By - - - -Author->Written_By - -N - - - -Book - -Book - - - -ReplenishmentOrder - -ReplenishmentOrder - - - -Book->ReplenishmentOrder - -1 - - - -isbn - -<u>isbn</u> - - - -Book->isbn - - - - -title - -title - - - -Book->title - - - - -pub_year - -pub_year - - - -Book->pub_year - - - - -price - -price - - - -Book->price - - - - -category - -category - - - -Book->category - - - - -stock - -stock - - - -Book->stock - - - - -threshold - -threshold - - - -Book->threshold - - - - -Book->Published_By - -N - - - -Book->Written_By - -N - - - -Contains_Cart - -Contains - - - -Book->Contains_Cart - -N - - - -Contains_Order - -Contains - - - -Book->Contains_Order - -N - - - -User - -User - - - -u_id - -<u>u_id</u> - - - -User->u_id - - - - -username - -username - - - -User->username - - - - -password - -password - - - -User->password - - - - -last_name - -last_name - - - -User->last_name - - - - -first_name - -first_name - - - -User->first_name - - - - -email - -email - - - -User->email - - - - -user_phone - -phone - - - -User->user_phone - - - - -address - -address - - - -User->address - - - - -role - -role - - - -User->role - - - - -Places_Order - -Places - - - -User->Places_Order - -1 - - - -Has_Cart - -Has - - - -User->Has_Cart - -1 - - - -Has_CreditCard - -Has - - - -User->Has_CreditCard - -1 - - - -ReplenishmentOrder->Book - -N - - - -ro_id - -<u>order_id</u> - - - -ReplenishmentOrder->ro_id - - - - -ro_date - -order_date - - - -ReplenishmentOrder->ro_date - - - - -ro_quantity - -quantity - - - -ReplenishmentOrder->ro_quantity - - - - -ro_status - -status - - - -ReplenishmentOrder->ro_status - - - - -CustomerOrder - -CustomerOrder - - - -co_id - -<u>order_id</u> - - - -CustomerOrder->co_id - - - - -co_date - -order_date - - - -CustomerOrder->co_date - - - - -co_total - -total_price - - - -CustomerOrder->co_total - - - - -CustomerOrder->Contains_Order - -1 - - - -Cart - -Cart - - - -cart_id - -<u>cart_id</u> - - - -Cart->cart_id - - - - -Cart->Contains_Cart - -1 - - - -CreditCard - -CreditCard - - - -card_id - -<u>card_id</u> - - - -CreditCard->card_id - - - - -cc_u_id - -u_id - - - -CreditCard->cc_u_id - - - - -cardholder_name - -cardholder_name - - - -CreditCard->cardholder_name - - - - -expiration_date - -expiration_date - - - -CreditCard->expiration_date - - - - -encrypted_card_number - -encrypted_card_number - - - -CreditCard->encrypted_card_number - - - - -last4 - -last4 - - - -CreditCard->last4 - - - - -keyver - -keyver - - - -CreditCard->keyver - - - - -PublisherPhone - -PublisherPhone - - - -phone - -phone - - - -PublisherPhone->phone - - - - -BookAuthor - - -BookAuthor - - - -ba_isbn - -isbn - - - -BookAuthor->ba_isbn - - - - -ba_author_id - -author_id - - - -BookAuthor->ba_author_id - - - - -CartItem - - -CartItem - - - -ci_quantity - -quantity - - - -CartItem->ci_quantity - - - - -CustomerOrderItem - - -CustomerOrderItem - - - -coi_quantity - -quantity - - - -CustomerOrderItem->coi_quantity - - - - -coi_price - -price - - - -CustomerOrderItem->coi_price - - - - -Written_By->BookAuthor - - - - -Contains_Cart->CartItem - - - - -Contains_Order->CustomerOrderItem - - - - -Places_Order->CustomerOrder - - - - -Has_Cart->Cart - - - - -Has_CreditCard->CreditCard - - - - -Publisher_HasPhone->PublisherPhone - -N - - - diff --git a/docs/UI_Screen_Logic_Description.md b/docs/UI_Screen_Logic_Description.md index 241b3d7..124b5b9 100644 --- a/docs/UI_Screen_Logic_Description.md +++ b/docs/UI_Screen_Logic_Description.md @@ -648,7 +648,234 @@ - Verifies authentication before rendering - Shows loading message during verification -**Note:** The actual book management, publisher orders, and reports pages are referenced but not implemented in the current codebase. The dashboard serves as a navigation hub to these future admin features. +--- + +### 12. Manage Books Page (`/admin/books`) +**File:** [frontend/app/admin/books/page.tsx](../frontend/app/admin/books/page.tsx) + +**Purpose:** Admin interface for managing book inventory and details + +**Logic Flow:** +1. **Authentication & Authorization:** + - Verifies user is authenticated with Admin role + - Redirects non-admin users to login + +2. **Data Loading:** + - Fetches all books with complete details + - Loads all authors for author assignment + - Removes duplicate ISBNs from display + +3. **Search & Filter:** + - Real-time search across book title, ISBN, author names, and category + - Client-side filtering for immediate results + - Search bar with magnifying glass icon + +4. **Book Management Operations:** + - **Edit Book:** + - Inline editing with expandable form + - Editable fields: title, publisher ID, publication year, price, category, stock, threshold + - Author assignment with multi-select checkboxes + - Real-time validation + - Save/Cancel buttons with loading states + + - **Delete Book:** + - Trash icon with confirmation dialog + - Shows loading state during deletion + - Removes book from inventory entirely + +5. **Book Display:** + - Tabular layout with book cover thumbnails + - Shows ISBN, title, authors, price, stock, category + - Stock level indicators (low stock warnings) + - Publication year and threshold information + +6. **Navigation:** + - "Add New Book" button linking to creation form + - Back to Admin Dashboard + - Breadcrumb navigation + +7. **Error & Success Handling:** + - Success messages for updates and deletions + - Error handling for failed operations + - Form validation feedback + +--- + +### 13. Add New Book Page (`/admin/books/new`) +**File:** [frontend/app/admin/books/new/page.tsx](../frontend/app/admin/books/new/page.tsx) + +**Purpose:** Create new books in the inventory system + +**Logic Flow:** +1. **Authentication Check:** + - Verifies Admin role access + +2. **Form Fields:** + - ISBN (required, unique identifier) + - Title (required) + - Publisher selection from available publishers + - Publication year + - Price (required, numeric validation) + - Category + - Initial stock quantity + - Stock threshold for reordering + - Author assignment (multi-select) + +3. **Author Management:** + - Load all available authors + - Multi-select checkbox interface + - Create new authors if needed + +4. **Validation:** + - Required field validation + - ISBN format and uniqueness checking + - Price and stock numeric validation + - Author selection requirement + +5. **Creation Process:** + - Validates all form data + - Submits book creation request + - Handles success/error responses + - Redirects to book management on success + +--- + +### 14. Publisher Orders Page (`/admin/publisher-orders`) +**File:** [frontend/app/admin/publisher-orders/page.tsx](../frontend/app/admin/publisher-orders/page.tsx) + +**Purpose:** Manage replenishment orders for low-stock books + +**Logic Flow:** +1. **Order Data Loading:** + - Fetches all pending replenishment orders + - Loads book details for each order + - Combines order data with book information + +2. **Order Display:** + - Shows order ID, book details, current stock + - Displays order quantity and urgency indicators + - Book cover thumbnails with order information + +3. **Order Management:** + - **Confirm Order:** + - Button to confirm replenishment order + - Updates stock levels automatically + - Marks order as completed + - Shows confirmation loading state + + - **Order Status:** + - Pending orders highlighted + - Completed orders marked with checkmarks + - Time-based priority indicators + +4. **Book Information:** + - Shows current stock vs. threshold levels + - Displays book title, ISBN, category + - Indicates critical stock levels + +5. **Automated System:** + - Orders automatically generated when stock falls below threshold + - Admin confirmation required before processing + - Integrates with inventory management system + +--- + +### 15. Reports Page (`/admin/reports`) +**File:** [frontend/app/admin/reports/page.tsx](../frontend/app/admin/reports/page.tsx) + +**Purpose:** Business intelligence and analytics dashboard + +**Logic Flow:** +1. **Report Categories:** + - **Sales Reports:** + - Previous month total sales + - Sales by specific date + - Date range analysis + + - **Top Customers:** + - Customers by total purchase amount + - Customer ranking and statistics + - Purchase history insights + + - **Best-Selling Books:** + - Books by total sales volume + - Revenue by book category + - Inventory performance metrics + +2. **Sales Analytics:** + - **Previous Month Sales:** + - Automatic calculation of previous month revenue + - Comparison metrics and trends + + - **Date-Specific Sales:** + - Date picker for specific day analysis + - Real-time sales data fetching + - Daily performance metrics + +3. **Customer Analytics:** + - Top customer identification by spend + - Customer loyalty metrics + - Purchase pattern analysis + +4. **Book Performance:** + - Best-selling books ranking + - Revenue contribution by title + - Stock turnover analysis + +5. **Visual Representation:** + - Card-based layout for each report type + - Statistical indicators and trends + - Color-coded performance metrics + +6. **Interactive Features:** + - Date selection for custom reports + - Real-time data updates + - Export capabilities for business analysis + +--- + +### 16. Customer Management Page (`/admin/customers`) +**File:** [frontend/app/admin/customers/page.tsx](../frontend/app/admin/customers/page.tsx) + +**Purpose:** Admin interface for managing customer accounts + +**Logic Flow:** +1. **Customer Data Loading:** + - Fetches all registered customers + - Displays customer profiles and account information + +2. **Customer Information Display:** + - Customer details (name, email, registration date) + - Order history summary + - Account status and activity + +3. **Customer Management:** + - View detailed customer profiles + - Monitor customer activity and purchases + - Account management capabilities + +--- + +### 17. Admin Registration Page (`/admin/register-admin`) +**File:** [frontend/app/admin/register-admin/page.tsx](../frontend/app/admin/register-admin/page.tsx) + +**Purpose:** Create new administrator accounts + +**Logic Flow:** +1. **Registration Form:** + - Admin-specific account creation + - Required fields for admin users + - Role assignment and permissions + +2. **Validation:** + - Admin credential validation + - Security requirements for admin accounts + - Authorization checks + +3. **Account Creation:** + - Creates new admin user accounts + - Assigns appropriate admin permissions + - Integrates with authentication system --- @@ -725,11 +952,15 @@ - Type-safe API calls with TypeScript - Error handling and response parsing - Endpoints for: - - Books (getAll, getByIsbn) + - Books (getAll, getByIsbn, create, update, delete) - Cart (getCart, addItem, removeItem, checkout) - Orders (getAll) - Credit Cards (getAll, addCard, deleteCard) - - Users (profile management) + - Users (profile management, getAllCustomers) + - Authors (getAll, create, update) + - Publishers (getAll) + - Reports (totalSales, topCustomers, topBooks) + - Replenishment Orders (getAll, confirm) --- @@ -756,6 +987,8 @@ graph TD AddBook[Add New Book /admin/books/new] PublisherOrders[Publisher Orders /admin/publisher-orders] Reports[Reports /admin/reports] + Customers[Customer Management /admin/customers] + RegisterAdmin[Register Admin /admin/register-admin] %% Authentication Flow Signup --> Login @@ -776,6 +1009,9 @@ graph TD AdminHome --> AddBook AdminHome --> PublisherOrders AdminHome --> Reports + AdminHome --> Customers + AdminHome --> RegisterAdmin + ManageBooks --> AddBook %% Styling classDef authClass fill:#e1f5ff,stroke:#01579b,stroke-width:2px,color:#01579b @@ -784,7 +1020,7 @@ graph TD class Login,Signup authClass class UserHome,Books,BookDetails,SearchResults,Cart,Checkout,Orders,Profile customerClass - class AdminHome,ManageBooks,AddBook,PublisherOrders,Reports adminClass + class AdminHome,ManageBooks,AddBook,PublisherOrders,Reports,Customers,RegisterAdmin adminClass ``` --- @@ -838,4 +1074,12 @@ This Order Processing System implements a comprehensive e-commerce interface wit - **Comprehensive error handling** for better user experience - **Type safety** with TypeScript throughout -The customer journey flows naturally from browsing to purchasing, while admin screens provide centralized access to management operations. All screens maintain consistent authentication checks, loading states, and error handling patterns. +The customer journey flows naturally from browsing to purchasing, while admin screens provide comprehensive management capabilities including: + +- **Complete Book Management:** CRUD operations for books with real-time stock tracking +- **Inventory Control:** Automated replenishment orders with admin confirmation workflow +- **Business Analytics:** Comprehensive reports for sales, customers, and book performance +- **Customer Oversight:** Customer account management and activity monitoring +- **Admin User Management:** Secure admin account creation and role management + +All screens maintain consistent authentication checks, loading states, and error handling patterns with role-based access control ensuring proper security boundaries. diff --git a/docs/chen_er_diagram.svg b/docs/chen_er_diagram.svg new file mode 100644 index 0000000..ff8b5a5 --- /dev/null +++ b/docs/chen_er_diagram.svg @@ -0,0 +1,704 @@ + + + + + + +ER + + + +Publisher + +Publisher + + + +Publisher_publisher_id + + +publisher_id + + + + +Publisher--Publisher_publisher_id + + + + +Publisher_publisher_name + +publisher_name + + + +Publisher--Publisher_publisher_name + + + + +Publisher_address + +address + + + +Publisher--Publisher_address + + + + +Publisher_phone + + +phone + + + +Publisher--Publisher_phone + + + + +Author + +Author + + + +Author_author_id + + +author_id + + + + +Author--Author_author_id + + + + +Author_author_name + +author_name + + + +Author--Author_author_name + + + + +Book + +Book + + + +Book_isbn + + +isbn + + + + +Book--Book_isbn + + + + +Book_title + +title + + + +Book--Book_title + + + + +Book_pub_year + +pub_year + + + +Book--Book_pub_year + + + + +Book_price + +price + + + +Book--Book_price + + + + +Book_category + +category + + + +Book--Book_category + + + + +Book_stock + +stock + + + +Book--Book_stock + + + + +Book_threshold + +threshold + + + +Book--Book_threshold + + + + +User + +User + + + +User_u_id + + +u_id + + + + +User--User_u_id + + + + +User_username + +username + + + +User--User_username + + + + +User_password + +password + + + +User--User_password + + + + +User_last_name + +last_name + + + +User--User_last_name + + + + +User_first_name + +first_name + + + +User--User_first_name + + + + +User_email + +email + + + +User--User_email + + + + +User_phone + +phone + + + +User--User_phone + + + + +User_address + +address + + + +User--User_address + + + + +User_role + +role + + + +User--User_role + + + + +ReplenishmentOrder + +ReplenishmentOrder + + + +ReplenishmentOrder_order_id + + +order_id + + + + +ReplenishmentOrder--ReplenishmentOrder_order_id + + + + +ReplenishmentOrder_order_date + +order_date + + + +ReplenishmentOrder--ReplenishmentOrder_order_date + + + + +ReplenishmentOrder_quantity + +quantity + + + +ReplenishmentOrder--ReplenishmentOrder_quantity + + + + +ReplenishmentOrder_status + +status + + + +ReplenishmentOrder--ReplenishmentOrder_status + + + + +CustomerOrder + +CustomerOrder + + + +CustomerOrder_order_id + + +order_id + + + + +CustomerOrder--CustomerOrder_order_id + + + + +CustomerOrder_order_date + +order_date + + + +CustomerOrder--CustomerOrder_order_date + + + + +CustomerOrder_total_price + +total_price + + + +CustomerOrder--CustomerOrder_total_price + + + + +Cart + +Cart + + + +Cart_cart_id + + +cart_id + + + + +Cart--Cart_cart_id + + + + +CreditCard + +CreditCard + + + +CreditCard_card_id + + +card_id + + + + +CreditCard--CreditCard_card_id + + + + +CreditCard_u_id + +u_id + + + +CreditCard--CreditCard_u_id + + + + +CreditCard_cardholder_name + +cardholder_name + + + +CreditCard--CreditCard_cardholder_name + + + + +CreditCard_expiration_date + +expiration_date + + + +CreditCard--CreditCard_expiration_date + + + + +CreditCard_encrypted_card_number + +encrypted_card_number + + + +CreditCard--CreditCard_encrypted_card_number + + + + +CreditCard_last4 + +last4 + + + +CreditCard--CreditCard_last4 + + + + +CreditCard_keyver + +keyver + + + +CreditCard--CreditCard_keyver + + + + +CartItem + + +CartItem + + + +CartItem_quantity + +quantity + + + +CartItem--CartItem_quantity + + + + +CustomerOrderItem + + +CustomerOrderItem + + + +CustomerOrderItem_quantity + +quantity + + + +CustomerOrderItem--CustomerOrderItem_quantity + + + + +CustomerOrderItem_price + +price + + + +CustomerOrderItem--CustomerOrderItem_price + + + + +Published_By + +Published_By + + + +Published_By--Publisher + +1 + + + +Published_By--Book + +N + + + +Written_By + +Written_By + + + +Written_By--Author + +N + + + +Written_By--Book + +N + + + +Places + +Places + + + +Places--User + +1 + + + +Places--CustomerOrder + +N + + + +Replenishes + +Replenishes + + + +Replenishes--Book + +1 + + + +Replenishes--ReplenishmentOrder + +N + + + +Contains_Cart + + +Contains_Cart + + + +Contains_Cart--Book + +N + + + +Contains_Cart--Cart + +1 + + + +Contains_Cart--CartItem + +1 + + + +Contains_Cart--CartItem + +1 + + + +Contains_Order + + +Contains_Order + + + +Contains_Order--Book + +N + + + +Contains_Order--CustomerOrder + +1 + + + +Contains_Order--CustomerOrderItem + +1 + + + +Contains_Order--CustomerOrderItem + +1 + + + +Has_Cart + + +Has_Cart + + + +Has_Cart--User + +1 + + + +Has_Cart--Cart + +1 + + + +Has_CreditCard + + +Has_CreditCard + + + +Has_CreditCard--User + +N + + + +Has_CreditCard--CreditCard + +1 + + + From a839bd399a2b1f46000c769a4de9380314c9b1a2 Mon Sep 17 00:00:00 2001 From: Ahmad Wael Date: Sun, 28 Dec 2025 16:19:50 +0200 Subject: [PATCH 2/3] fix(backend): Enhance logging and user creation flow Improved GlobalRequestLoggingMiddleware to include correlation IDs, user info extraction, request/response timing, and detailed error logging for Postgres and unhandled exceptions. Refactored IUserRepository and UserRepository to set the generated user ID on creation instead of returning it, and updated UserService to validate that the user ID is generated after creation. --- backend/Middleware/GlobalLogger.cs | 114 +++++++++++++++---- backend/Repositories/User/IUserRepository.cs | 2 +- backend/Repositories/User/UserRepository.cs | 4 +- backend/Services/User/UserService.cs | 4 + 4 files changed, 100 insertions(+), 24 deletions(-) diff --git a/backend/Middleware/GlobalLogger.cs b/backend/Middleware/GlobalLogger.cs index 38c93b3..3eee924 100644 --- a/backend/Middleware/GlobalLogger.cs +++ b/backend/Middleware/GlobalLogger.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; -using System.IO; +using Npgsql; +using System.Diagnostics; +using System.Security.Claims; using System.Text; namespace backend.Middleware; @@ -10,7 +12,9 @@ public class GlobalRequestLoggingMiddleware private readonly RequestDelegate _next; private readonly ILogger _logger; - public GlobalRequestLoggingMiddleware(RequestDelegate next, ILogger logger) + public GlobalRequestLoggingMiddleware( + RequestDelegate next, + ILogger logger) { _next = next; _logger = logger; @@ -18,29 +22,97 @@ public GlobalRequestLoggingMiddleware(RequestDelegate next, ILogger c.Type == "uid")?.Value; + var username = context.User?.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier)?.Value + ?? context.User?.Claims?.FirstOrDefault(c => c.Type == "sub")?.Value; - try + using (_logger.BeginScope(new Dictionary { - await _next(context); // call next middleware/controller - _logger.LogInformation("Response Status: {StatusCode}", context.Response.StatusCode); - } - catch (Exception ex) + ["CorrelationId"] = correlationId + })) { - _logger.LogError(ex, "Unhandled exception for request {Method} {Path}", context.Request.Method, context.Request.Path); - throw; + // ---- REQUEST LOGGING ---- + _logger.LogInformation( + "Incoming Request | {Method} {Path} | Auth={Auth} | UserId={UserId} | Username={Username}", + context.Request.Method, + context.Request.Path, + isAuthenticated, + userId ?? "anonymous", + username ?? "anonymous" + ); + + // Log request body (SAFE endpoints only) + if ((context.Request.Method == HttpMethods.Post || + context.Request.Method == HttpMethods.Put) && + !context.Request.Path.StartsWithSegments("/auth")) + { + context.Request.EnableBuffering(); + + using var reader = new StreamReader( + context.Request.Body, + Encoding.UTF8, + leaveOpen: true); + + var body = await reader.ReadToEndAsync(); + context.Request.Body.Position = 0; + + if (!string.IsNullOrWhiteSpace(body)) + { + _logger.LogInformation("Request Body: {Body}", body); + } + } + + try + { + await _next(context); // Continue pipeline + + stopwatch.Stop(); + + // ---- RESPONSE LOGGING ---- + _logger.LogInformation( + "Response | {Method} {Path} | Status={StatusCode} | Time={ElapsedMs}ms", + context.Request.Method, + context.Request.Path, + context.Response.StatusCode, + stopwatch.ElapsedMilliseconds + ); + } + catch (PostgresException pgEx) + { + stopwatch.Stop(); + + // ---- DATABASE ERROR LOGGING ---- + _logger.LogError( + pgEx, + "Postgres Error | SqlState={SqlState} | Constraint={Constraint} | Message={Message} | Time={ElapsedMs}ms", + pgEx.SqlState, + pgEx.ConstraintName, + pgEx.MessageText, + stopwatch.ElapsedMilliseconds + ); + + throw; + } + catch (Exception ex) + { + stopwatch.Stop(); + + // ---- UNHANDLED ERROR LOGGING ---- + _logger.LogError( + ex, + "Unhandled Exception | {Method} {Path} | Time={ElapsedMs}ms", + context.Request.Method, + context.Request.Path, + stopwatch.ElapsedMilliseconds + ); + + throw; + } } } } diff --git a/backend/Repositories/User/IUserRepository.cs b/backend/Repositories/User/IUserRepository.cs index 2a67877..f4f18ca 100644 --- a/backend/Repositories/User/IUserRepository.cs +++ b/backend/Repositories/User/IUserRepository.cs @@ -8,7 +8,7 @@ public interface IUserRepository Task GetByEmailAsync(string email); Task GetByLoginAsync(string login); Task> GetAllAsync(); - Task CreateAsync(User user); + Task CreateAsync(User user); } diff --git a/backend/Repositories/User/UserRepository.cs b/backend/Repositories/User/UserRepository.cs index 1f35642..b84e8de 100644 --- a/backend/Repositories/User/UserRepository.cs +++ b/backend/Repositories/User/UserRepository.cs @@ -40,7 +40,7 @@ public async Task> GetAllAsync() } - public async Task CreateAsync(User user) + public async Task CreateAsync(User user) { const string sql = @" INSERT INTO ""user"" (username, password, last_name, first_name, email, phone, address, role) @@ -48,7 +48,7 @@ public async Task CreateAsync(User user) RETURNING u_id; "; - return await _db.ExecuteScalarAsync(sql, user); + user.UId = await _db.ExecuteScalarAsync(sql, user); } } diff --git a/backend/Services/User/UserService.cs b/backend/Services/User/UserService.cs index 828a135..5d57fb1 100644 --- a/backend/Services/User/UserService.cs +++ b/backend/Services/User/UserService.cs @@ -47,6 +47,8 @@ public async Task RegisterAsync(UserRegisterDto dto) user.Password = _passwordHasher.HashPassword(user, dto.Password); await _repo.CreateAsync(user); + if (user.UId == Guid.Empty) + throw new Exception("User ID was not generated"); var accessToken = GenerateJwt(user, false); // short-lived access token var refreshToken = GenerateJwt(user, true); // long-lived refresh token @@ -84,6 +86,8 @@ public async Task RegisterAdminAsync(UserRegisterDto dto) user.Password = _passwordHasher.HashPassword(user, dto.Password); await _repo.CreateAsync(user); + if (user.UId == Guid.Empty) + throw new Exception("User ID was not generated"); var accessToken = GenerateJwt(user, false); // short-lived access token var refreshToken = GenerateJwt(user, true); // long-lived refresh token From 73f73104283d7de879a543cc7e5cc1a8230dd254 Mon Sep 17 00:00:00 2001 From: Ahmad Wael Date: Sun, 28 Dec 2025 16:49:40 +0200 Subject: [PATCH 3/3] feat(profile update): Add user profile update endpoint and frontend integration Implemented a new PUT /users/me endpoint in the backend to allow users to update their profile information. Added UserUpdateDto, repository, and service methods to support profile updates with email uniqueness validation. Updated the frontend profile page to use the new endpoint for profile updates. --- backend/Controllers/UsersController.cs | 13 +++++++ backend/DTOs/User/UserDTO.cs | 8 +++++ backend/Repositories/User/IUserRepository.cs | 3 ++ backend/Repositories/User/UserRepository.cs | 31 ++++++++++++++++ backend/Services/User/IUserService.cs | 1 + backend/Services/User/UserService.cs | 15 ++++++++ frontend/app/user/profile/page.tsx | 37 +++++++++----------- 7 files changed, 87 insertions(+), 21 deletions(-) diff --git a/backend/Controllers/UsersController.cs b/backend/Controllers/UsersController.cs index 8b6156b..5dae2e2 100644 --- a/backend/Controllers/UsersController.cs +++ b/backend/Controllers/UsersController.cs @@ -36,4 +36,17 @@ public async Task GetByUsername(string username) } return Ok(user); } + + [HttpPut("me")] + public async Task UpdateProfile([FromBody] UserUpdateDto dto) + { + var uidClaim = User.FindFirst("uid")?.Value; + if (uidClaim == null) + return Unauthorized(); + + var userId = Guid.Parse(uidClaim); + + await _service.UpdateProfileAsync(userId, dto); + return NoContent(); + } } diff --git a/backend/DTOs/User/UserDTO.cs b/backend/DTOs/User/UserDTO.cs index f412fb0..d7189e4 100644 --- a/backend/DTOs/User/UserDTO.cs +++ b/backend/DTOs/User/UserDTO.cs @@ -29,3 +29,11 @@ public class UserResponseDto public string Role { get; set; } } +public class UserUpdateDto +{ + public string FirstName { get; set; } = null!; + public string LastName { get; set; } = null!; + public string Email { get; set; } = null!; + public string? Phone { get; set; } + public string? Address { get; set; } +} diff --git a/backend/Repositories/User/IUserRepository.cs b/backend/Repositories/User/IUserRepository.cs index f4f18ca..3af3bf6 100644 --- a/backend/Repositories/User/IUserRepository.cs +++ b/backend/Repositories/User/IUserRepository.cs @@ -1,4 +1,5 @@ using backend.Models; +using backend.DTOs; namespace backend.Repositories; @@ -9,6 +10,8 @@ public interface IUserRepository Task GetByLoginAsync(string login); Task> GetAllAsync(); Task CreateAsync(User user); + Task GetByIdAsync(Guid id); + Task UpdateProfileAsync(Guid id, UserUpdateDto dto); } diff --git a/backend/Repositories/User/UserRepository.cs b/backend/Repositories/User/UserRepository.cs index b84e8de..11d3eaf 100644 --- a/backend/Repositories/User/UserRepository.cs +++ b/backend/Repositories/User/UserRepository.cs @@ -1,6 +1,7 @@ using System.Data; using Dapper; using backend.Models; +using backend.DTOs; namespace backend.Repositories; @@ -51,4 +52,34 @@ public async Task CreateAsync(User user) user.UId = await _db.ExecuteScalarAsync(sql, user); } + public async Task GetByIdAsync(Guid id) + { + const string sql = @"SELECT * FROM ""user"" WHERE u_id = @Id;"; + return await _db.QuerySingleOrDefaultAsync(sql, new { Id = id }); + } + + public async Task UpdateProfileAsync(Guid id, UserUpdateDto dto) + { + const string sql = @" + UPDATE ""user"" + SET + first_name = @FirstName, + last_name = @LastName, + email = @Email, + phone = @Phone, + address = @Address + WHERE u_id = @Id; + "; + + await _db.ExecuteAsync(sql, new + { + Id = id, + dto.FirstName, + dto.LastName, + dto.Email, + dto.Phone, + dto.Address + }); + } + } diff --git a/backend/Services/User/IUserService.cs b/backend/Services/User/IUserService.cs index eb98eca..bb30edc 100644 --- a/backend/Services/User/IUserService.cs +++ b/backend/Services/User/IUserService.cs @@ -10,6 +10,7 @@ public interface IUserService Task GetUserByUsernameAsync(string username); Task> GetAllUsersAsync(); Task RefreshAsync(string refreshToken); + Task UpdateProfileAsync(Guid userId, UserUpdateDto dto); } diff --git a/backend/Services/User/UserService.cs b/backend/Services/User/UserService.cs index 5d57fb1..ac21e90 100644 --- a/backend/Services/User/UserService.cs +++ b/backend/Services/User/UserService.cs @@ -237,4 +237,19 @@ public async Task> GetAllUsersAsync() }; } + public async Task UpdateProfileAsync(Guid userId, UserUpdateDto dto) + { + var existingUser = await _repo.GetByIdAsync(userId); + if (existingUser == null) + throw new Exception("User not found"); + + // Optional: prevent email duplication + var emailOwner = await _repo.GetByEmailAsync(dto.Email); + if (emailOwner != null && emailOwner.UId != userId) + throw new Exception("Email already in use"); + + await _repo.UpdateProfileAsync(userId, dto); + } + + } diff --git a/frontend/app/user/profile/page.tsx b/frontend/app/user/profile/page.tsx index 92daf1d..65dcc20 100644 --- a/frontend/app/user/profile/page.tsx +++ b/frontend/app/user/profile/page.tsx @@ -233,29 +233,24 @@ export default function ProfilePage() { try { const currentUser = getCurrentUser(); if (!currentUser) return; - - // Note: This assumes there's an update endpoint. If not, we'll need to create one. - // For now, we'll show a message that profile updates need backend support - setError("Profile update functionality requires backend API endpoint. Please contact support."); - // Uncomment when backend endpoint is available: - // const response = await apiRequest(`/users/${currentUser.username}`, { - // method: "PUT", - // body: JSON.stringify({ - // FirstName: formData.firstName, - // LastName: formData.lastName, - // Email: formData.email, - // Phone: formData.phone || null, - // Address: formData.address || null, - // }), - // }); + const response = await apiRequest(`/users/me`, { + method: "PUT", + body: JSON.stringify({ + FirstName: formData.firstName, + LastName: formData.lastName, + Email: formData.email, + Phone: formData.phone || null, + Address: formData.address || null, + }), + }); - // if (response.error) { - // setError(response.error); - // } else { - // setSuccess("Profile updated successfully!"); - // await loadProfile(); - // } + if (response.error) { + setError(response.error); + } else { + setSuccess("Profile updated successfully!"); + await loadProfile(); + } } catch (error: any) { setError("Failed to update profile: " + (error.message || "Unknown error")); } finally {