Build A Secure Shopping Cart API

by Alex Johnson 33 views

Welcome, developers! Today, we're diving deep into the exciting world of e-commerce development by focusing on a critical component: the Shopping Cart API. This isn't just about adding items to a list; it's about creating a robust, secure, and efficient system that forms the backbone of any online store. Our goal here is to implement comprehensive shopping cart functionality and its associated API endpoints, ensuring a seamless experience for your users. We'll be integrating crucial elements like user authentication and the product catalog to build a complete and secure e-commerce cart. Get ready to roll up your sleeves and build something truly impactful!

๐Ÿ“‹ Quick Summary: Crafting Your E-commerce Cart

At its core, this task is all about building a shopping cart API. Think of it as the digital equivalent of a physical shopping cart in a supermarket, but with a lot more sophisticated logic behind the scenes. We need to create the functionality that allows users to add items, view their cart's contents, remove items, and even clear their entire cart. But it doesn't stop there. This isn't a standalone feature; it's deeply intertwined with other parts of our e-commerce system. We'll be leveraging user authentication to ensure that each user's cart is private and secure, and we'll be connecting to the product catalog to fetch real-time product information, like names and prices, and to validate that the items users are trying to add actually exist. The API endpoints we design will be the interface through which our frontend applications (like a website or a mobile app) will interact with this cart functionality. This means they need to be well-defined, easy to understand, and, most importantly, secure. We're aiming for a complete e-commerce cart experience, and that requires careful planning and implementation of these core API features.

๐Ÿ“Š Metadata: Project Health Check

Before we dive into the nitty-gritty of implementation, let's take a moment to look at the project's status and surrounding details. Understanding these elements helps us prioritize and manage our workload effectively. The priority for this task is set to medium, meaning it's important but perhaps not as time-sensitive as a critical bug fix or a core feature launch. This gives us a reasonable amount of time to implement it thoroughly and correctly. We also see that this task has dependencies on Task 3 (User Authentication) and Task 4 (Product Catalog). This is a crucial piece of information; we absolutely cannot complete the shopping cart API without having these foundational pieces in place. The status is currently Pending, indicating that we're gearing up to start but haven't kicked off the actual development work yet. The workflow run identifier, play-project-workflow-template-j8h4p, is a unique tag that helps us track this task through our automated development pipeline. Finally, the started timestamp, 2025-11-16 01:05:54 UTC, gives us a precise moment when this task officially entered the development cycle. By keeping these metadata points in mind, we ensure that our development process remains organized and that we're building upon a solid foundation, respecting the project's overall structure and dependencies.


๐Ÿ“– Full Task Details: Bringing the Shopping Cart to Life

Overview: The Heart of E-commerce Transactions

This task is all about implementing the shopping cart functionality and its accompanying API endpoints. Imagine a user browsing your online store. They find a product they love and click 'Add to Cart'. What happens next? That's where our work comes in! We need to build the system that captures this action, stores it, and makes it accessible to the user. This involves creating the core logic for managing a shopping cart, which includes all the essential operations: adding items, removing items, updating quantities, viewing the cart's contents, and clearing it entirely. Crucially, this functionality must be exposed through a set of well-defined API endpoints. These endpoints act as the communication bridge between your frontend (the user interface) and your backend logic. To make this truly functional and secure, we'll be integrating two vital components: user authentication and the product catalog. User authentication, likely handled via JWT (JSON Web Tokens), ensures that each user's cart is private and isolated โ€“ User A should never see or modify User B's cart. The product catalog integration is equally important; it allows us to fetch dynamic product details like names and prices and to validate that the products being added to the cart are legitimate and available. The ultimate goal is to provide a complete, secure, and user-friendly e-commerce cart experience, ensuring every transaction is handled with precision and care.

Context: Building on Solid Foundations

This task, Task 5: Shopping Cart API, is designed as a Level 1 task. What does that mean in our project structure? It signifies that this task is a direct successor and relies heavily on the completion of previous foundational tasks. Specifically, it depends on Task 3: User Authentication and Task 4: Product Catalog. Without a robust system for authenticating users and a well-defined catalog of products, our shopping cart would lack essential security and data integrity. Task 3 provides us with the means to identify and authenticate users, typically using JWTs. This is paramount because it allows us to create user-specific carts and ensures that cart data remains private and isolated. Task 4, on the other hand, furnishes us with the product data we need โ€“ think product IDs, names, prices, and importantly, inventory levels. By integrating these two pre-existing components, we're able to build a shopping cart that is not only functional but also secure and data-accurate. We're combining the power of JWT-based user isolation with the necessity of product validation to construct a shopping cart that is reliable and trustworthy. This layered approach ensures that each component builds upon the last, creating a cohesive and powerful e-commerce platform.

Objectives: The Blueprint for Success

To ensure we deliver a high-quality Shopping Cart API, we've outlined a clear set of objectives. These objectives serve as our roadmap, guiding the development process and ensuring all critical aspects are covered. Firstly, we need to Create a CartService with thread-safe in-memory storage. This means designing a service that can handle multiple requests concurrently without data corruption, using mechanisms like Arc<Mutex> to manage shared mutable state safely. Secondly, we must Implement core cart CRUD operations (add, remove, clear, get). These are the fundamental actions a user performs with their cart, and they need to be robust and efficient. Thirdly, we will Build API routes with JWT authentication. This involves defining the API endpoints (like /cart, /cart/add, etc.) and ensuring that each request is authenticated using the JWTs provided by Task 3. Fourthly, we need to Integrate with the Product Catalog for validation. This means our cart service will need to communicate with the product catalog service to verify product existence and retrieve necessary details. Finally, and critically, we must Handle inventory checking before adding items. Before a product is added to the cart, we need to verify that sufficient stock is available. This prevents overselling and maintains data integrity. Meeting these objectives will result in a functional, secure, and reliable shopping cart system.

