From 43b3a5c0ee1749f95768c28ff1e193b2f3c6ef9f Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 29 Sep 2025 20:32:38 +0000
Subject: [PATCH 1/4] Initial plan
From 70a355bdd15a348a6232a989873892e8ee6f1834 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 29 Sep 2025 20:41:19 +0000
Subject: [PATCH 2/4] Add responsive HTML UI with full CRUD functionality
Co-authored-by: liamchampton <42477259+liamchampton@users.noreply.github.com>
---
src/app.py | 6 +-
src/templates/index.html | 456 +++++++++++++++++++++++++++++++++++++++
src/test_app.py | 26 ++-
3 files changed, 486 insertions(+), 2 deletions(-)
create mode 100644 src/templates/index.html
diff --git a/src/app.py b/src/app.py
index c1224fe..3c83af6 100644
--- a/src/app.py
+++ b/src/app.py
@@ -1,4 +1,4 @@
-from flask import Flask, jsonify, request, abort
+from flask import Flask, jsonify, request, abort, render_template
from uuid import uuid4
app = Flask(__name__)
@@ -6,6 +6,10 @@
# In-memory product store
data = {}
+@app.route('/')
+def index():
+ return render_template('index.html')
+
@app.route('/products', methods=['GET'])
def get_products():
return jsonify(list(data.values())), 200
diff --git a/src/templates/index.html b/src/templates/index.html
new file mode 100644
index 0000000..cf8fbdc
--- /dev/null
+++ b/src/templates/index.html
@@ -0,0 +1,456 @@
+
+
+
+
+
+ Product Store Demo
+
+
+
+
+
+
+
+
+
+
+
+
Products
+
Loading products...
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test_app.py b/src/test_app.py
index 7f7ed38..12ed55a 100644
--- a/src/test_app.py
+++ b/src/test_app.py
@@ -1 +1,25 @@
-# Unit test file
\ No newline at end of file
+import pytest
+from app import app
+
+@pytest.fixture
+def client():
+ app.config['TESTING'] = True
+ with app.test_client() as client:
+ yield client
+
+def test_create_and_get_product(client):
+ # Create
+ rv = client.post('/products', json={'name': 'Test', 'description': 'Desc'})
+ assert rv.status_code == 201
+ product = rv.get_json()
+ # Get
+ rv = client.get(f"/products/{product['id']}")
+ assert rv.status_code == 200
+ assert rv.get_json()['name'] == 'Test'
+
+def test_index_route(client):
+ """Test that the main route serves the HTML UI"""
+ rv = client.get('/')
+ assert rv.status_code == 200
+ assert b'Product Store Demo' in rv.data
+ assert b'Add New Product' in rv.data
\ No newline at end of file
From e298fc0e7657b8b4519b9efdb10b181d091c46cb Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 29 Sep 2025 20:45:17 +0000
Subject: [PATCH 3/4] Fix XSS vulnerabilities by using event delegation and
data attributes
Co-authored-by: liamchampton <42477259+liamchampton@users.noreply.github.com>
---
src/templates/index.html | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/src/templates/index.html b/src/templates/index.html
index cf8fbdc..f9e1822 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -269,6 +269,19 @@ Products
this.cancelBtn.addEventListener('click', () => this.cancelEdit());
}
+ bindProductEvents() {
+ // Use event delegation for dynamically created buttons
+ this.productGrid.addEventListener('click', (e) => {
+ if (e.target.classList.contains('edit-btn')) {
+ const productId = e.target.getAttribute('data-id');
+ this.editProduct(productId);
+ } else if (e.target.classList.contains('delete-btn')) {
+ const productId = e.target.getAttribute('data-id');
+ this.deleteProduct(productId);
+ }
+ });
+ }
+
async loadProducts() {
try {
this.showLoading();
@@ -289,17 +302,18 @@ Products
this.productGrid.innerHTML = 'No products found. Add your first product above!
';
} else {
this.productGrid.innerHTML = this.products.map(product => `
-
+
${this.escapeHtml(product.name)}
${this.escapeHtml(product.description || 'No description')}
-
-
+
+
`).join('');
}
this.productGrid.classList.remove('hidden');
+ this.bindProductEvents();
}
async handleSubmit(e) {
From c96a4c2cab80ee1c9e3f81932a0dc915d45df980 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 1 Oct 2025 16:05:01 +0000
Subject: [PATCH 4/4] Add category dropdown field to products with 9 predefined
categories
Co-authored-by: liamchampton <42477259+liamchampton@users.noreply.github.com>
---
src/app.py | 4 ++--
src/templates/index.html | 35 ++++++++++++++++++++++++++++++++---
2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/src/app.py b/src/app.py
index 3c83af6..b686a93 100644
--- a/src/app.py
+++ b/src/app.py
@@ -27,7 +27,7 @@ def create_product():
if not body or 'name' not in body:
abort(400)
product_id = str(uuid4())
- product = {'id': product_id, 'name': body['name'], 'description': body.get('description', '')}
+ product = {'id': product_id, 'name': body['name'], 'description': body.get('description', ''), 'category': body.get('category', '')}
data[product_id] = product
return jsonify(product), 201
@@ -38,7 +38,7 @@ def update_product(product_id):
body = request.get_json()
if not body or 'name' not in body:
abort(400)
- data[product_id].update({'name': body['name'], 'description': body.get('description', '')})
+ data[product_id].update({'name': body['name'], 'description': body.get('description', ''), 'category': body.get('category', '')})
return jsonify(data[product_id]), 200
@app.route('/products/
', methods=['DELETE'])
diff --git a/src/templates/index.html b/src/templates/index.html
index f9e1822..3c7ee87 100644
--- a/src/templates/index.html
+++ b/src/templates/index.html
@@ -55,7 +55,7 @@
font-weight: 500;
}
- input, textarea {
+ input, textarea, select {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
@@ -139,6 +139,16 @@
min-height: 40px;
}
+ .product-category {
+ display: inline-block;
+ background: #3498db;
+ color: white;
+ padding: 0.25rem 0.75rem;
+ border-radius: 4px;
+ font-size: 0.875rem;
+ margin-bottom: 0.75rem;
+ }
+
.product-actions {
display: flex;
gap: 0.5rem;
@@ -227,6 +237,21 @@
+
+
+
+