Skip to content

Commit 01446ee

Browse files
authored
Merge pull request #29 from fuzziecoder/codex/fix-issue-#26-by-migrating-to-db-osa172
[issue]order creation previously trusted client-provided pricing/details
2 parents 2cbec1d + 46ac80b commit 01446ee

3 files changed

Lines changed: 45 additions & 6 deletions

File tree

backend/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@ Server starts at `http://localhost:4000` by default.
1212

1313
## Database
1414

15+
### Issue #26: Move from in-memory store to persistent DB
16+
1517
- Uses `node:sqlite` with a local database file at `backend/data/brocode.sqlite`.
1618
- You can override the location with `BROCODE_DB_PATH=/custom/path.sqlite npm run backend`.
1719
- On first start, seed data is inserted for users, spots, catalog items, and a sample order.
20+
- New orders are validated against DB data (known `spotId`, `userId`, `productId`) and item pricing is always derived from catalog prices in the database.
1821

1922
## Available endpoints
2023

backend/db.js

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,13 @@ const fetchOrderItemsByOrderIds = (orderIds) => {
128128
return itemsByOrderId;
129129
};
130130

131+
const getCatalogItemByIdStatement = db.prepare(
132+
'SELECT id, category, name, price FROM catalog_items WHERE id = ?'
133+
);
134+
135+
const userExistsStatement = db.prepare('SELECT 1 AS found FROM users WHERE id = ? LIMIT 1');
136+
const spotExistsStatement = db.prepare('SELECT 1 AS found FROM spots WHERE id = ? LIMIT 1');
137+
131138
export const database = {
132139
getUserByCredentials(username, password) {
133140
const user = db
@@ -162,6 +169,14 @@ export const database = {
162169
return rows;
163170
},
164171

172+
userExists(userId) {
173+
return Boolean(userExistsStatement.get(userId));
174+
},
175+
176+
spotExists(spotId) {
177+
return Boolean(spotExistsStatement.get(spotId));
178+
},
179+
165180
getSpots() {
166181
return db
167182
.prepare('SELECT id, location, date, host_user_id AS hostUserId FROM spots ORDER BY date DESC')
@@ -200,14 +215,21 @@ export const database = {
200215
createOrder({ spotId, userId, items }) {
201216
const parsedItems = items.map((item) => {
202217
const quantity = Number(item.quantity || 0);
203-
const unitPrice = Number(item.unitPrice || 0);
218+
if (!item.productId || !Number.isInteger(quantity) || quantity <= 0) {
219+
throw new Error('Each order item must include productId and a positive integer quantity');
220+
}
221+
222+
const catalogItem = getCatalogItemByIdStatement.get(item.productId);
223+
if (!catalogItem) {
224+
throw new Error(`Unknown productId: ${item.productId}`);
225+
}
204226

205227
return {
206-
productId: item.productId,
207-
name: item.name,
228+
productId: catalogItem.id,
229+
name: catalogItem.name,
208230
quantity,
209-
unitPrice,
210-
total: quantity * unitPrice,
231+
unitPrice: catalogItem.price,
232+
total: quantity * catalogItem.price,
211233
};
212234
});
213235

backend/server.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,25 @@ const server = createServer(async (req, res) => {
119119
return;
120120
}
121121

122+
if (!database.userExists(userId)) {
123+
sendJson(res, 404, { error: `Unknown userId: ${userId}` });
124+
return;
125+
}
126+
127+
if (!database.spotExists(spotId)) {
128+
sendJson(res, 404, { error: `Unknown spotId: ${spotId}` });
129+
return;
130+
}
131+
122132
const newOrder = database.createOrder({ spotId, userId, items });
123133
sendJson(res, 201, newOrder);
124134
return;
125135
} catch (error) {
126-
sendJson(res, 400, { error: error.message });
136+
const isValidationError =
137+
error.message.includes('Unknown productId') ||
138+
error.message.includes('Each order item must include productId');
139+
140+
sendJson(res, isValidationError ? 400 : 500, { error: error.message });
127141
return;
128142
}
129143
}

0 commit comments

Comments
 (0)