Dependencies: Building Blocks of the Cart

Our Shopping Cart API task doesn't exist in a vacuum. It's strategically placed within a larger project, and its successful implementation hinges on the completion and stability of two key preceding tasks: Task 3: User Authentication and Task 4: Product Catalog. Let's break down why these are so crucial.

  • Task 3: User Authentication (JWT Validation): This is the gatekeeper for our cart. User authentication ensures that we know who is interacting with the system. By using JWTs, we establish a secure way to verify user identities across requests. For the shopping cart, this is vital for two main reasons: privacy and personalization. Each user's cart must be strictly isolated; User A should never see or modify User B's cart. JWTs allow us to reliably identify the user making a request, ensuring they only access and modify their own cart data. Without this, our cart system would be fundamentally insecure.

  • Task 4: Product Catalog (Product Retrieval and Validation): This task provides the 'what' for our cart. The product catalog is the source of truth for all items available in the store. When a user wants to add an item to their cart, we need to know if that product exists, what its current price is, and, crucially, its inventory count. The product catalog service enables us to perform these checks. It ensures that we don't add non-existent products to the cart and, more importantly, that we don't allow users to purchase items that are out of stock. This integration is key to maintaining data accuracy and preventing overselling, which can lead to customer dissatisfaction and operational issues.

In essence, Task 3 gives us the identity of the user, and Task 4 gives us the details of the product. Together, they form the essential prerequisites for building a functional and secure shopping cart.

Implementation Plan: Step-by-Step Guide

Let's map out the steps required to build our Shopping Cart API. This plan breaks down the process into manageable stages, ensuring a systematic approach to development.

Step 1: Create Cart Service Module

Our first move is to establish the basic structure for our cart functionality. This involves creating a new Rust module specifically for the cart. We'll create a file named src/cart/mod.rs. This file will act as the entry point for our cart-related code, exporting the necessary components, particularly the CartService, and any related data structures like Cart and CartItem, making them accessible to other parts of our application.

// src/cart/mod.rs

pub mod service;

pub use self::service::{CartService, Cart, CartItem};

Step 2: Implement Cart Data Structures

Next, we define the data models that will represent our shopping cart and its items. These structures are fundamental to how we store and manipulate cart data. We'll define these within src/cart/service.rs. The CartItem struct will hold details about a single product in the cart, such as its ID, quantity, name, and unit price. The Cart struct will represent the entire cart, containing a unique ID, the ID of the associated user, and a list of CartItems. We'll use serde for serialization/deserialization and rust_decimal::Decimal for accurate price representation.

// src/cart/service.rs

