Skip to content

Commit

Permalink
Enhancements to Checkout Process and Database Schema
Browse files Browse the repository at this point in the history
  • Loading branch information
sentry-autofix[bot] authored Jan 8, 2025
1 parent d27844a commit 456ea1a
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 33 deletions.
29 changes: 18 additions & 11 deletions flask/src/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,20 +142,27 @@ def get_inventory(cart):
try:
with sentry_sdk.start_span(op="get_inventory", description="db.connect"):
connection = db.connect()
with sentry_sdk.start_span(op="get_inventory", description="db.query") as span:
inventory = connection.execute(
"SELECT * FROM inventory WHERE productId in %s" % (productIds)

with sentry_sdk.start_span(op="get_inventory.validate_products", description="db.query") as span:
# First verify if products exist
products = connection.execute(
"SELECT id FROM products WHERE id in %s" % (productIds)
).fetchall()
span.set_data("inventory",inventory)

if len(products) != len(productIds):
raise Exception("One or more products not found in catalog")

# Then get inventory with proper join to ensure we get all products
with sentry_sdk.start_span(op="get_inventory.get_inventory", description="db.query") as span:
inventory = connection.execute(
"""SELECT i.*, COALESCE(i.count, 0) as count
FROM products p
LEFT JOIN inventory i ON p.id = i.productId
WHERE p.id in %s""" % (productIds)
).fetchall()
span.set_data("inventory", inventory)
except BrokenPipeError as err:
raise DatabaseConnectionError('get_inventory')
except Exception as err:
err_string = str(err)
if UNPACK_FROM_ERROR in err_string:
raise DatabaseConnectionError('get_inventory')
else:
raise(err)

return inventory


Expand Down
64 changes: 43 additions & 21 deletions flask/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,31 +165,53 @@ def checkout():
form = order["form"]
validate_inventory = True if "validate_inventory" not in order else order["validate_inventory"] == "true"

inventory = []
try:
with sentry_sdk.start_span(op="/checkout.get_inventory", description="function"):
with sentry_sdk.metrics.timing(key="checkout.get_inventory.execution_time"):
inventory = get_inventory(cart)
with sentry_sdk.start_transaction(name="checkout", op="checkout") as transaction:
inventory = []
try:
with sentry_sdk.start_span(op="/checkout.get_inventory", description="function"):
with sentry_sdk.metrics.timing(key="checkout.get_inventory.execution_time"):
inventory = get_inventory(cart)
except Exception as err:
sentry_sdk.metrics.incr(key="checkout.failed", tags={"reason": "inventory_fetch_failed"})
raise err

print("> /checkout inventory", inventory)
print("> validate_inventory", validate_inventory)

with sentry_sdk.start_span(op="process_order", description="function"):
quantities = cart['quantities']
inventory_map = {str(item['productId']): item for item in inventory}

# Validate all quantities against inventory
insufficient_items = []
for product_id, requested_qty in quantities.items():
requested_qty = int(requested_qty)
if product_id not in inventory_map:
insufficient_items.append({"id": product_id, "reason": "not_found"})
continue

available_qty = inventory_map[product_id]['count']
if validate_inventory and (available_qty < requested_qty):
insufficient_items.append({
"id": product_id,
"reason": "insufficient_stock",
"requested": requested_qty,
"available": available_qty
})

if insufficient_items:
sentry_sdk.metrics.incr(key="checkout.failed", tags={"reason": "insufficient_stock"})
return jsonify({
"error": "Inventory check failed",
"details": insufficient_items
}), 400

response = make_response("success")
return response
except Exception as err:
raise (err)

print("> /checkout inventory", inventory)
print("> validate_inventory", validate_inventory)

with sentry_sdk.start_span(op="process_order", description="function"):
quantities = cart['quantities']
for cartItem in quantities:
for inventoryItem in inventory:
print("> inventoryItem.count", inventoryItem['count'])
if (validate_inventory and (inventoryItem.count < quantities[cartItem] or quantities[cartItem] >= inventoryItem.count)):
sentry_sdk.metrics.incr(key="checkout.failed")
raise Exception("Not enough inventory for product")
if len(inventory) == 0 or len(quantities) == 0:
raise Exception("Not enough inventory for product")

response = make_response("success")
return response


@app.route('/success', methods=['GET'])
def success():
Expand Down
48 changes: 48 additions & 0 deletions flask/src/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
-- Ensure products table has proper constraints
CREATE TABLE IF NOT EXISTS products (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL CHECK (price >= 0),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- Ensure inventory table has proper constraints and is linked to products
CREATE TABLE IF NOT EXISTS inventory (
id SERIAL PRIMARY KEY,
productId INTEGER NOT NULL REFERENCES products(id),
count INTEGER NOT NULL DEFAULT 0 CHECK (count >= 0),
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_product UNIQUE (productId)
);

-- Create trigger to auto-create inventory records for new products
CREATE OR REPLACE FUNCTION create_inventory_for_product()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO inventory (productId, count)
VALUES (NEW.id, 0)
ON CONFLICT (productId) DO NOTHING;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Attach trigger to products table
DROP TRIGGER IF EXISTS ensure_inventory_exists ON products;
CREATE TRIGGER ensure_inventory_exists
AFTER INSERT ON products
FOR EACH ROW
EXECUTE FUNCTION create_inventory_for_product();

-- Create function to fix missing inventory records
CREATE OR REPLACE FUNCTION fix_missing_inventory()
RETURNS void AS $$
BEGIN
INSERT INTO inventory (productId, count)
SELECT id, 0
FROM products p
WHERE NOT EXISTS (
SELECT 1 FROM inventory i WHERE i.productId = p.id
);
END;
$$ LANGUAGE plpgsql;
42 changes: 41 additions & 1 deletion react/src/components/Checkout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getTag, itemsInCart } from '../utils/utils';
function Checkout({ backend, rageclick, checkout_success, cart }) {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
let initialFormValues;
let se = sessionStorage.getItem('se');
const seTdaPrefixRegex = /[^-]+-tda-[^-]+-/;
Expand Down Expand Up @@ -114,8 +115,37 @@ function Checkout({ backend, rageclick, checkout_success, cart }) {
setLoading(true);

try {
await checkout(cart);
const response = await fetch(backend + '/checkout?v2=true', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cart: cart,
form: form,
validate_inventory: checkout_success ? "false" : "true",
}),
});

if (!response.ok) {
const errorData = await response.json();

if (errorData.details) {
const errorMessage = errorData.details.map(item => {
if (item.reason === 'not_found') {
return `Product ${item.id} is no longer available`;
} else if (item.reason === 'insufficient_stock') {
return `Only ${item.available} units available for product ${item.id}, but ${item.requested} were requested`;
}
return `Issue with product ${item.id}`;
}).join('\n');

setError(errorMessage);
throw new Error(errorMessage);
} else {
throw new Error(errorData.error || 'Checkout failed');
}
}
} catch (error) {
setError(error.message || 'An unexpected error occurred');
Sentry.captureException(error);
hadError = true;
}
Expand All @@ -128,9 +158,19 @@ function Checkout({ backend, rageclick, checkout_success, cart }) {
}
})
}

const clearError = () => {
setError(null);
};

return (
<div className="checkout-container">
{error && (
<div className="error-message" role="alert" onClick={clearError}>
<p>{error}</p>
<button className="dismiss-error">Dismiss</button>
</div>
)}
{loading ? (
<Loader
type="ThreeDots"
Expand Down

0 comments on commit 456ea1a

Please sign in to comment.