use serde::{Serialize, Deserialize};
use rust_decimal::Decimal;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CartItem {
    pub product_id: i32,
    pub quantity: i32,
    pub product_name: String,
    pub unit_price: Decimal,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cart {
    pub id: i32,
    pub user_id: i32,
    pub items: Vec<CartItem>,
}

Step 3: Implement CartService

This is the core logic hub. In src/cart/service.rs, we'll implement the CartService struct. This service will manage the in-memory storage for all user carts. To ensure thread safety, especially in a concurrent web server environment, we'll use Arc<Mutex<HashMap<i32, Cart>>> to store the carts, keyed by a unique cart ID. We'll also need a mechanism to generate unique cart IDs, perhaps using another Arc<Mutex<i32>> for next_id. The implementation will include methods for new() to initialize the service, and placeholders for the essential CRUD operations: get_or_create_cart, add_item, remove_item, get_cart, and clear_cart. These methods will contain the detailed logic for managing cart data, interacting with the shared state, and performing necessary validations.

// src/cart/service.rs (continued)

use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use crate::catalog::models::Product; // Assuming Product model is accessible here

pub struct CartService {
    carts: Arc<Mutex<HashMap<i32, Cart>>>, // Shared, mutable, thread-safe storage for carts
    next_id: Arc<Mutex<i32>>,           // Counter for generating unique cart IDs
}

impl CartService {
    pub fn new() -> Self {
        CartService {
            carts: Arc::new(Mutex::new(HashMap::new())),
            next_id: Arc::new(Mutex::new(1)), // Start IDs from 1
        }
    }

    // Placeholder for getting or creating a cart for a given user ID
    pub fn get_or_create_cart(&self, user_id: i32) -> Cart {
        // Implementation details to follow...
    }

    // Placeholder for adding an item to the cart
    pub fn add_item(&self, user_id: i32, product: &Product, quantity: i32) -> Cart {
        // Implementation details to follow...
    }

    // Placeholder for removing an item from the cart
    pub fn remove_item(&self, user_id: i32, product_id: i32) -> Option<Cart> {
        // Implementation details to follow...
    }

    // Placeholder for retrieving a user's cart
    pub fn get_cart(&self, user_id: i32) -> Option<Cart> {
        // Implementation details to follow...
    }

    // Placeholder for clearing all items from a user's cart
    pub fn clear_cart(&self, user_id: i32) -> Option<Cart> {
        // Implementation details to follow...
    }
}

Step 4: Create Cart API Routes

With the service logic in place, we need to expose it via an API. We'll create a new file, src/api/cart_routes.rs, to define our API endpoints for the cart. This file will contain handler functions for different HTTP methods (GET, POST, DELETE) and routes (e.g., /, /add, /remove/{product_id}, /clear). These handlers will receive requests, interact with the CartService, and return appropriate HTTP responses. Crucially, these handlers will need to extract and validate JWTs from the request headers to ensure authentication, as mandated by Task 3.

// src/api/cart_routes.rs

use actix_web::{web, HttpResponse, Responder, HttpRequest};
use serde::Deserialize;

// Define request body structure for adding items
#[derive(Deserialize)]
pub struct AddItemRequest {
    pub product_id: i32,
    pub quantity: i32,
}

// Function to configure the routes for the cart API
pub fn configure_cart_routes(cfg: &mut web::ServiceConfig) {
    cfg.route("", web::get().to(get_cart)) // GET /api/cart
       .route("/add", web::post().to(add_item)) // POST /api/cart/add
       .route("/remove/{product_id}", web::delete().to(remove_item)) // DELETE /api/cart/remove/{id}
       .route("/clear", web::post().to(clear_cart)); // POST /api/cart/clear
}

// Handler functions will be defined here. They will need to:
// 1. Extract JWT from Authorization header.
// 2. Validate the JWT.
// 3. Extract user ID from validated JWT claims.
// 4. Call appropriate CartService methods.
// 5. Interact with ProductService if needed (for validation and pricing).
// 6. Return appropriate HTTP responses (Ok, NotFound, BadRequest, Unauthorized, etc.).

// Example placeholder for handler function signatures:
// async fn get_cart(cart_service: web::Data<CartService>, req: HttpRequest) -> impl Responder { ... }
// async fn add_item(cart_service: web::Data<CartService>, product_service: web::Data<ProductService>, req: HttpRequest, item: web::Json<AddItemRequest>) -> impl Responder { ... }
// async fn remove_item(cart_service: web::Data<CartService>, req: HttpRequest, path: web::Path<i32>) -> impl Responder { ... }
// async fn clear_cart(cart_service: web::Data<CartService>, req: HttpRequest) -> impl Responder { ... }

Step 5: Integrate with Main App

Finally, we need to connect our new cart module and API routes into the main application. This involves updating src/main.rs to import the CartService and making it available to the application via web::Data. We'll also need to update src/api/mod.rs to include our new cart_routes module. This ensures that the cart endpoints are registered and accessible when the application starts.

// src/main.rs (snippet within App configuration)

mod cart;
mod api; // Assuming api module is already set up

use crate::cart::CartService;
use actix_web::App;

// ... inside your HttpServer setup ...

    // Create and share the CartService instance
    let cart_service = web::Data::new(CartService::new());

    App::new()
        .app_data(cart_service.clone()) // Make CartService available to handlers
        // ... other app_data and configure calls ...
        .configure(api::configure_routes) // Ensure api::configure_routes is set up to include cart routes
// ... rest of your App configuration ...
// src/api/mod.rs

pub mod routes;       // Assuming this handles general routes
pub mod cart_routes;  // Add our new cart routes module

// You might also have modules like user_routes, product_routes, etc.

Testing Strategy: Ensuring Quality and Security

A robust testing strategy is crucial for delivering a reliable Shopping Cart API. Our approach will encompass several layers of testing to ensure functionality, security, and integration.

  1. Unit tests for CartService operations: We'll write focused tests for the CartService itself. These tests will verify the core logic of creating carts, adding items (including handling existing items and new items), removing items, retrieving carts, and clearing carts. These tests will run in isolation, without involving the web server or external services, ensuring the fundamental logic is sound.
  2. Integration tests with JWT authentication: Since the cart API endpoints are protected by JWTs, our integration tests must simulate real user requests. This means generating valid JWTs (or using predefined test tokens) and including them in the Authorization header. We'll test scenarios like successfully adding an item with a valid token, attempting to add an item without a token, and using an expired or invalid token to ensure appropriate 401 Unauthorized responses.
  3. Test inventory validation: A key requirement is checking inventory before adding items. Our integration tests will specifically cover this. We'll simulate scenarios where a user tries to add more items than are available in stock and verify that the API returns a 400 Bad Request with a clear error message about insufficient inventory. We'll also test the successful addition of items when inventory is sufficient.
  4. Test cart isolation per user: It's paramount that one user cannot access another's cart. Our integration tests will involve simulating requests from two different users (each with their own valid JWT). We'll add items to User A's cart, then attempt to view or modify User B's cart using User B's token. We expect User B's cart to remain unaffected or empty (unless they've added items themselves), confirming that cart data is properly isolated.

By employing these testing methods, we can build confidence in the correctness, security, and reliability of our Shopping Cart API.

Success Criteria: Measuring Our Achievements

To determine if this task is successfully completed, we'll look for the fulfillment of the following criteria. These are the non-negotiable benchmarks that indicate a job well done.

  • [ ] Cart service thread-safe with Arc<Mutex>: The CartService must correctly utilize Arc<Mutex> to manage its internal HashMap of carts, ensuring safe concurrent access from multiple threads without data races or corruption. This is fundamental for a web service.
  • [ ] All cart operations work correctly: Every defined operation โ€“ adding items, removing items, getting the cart, clearing the cart, and creating a cart if it doesn't exist โ€“ must function as expected. This includes correct data manipulation and state updates.
  • [ ] JWT authentication required for all endpoints: Every API endpoint related to the shopping cart must enforce JWT authentication. Requests lacking a valid Authorization: Bearer <token> header or containing an invalid token should be rejected.
  • [ ] Inventory checked before adding items: The API must integrate with the product catalog to check inventory levels. If the requested quantity exceeds available stock, the operation to add the item must fail with an appropriate error response (e.g., 400 Bad Request).
  • [ ] Cart properly isolated per user: A critical security and functionality requirement. Each user must only be able to access and modify their own cart. Testing must confirm that User A cannot view or alter User B's cart data under any circumstances.

Meeting these success criteria will signify that we have successfully built a secure, functional, and reliable Shopping Cart API.

Files Created: The Building Blocks

As we progress through this implementation, the following files will be created or significantly modified:

  • src/cart/mod.rs: This file will serve as the module declaration for our cart functionality, exporting the CartService and related data structures.
  • src/cart/service.rs: This will contain the core implementation of the CartService, including the data structures (Cart, CartItem) and the logic for managing carts in memory.
  • src/api/cart_routes.rs: This file will define the API endpoints for the shopping cart, including the handler functions and route configurations.

These files form the essential components of our Shopping Cart API implementation.


โœ… Acceptance Criteria: The Final Checklist

This section provides a detailed checklist to ensure all requirements for the Shopping Cart API are met before considering the task complete. It covers core functionality, API endpoints, and validation steps.

Core Requirements: The Foundation

  • [ ] src/cart/mod.rs exports CartService: Verify that the CartService and any necessary related types (like Cart, CartItem) are publicly exported from the cart module.
  • [ ] src/cart/service.rs implements all cart operations: Ensure that the CartService struct has implemented methods for creating, retrieving, adding to, removing from, and clearing carts. This includes proper handling of user IDs and cart states.
  • [ ] src/api/cart_routes.rs implements all API endpoints: Confirm that all necessary routes (/, /add, /remove/{id}, /clear) are defined within this file and correctly mapped to handler functions.
  • [ ] All endpoints require JWT authentication: Every cart-related API endpoint must be protected. Attempts to access them without a valid Authorization: Bearer <token> header should result in an error.
  • [ ] Returns 401 for missing/invalid tokens: Specifically test that requests with no token, a malformed token, or an invalid signature/expired token receive a 401 Unauthorized HTTP response.
  • [ ] Inventory validated before adding items: When a POST /api/cart/add request is made, the system must check the inventory_count from the Product Catalog against the requested quantity. If insufficient, a 400 Bad Request should be returned.
  • [ ] Cart isolated per user (user A can't access user B's cart): Rigorous testing is needed to ensure that authenticated requests from User A cannot see, modify, or affect the cart belonging to User B. Each user's cart must be strictly segregated.

API Endpoints: The Interface

These are the specific API routes that must be functional and correctly implemented:

  • [ ] GET /api/cart - Get user's cart: This endpoint should return the current contents of the authenticated user's shopping cart. If the cart is empty or doesn't exist yet, it should ideally return an empty cart structure rather than an error.
  • [ ] POST /api/cart/add - Add item (with inventory check): This endpoint accepts a JSON payload containing product_id and quantity. It must perform the inventory check and add the item to the user's cart, updating the quantity if the item already exists. Successful additions should return the updated cart.
  • [ ] DELETE /api/cart/remove/{id} - Remove item: This endpoint takes a product_id from the URL path and removes the corresponding item from the user's cart. The response should include the updated cart.
  • [ ] POST /api/cart/clear - Clear cart: This endpoint removes all items from the authenticated user's cart, effectively emptying it. The response should show the now empty cart.

Testing: Proving It Works

To validate the implementation, the following curl commands (or equivalent API testing tools) should be used with a valid JWT placeholder:

# Replace with an actual valid JWT obtained from the authentication endpoint
TOKEN="valid_jwt_here"

# 1. Get the user's current cart
# Expected: Returns the cart JSON (possibly empty initially)
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/cart

# 2. Add an item to the cart (e.g., product ID 1, quantity 2)
# Expected: Returns the updated cart JSON. If inventory is insufficient, returns 400.
curl -X POST -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"product_id":1,"quantity":2}' \
  http://localhost:8080/api/cart/add

# 3. Remove an item from the cart (e.g., remove product ID 1)
# Expected: Returns the cart JSON with the item removed.
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/api/cart/remove/1

# 4. Clear the entire cart
# Expected: Returns an empty cart JSON.
curl -X POST -H "Authorization: Bearer $TOKEN" \
  http://localhost:8080/api/cart/clear

Validation: The Final Seal of Approval

  • [ ] cargo test passes: All unit and integration tests defined for the cart module and API should pass without errors.
  • [ ] Cart operations work with valid JWT: The curl examples above (and other relevant scenarios) should execute successfully when a valid JWT is provided.
  • [ ] Returns 401 without JWT: Any attempt to access the cart endpoints without an Authorization header or with an invalid token must result in a 401 Unauthorized status.
  • [ ] Returns 400 for insufficient inventory: When attempting to add more of a product than available, the POST /api/cart/add endpoint must return a 400 Bad Request status with a descriptive error message.
  • [ ] Returns 404 for invalid product_id: If an attempt is made to add or remove an item with a product_id that does not exist in the Product Catalog, the API should return a 404 Not Found status.

๐ŸŽฏ Implementation Notes (from tasks.json): Bringing Code to Life

Here we find the detailed instructions for implementing the Shopping Cart API, ensuring all functional and structural requirements are met. This section essentially translates the high-level objectives into concrete coding steps.

  1. Create src/cart/mod.rs: This file is the entry point for the cart module. Its primary role is to export the necessary components, making them accessible to the rest of the application. The provided snippet shows how to declare the service submodule and re-export CartService.

    // src/cart/mod.rs
    pub mod service;
    pub use self::service::CartService;
    
  2. Create src/cart/service.rs: This is where the heart of the shopping cart logic resides. It defines the data structures (CartItem, Cart) and the CartService implementation. The CartService uses Arc<Mutex<HashMap<i32, Cart>>> for thread-safe, in-memory storage of carts, keyed by cart ID. It also manages a counter (next_id) for generating unique IDs. The implementation includes methods for:

    • new(): Initializes the service with empty storage.
    • get_or_create_cart(user_id): Finds an existing cart for the user or creates a new one if none exists. This ensures every authenticated user has a cart.
    • add_item(user_id, product, quantity): Adds a specified quantity of a product to the user's cart. It handles cases where the product is already in the cart (incrementing quantity) or is a new addition. It crucially relies on the Product model for details like name and price.
    • remove_item(user_id, product_id): Removes a specific product entirely from the user's cart.
    • get_cart(user_id): Retrieves the current state of the user's cart.
    • clear_cart(user_id): Removes all items from the user's cart. The code also includes necessary imports for serde, rust_decimal, std::sync, std::collections, and models from crate::auth and crate::catalog.
    // src/cart/service.rs
    use crate::auth::models::User; // Note: User model import might not be directly needed in service logic if only user_id is used
    use crate::catalog::models::Product;
    use serde::{Serialize, Deserialize};
    use std::sync::{Arc, Mutex};
    use std::collections::HashMap;
    use rust_decimal::Decimal; // Ensure Decimal is imported
    
    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct CartItem {
        pub product_id: i32,
        pub quantity: i32,
        pub product_name: String,
        pub unit_price: Decimal,
    }
    
    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct Cart {
        pub id: i32,
        pub user_id: i32,
        pub items: Vec<CartItem>,
    }
    
    pub struct CartService {
        carts: Arc<Mutex<HashMap<i32, Cart>>>, // Stores carts by their unique ID
        next_id: Arc<Mutex<i32>>,           // Counter for generating new cart IDs
    }
    
    impl CartService {
        pub fn new() -> Self {
            CartService {
                carts: Arc::new(Mutex::new(HashMap::new())),
                next_id: Arc::new(Mutex::new(1)), // Start IDs from 1
            }
        }
        
        pub fn get_or_create_cart(&self, user_id: i32) -> Cart {
            let mut carts = self.carts.lock().unwrap(); // Lock the mutex to access carts
            
            // Iterate through all carts to find if one already exists for the user_id
            // Note: A more efficient approach for large numbers of users might involve a secondary map from user_id to cart_id.
            for cart in carts.values() { // Use .values() for read-only access initially
                if cart.user_id == user_id {
                    return cart.clone(); // Return a clone of the found cart
                }
            }
            
            // If no cart is found, create a new one
            let mut next_id = self.next_id.lock().unwrap(); // Lock the ID counter
            let new_cart = Cart {
                id: *next_id,      // Assign the next available ID
                user_id,           // Set the user ID
                items: Vec::new(), // Initialize with an empty list of items
            };
            
            *next_id += 1; // Increment the ID counter for the next cart
            carts.insert(new_cart.id, new_cart.clone()); // Insert the new cart into the map
            new_cart // Return the newly created cart
        }
        
        pub fn add_item(&self, user_id: i32, product: &Product, quantity: i32) -> Cart {
            let mut carts = self.carts.lock().unwrap(); // Lock carts for modification
            
            // Get or create the cart for the user. We need mutable access, so we find the cart_id first.
            // This requires a bit of careful handling as we need mutable access to the cart within the locked HashMap.
            let cart_id_to_modify = {
                // Temporary scope to hold the lock on carts
                let mut found_cart_id: Option<i32> = None;
                for cart in carts.values() {
                    if cart.user_id == user_id {
                        found_cart_id = Some(cart.id);
                        break;
                    }
                }
                // If not found, create it and get its ID
                found_cart_id.unwrap_or_else(|| {
                    let new_cart = self.get_or_create_cart(user_id);
                    new_cart.id // This is slightly inefficient as get_or_create_cart locks and unlocks internally
                })
            };
            
            // Now get mutable access to the specific cart
            let cart = carts.get_mut(&cart_id_to_modify).unwrap(); // Unwrap is safe here because we ensured it exists
            
            // Check if the product already exists in the cart
            if let Some(item) = cart.items.iter_mut().find(|i| i.product_id == product.id) {
                // If it exists, just increase the quantity
                item.quantity += quantity;
            } else {
                // If it's a new product, add it as a new CartItem
                cart.items.push(CartItem {
                    product_id: product.id,
                    quantity,
                    product_name: product.name.clone(),
                    unit_price: product.price,
                });
            }
            
            cart.clone() // Return a clone of the modified cart
        }
        
        pub fn remove_item(&self, user_id: i32, product_id: i32) -> Option<Cart> {
            let mut carts = self.carts.lock().unwrap(); // Lock for mutation
            
            // Find the cart associated with the user_id
            let mut cart_to_modify: Option<&mut Cart> = None;
            for cart in carts.values_mut() { // Use values_mut() to get mutable references
                if cart.user_id == user_id {
                    cart_to_modify = Some(cart);
                    break;
                }
            }
    
            if let Some(cart) = cart_to_modify {
                // Remove the item by retaining only items whose product_id does not match
                cart.items.retain(|item| item.product_id != product_id);
                Some(cart.clone()) // Return a clone of the updated cart
            } else {
                None // Cart not found for the user
            }
        }
        
        pub fn get_cart(&self, user_id: i32) -> Option<Cart> {
            let carts = self.carts.lock().unwrap(); // Lock for read access
            
            // Iterate through carts to find the one belonging to the user_id
            for cart in carts.values() {
                if cart.user_id == user_id {
                    return Some(cart.clone()); // Return a clone if found
                }
            }
            
            None // Return None if no cart is found for the user
        }
        
        pub fn clear_cart(&self, user_id: i32) -> Option<Cart> {
            let mut carts = self.carts.lock().unwrap(); // Lock for mutation
            
            // Find the cart associated with the user_id
            let mut cart_to_modify: Option<&mut Cart> = None;
            for cart in carts.values_mut() {
                if cart.user_id == user_id {
                    cart_to_modify = Some(cart);
                    break;
                }
            }
    
            if let Some(cart) = cart_to_modify {
                cart.items.clear(); // Clear all items from the cart
                Some(cart.clone()) // Return a clone of the now empty cart
            } else {
                None // Cart not found for the user
            }
        }
    }
    
  3. Create src/api/cart_routes.rs: This file defines the API endpoints for interacting with the shopping cart. It sets up routes for GET /cart, POST /cart/add, DELETE /cart/remove/{product_id}, and POST /cart/clear. Each handler function is designed to:

    • Extract the JWT from the Authorization header.
    • Validate the token using crate::auth::jwt::validate_token.
    • Parse the user ID (sub claim) from the JWT claims.
    • Interact with the CartService (passed via web::Data) and potentially ProductService (for item additions).
    • Perform necessary validations (like inventory checks).
    • Return appropriate HttpResponse objects (e.g., Ok().json(...), Unauthorized().finish(), BadRequest().json(...), NotFound().json(...)). The AddItemRequest struct is defined for deserializing the JSON payload of the add item request.
    // src/api/cart_routes.rs
    use actix_web::{web, HttpResponse, Responder, HttpRequest};
    use serde::Deserialize;
    use crate::cart::CartService;
    use crate::catalog::ProductService; // Assuming ProductService is needed for item addition
    use crate::auth::jwt::validate_token; // Assuming JWT validation utility
    
    #[derive(Deserialize)]
    pub struct AddItemRequest {
        pub product_id: i32,
        pub quantity: i32,
    }
    
    // Configures the routes within a /cart scope
    pub fn configure_cart_routes(cfg: &mut web::ServiceConfig) {
        cfg.service(
            web::scope("") // Apply routes under the /cart path, handled by the caller (main app config)
                .route("", web::get().to(get_cart)) // GET /api/cart
                .route("/add", web::post().to(add_item)) // POST /api/cart/add
                .route("/remove/{product_id}", web::delete().to(remove_item)) // DELETE /api/cart/remove/{id}
                .route("/clear", web::post().to(clear_cart)) // POST /api/cart/clear
        );
    }
    
    async fn get_cart(
        cart_service: web::Data<CartService>,
        req: web::HttpRequest,
    ) -> impl Responder {
        // Extract and validate JWT to get user_id
        if let Some(auth_header) = req.headers().get("Authorization") {
            if let Ok(auth_str) = auth_header.to_str() {
                if auth_str.starts_with("Bearer ") {
                    let token = &auth_str[7..]; // Slice off "Bearer "
                    if let Ok(claims) = validate_token(token) {
                        // Safely parse user_id from claims.sub
                        if let Ok(user_id) = claims.sub.parse::<i32>() {
                            // Attempt to get the cart, or create one if it doesn't exist
                            let cart = cart_service.get_or_create_cart(user_id);
                            return HttpResponse::Ok().json(cart);
                        }
                    }
                }
            }
        }
        // If authentication fails or user_id cannot be parsed
        HttpResponse::Unauthorized().finish()
    }
    
    async fn add_item(
        cart_service: web::Data<CartService>,
        product_service: web::Data<ProductService>, // Need access to ProductService
        req: web::HttpRequest,
        item: web::Json<AddItemRequest>,
    ) -> impl Responder {
        // Extract and validate JWT to get user_id
        if let Some(auth_header) = req.headers().get("Authorization") {
            if let Ok(auth_str) = auth_header.to_str() {
                if auth_str.starts_with("Bearer ") {
                    let token = &auth_str[7..];
                    if let Ok(claims) = validate_token(token) {
                        if let Ok(user_id) = claims.sub.parse::<i32>() {
                            
                            // Retrieve product details from ProductService
                            if let Some(product) = product_service.get_by_id(item.product_id) {
                                // Check inventory count BEFORE adding to cart
                                if product.inventory_count >= item.quantity {
                                    // Add item to cart using CartService
                                    let cart = cart_service.add_item(user_id, &product, item.quantity);
                                    return HttpResponse::Ok().json(cart);
                                } else {
                                    // Not enough inventory
                                    return HttpResponse::BadRequest().json(serde_json::json!({
                                        "error": "Insufficient inventory for product."
                                    }));
                                }
                            } else {
                                // Product not found
                                return HttpResponse::NotFound().json(serde_json::json!({
                                    "error": "Product not found."
                                }));
                            }
                        }
                    }
                }
            }
        }
        // If authentication or any step fails
        HttpResponse::Unauthorized().finish()
    }
    
    async fn remove_item(
        cart_service: web::Data<CartService>,
        req: web::HttpRequest,
        path: web::Path<i32>, // Path parameter for product_id
    ) -> impl Responder {
        let product_id = path.into_inner();
        
        // Extract and validate JWT to get user_id
        if let Some(auth_header) = req.headers().get("Authorization") {
            if let Ok(auth_str) = auth_header.to_str() {
                if auth_str.starts_with("Bearer ") {
                    let token = &auth_str[7..];
                    if let Ok(claims) = validate_token(token) {
                        if let Ok(user_id) = claims.sub.parse::<i32>() {
                            
                            // Attempt to remove the item
                            if let Some(cart) = cart_service.remove_item(user_id, product_id) {
                                return HttpResponse::Ok().json(cart);
                            } else {
                                // Item not found in cart, or cart doesn't exist for user
                                // Consider if this should be NotFound or BadRequest depending on desired UX
                                return HttpResponse::NotFound().json(serde_json::json!({
                                    "error": "Item not found in cart or cart does not exist."
                                }));
                            }
                        }
                    }
                }
            }
        }
        HttpResponse::Unauthorized().finish()
    }
    
    async fn clear_cart(
        cart_service: web::Data<CartService>,
        req: web::HttpRequest,
    ) -> impl Responder {
        // Extract and validate JWT to get user_id
        if let Some(auth_header) = req.headers().get("Authorization") {
            if let Ok(auth_str) = auth_header.to_str() {
                if auth_str.starts_with("Bearer ") {
                    let token = &auth_str[7..];
                    if let Ok(claims) = validate_token(token) {
                        if let Ok(user_id) = claims.sub.parse::<i32>() {
                            
                            // Attempt to clear the cart
                            if let Some(cart) = cart_service.clear_cart(user_id) {
                                return HttpResponse::Ok().json(cart);
                            } else {
                                // Cart not found for the user, maybe return empty cart or NotFound
                                // Returning an empty cart might be more user-friendly if the cart wasn't explicitly created yet.
                                // Let's assume get_or_create_cart was implicitly called or should be handled.
                                // For now, returning NotFound if clear fails and cart didn't exist.
                                return HttpResponse::NotFound().json(serde_json::json!({
                                    "error": "Cart not found for user."
                                }));
                            }
                        }
                    }
                }
            }
        }
        HttpResponse::Unauthorized().finish()
    }
    
  4. Update src/api/mod.rs: This file acts as a central hub for API routing configuration. It needs to be updated to include the newly created cart_routes module, making its configuration function discoverable by the main application setup.

    // src/api/mod.rs
    pub mod routes;       // Assuming this file contains the main configure_routes function
    pub mod cart_routes;  // Import our new cart routes module
    
    // You might have other route modules here as well, e.g., user_routes, product_routes
    
  5. Update src/api/routes.rs: The main configure_routes function, likely located in src/api/routes.rs, needs to be modified to incorporate the cart routes. This is done by calling configure_cart_routes within the appropriate scope (e.g., /api/cart).

    // src/api/routes.rs (example snippet of configure_routes)
    
    use actix_web::web;
    // Assuming other route configurations are here
    // use crate::api::user_routes::configure_user_routes;
    // use crate::api::product_routes::configure_product_routes;
    use crate::api::cart_routes::configure_cart_routes; // Import the cart routes configuration
    
    // Placeholder for health check handler if it exists
    // async fn health_check() -> impl Responder { HttpResponse::Ok().finish() }
    
    pub fn configure_routes(cfg: &mut web::ServiceConfig) {
        cfg.service(
            web::scope("/api") // Base scope for all API routes
                // .service(web::resource("/health").to(health_check)) // Example health check
                // .service(web::scope("/users").configure(configure_user_routes))
                // .service(web::scope("/products").configure(configure_product_routes))
                .service(web::scope("/cart").configure(configure_cart_routes)) // Mount the cart routes here
        );
    }
    

These implementation notes provide a clear, step-by-step guide for developing the Shopping Cart API, ensuring all components are correctly integrated and functional.

๐Ÿงช Test Strategy (from tasks.json): Ensuring Robustness

To guarantee the quality and reliability of the Shopping Cart API, we will employ a comprehensive testing strategy. This multi-faceted approach ensures that the API not only functions as expected but is also secure and integrates seamlessly with other system components.

  1. Verify that all required files are created: The first step is a sanity check to confirm that src/cart/mod.rs, src/cart/service.rs, and src/api/cart_routes.rs have indeed been created and are correctly placed within the project structure. This ensures the foundational elements of our API are in place.
  2. Compile the code to ensure no syntax errors: Before running any tests, we must ensure the code compiles successfully. Running cargo build or cargo check will catch any syntax errors, type mismatches, or basic compilation issues, preventing us from proceeding with faulty code.
  3. Write unit tests for the CartService: Focused unit tests will be developed for the CartService module. These tests will meticulously verify each method: get_or_create_cart, add_item, remove_item, get_cart, and clear_cart. We'll test edge cases like adding multiple items, removing non-existent items, and clearing an empty cart. These tests operate independently of the web server, providing fast feedback on the core logic.
  4. Test the cart API endpoints with mock requests including JWT authentication: Integration tests will simulate real-world API interactions. We'll use tools like reqwest or actix_web's testing utilities to send HTTP requests to our API endpoints. Crucially, these tests will include mock JWT tokens in the Authorization header to ensure that authentication is correctly enforced and that each user's cart is isolated.
  5. Verify integration with the Product Catalog module when adding items to the cart: Tests will specifically check the interaction between the Cart API and the Product Catalog. This involves verifying that when an item is added, the product_id is correctly looked up, its price and name are recorded, and, most importantly, that the inventory check logic works as expected (preventing additions when stock is insufficient).
  6. Test authentication requirements for all cart operations: We will systematically test each API endpoint to confirm that unauthorized access is prevented. This includes sending requests without any Authorization header, with malformed tokens, or with invalid/expired tokens, and ensuring that a 401 Unauthorized response is consistently returned.
  7. Verify proper error handling for invalid requests or unauthorized access: Beyond authentication, we'll test other error conditions. This includes sending invalid data in request bodies (e.g., incorrect JSON structure for add_item), using incorrect HTTP methods, or attempting operations on non-existent resources (like removing an item not in the cart). We expect appropriate error codes (e.g., 400 Bad Request, 404 Not Found) and informative error messages.

By adhering to this testing strategy, we aim to deliver a robust, secure, and fully functional Shopping Cart API.


๐Ÿ”— Task Master Integration: Keeping Track

This section details how this specific task, the Shopping Cart API, is integrated with our project management tool, TaskMaster. This ensures seamless tracking, automated updates, and clear visibility into the development pipeline.

This issue is automatically synchronized with TaskMaster. This means that as agents progress through their assigned steps, the status of this task in TaskMaster will be updated in real-time.

  • Task File: .taskmaster/tasks/tasks.json - This path indicates where the detailed configuration for this task is stored within the project's repository. It likely contains metadata, requirements, and dependencies.
  • Task Details: .taskmaster/docs/task-5/ - This directory probably holds more in-depth documentation, specifications, or historical information related to Task 5.
  • Service: cto-parallel-test - This identifies the specific microservice or component this task is associated with. In this case, it's part of the cto-parallel-test service.
  • Workflow: play-project-workflow-template-j8h4p - This is the unique identifier for the automated workflow that this task follows. This workflow dictates the sequence of agent actions and reviews.

Agent Pipeline: The Journey Through Development

Our development process is orchestrated by a series of specialized agents, each responsible for a specific stage. The pipeline for this Shopping Cart API task looks like this:

  1. Rex - Implementation: The initial coding and development phase is handled by Rex. This is where the actual logic for the CartService and API routes is written.
  2. Cleo - Code Quality Review: Once Rex completes the implementation, Cleo steps in to review the code for quality, adherence to standards, and potential improvements.
  3. Cipher - Security Analysis (if enabled): If security analysis is part of our workflow, Cipher will examine the code for any security vulnerabilities, particularly important for an API handling user data and transactions.
  4. Tess - QA Testing: Tess is responsible for executing the test strategy outlined previously. This includes running unit tests, integration tests, and verifying acceptance criteria.
  5. Atlas - Integration & Merge: After passing QA, Atlas will handle the integration of the new code into the main codebase and manage the merge process.
  6. Bolt - Production Deployment: The final stage involves Bolt, who oversees the deployment of the tested and merged code into the production environment.

This structured pipeline ensures that each step is validated before moving to the next, leading to a higher quality and more reliable final product.


๐Ÿ“ก Live Status: Real-time Project Monitoring

This section provides information on how the progress of the Shopping Cart API task is monitored and communicated. It emphasizes real-time updates and channels for feedback.

This issue is monitored by Morgan PM. Status updates are posted automatically as agents progress through the workflow. This means that you can get up-to-the-minute information on where the task stands without needing manual check-ins.

  • Current Status: View the Project board for real-time updates - The primary source for tracking the current status is the project's board. This visual tool aggregates updates from the automated workflow and agent actions.
  • Feedback: Comment with @morgan to request scope changes or clarifications - If there are any questions, concerns, or proposed changes regarding the scope or implementation of this task, the designated channel for feedback is to comment directly on the task/issue and tag @morgan, the Project Manager.

This issue is managed by Morgan (Project Manager) for workflow play-project-workflow-template-j8h4p.

For more information on building robust e-commerce backends, you can explore resources on API design best practices and secure backend development. A great starting point for learning about Rust web frameworks is the official documentation for Actix Web.