Skip to content

Commit 8ef66f3

Browse files
authored
Merge pull request #38 from fuzziecoder/codex/fix-user-deletion-handling-in-backend
Add DELETE /api/users/:userId with full DB cleanup and cascade constraints
2 parents cfb3d8e + b76005c commit 8ef66f3

3 files changed

Lines changed: 144 additions & 1 deletion

File tree

backend/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Server starts at `http://localhost:4000` by default.
4444
- `GET /api/orders?spotId=...&userId=...`
4545
- `POST /api/orders`
4646
- `GET /api/bills/:spotId`
47+
- `DELETE /api/users/:userId` (removes the user and all related records)
4748

4849
## Example login payload
4950

backend/db.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,79 @@ const verifyPassword = (password, storedPassword) => {
3434
return timingSafeEqual(candidateHash, expectedHash);
3535
};
3636

37+
db.exec(`
38+
CREATE TABLE IF NOT EXISTS users (
39+
id TEXT PRIMARY KEY,
40+
username TEXT NOT NULL UNIQUE,
41+
password TEXT NOT NULL,
42+
name TEXT NOT NULL,
43+
role TEXT NOT NULL
44+
);
45+
46+
CREATE TABLE IF NOT EXISTS spots (
47+
id TEXT PRIMARY KEY,
48+
location TEXT NOT NULL,
49+
date TEXT NOT NULL,
50+
host_user_id TEXT NOT NULL,
51+
FOREIGN KEY (host_user_id) REFERENCES users(id) ON DELETE CASCADE
52+
);
53+
54+
CREATE TABLE IF NOT EXISTS catalog_items (
55+
id TEXT PRIMARY KEY,
56+
category TEXT NOT NULL,
57+
name TEXT NOT NULL,
58+
price REAL NOT NULL
59+
);
60+
61+
CREATE TABLE IF NOT EXISTS orders (
62+
id TEXT PRIMARY KEY,
63+
spot_id TEXT NOT NULL,
64+
user_id TEXT NOT NULL,
65+
total_amount REAL NOT NULL,
66+
created_at TEXT NOT NULL,
67+
FOREIGN KEY (spot_id) REFERENCES spots(id),
68+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
69+
);
70+
71+
CREATE TABLE IF NOT EXISTS order_items (
72+
id INTEGER PRIMARY KEY AUTOINCREMENT,
73+
order_id TEXT NOT NULL,
74+
product_id TEXT NOT NULL,
75+
name TEXT NOT NULL,
76+
quantity INTEGER NOT NULL,
77+
unit_price REAL NOT NULL,
78+
total REAL NOT NULL,
79+
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
80+
);
81+
`);
82+
83+
const hasUsers = db.prepare('SELECT COUNT(*) AS count FROM users').get().count > 0;
84+
85+
if (!hasUsers) {
86+
const insertUser = db.prepare('INSERT INTO users (id, username, password, name, role) VALUES (?, ?, ?, ?, ?)');
87+
insertUser.run('u-1', 'brocode', hashPassword('changeme'), 'Ram', 'admin');
88+
insertUser.run('u-2', 'dhanush', hashPassword('changeme'), 'Dhanush', 'user');
89+
90+
const insertSpot = db.prepare('INSERT INTO spots (id, location, date, host_user_id) VALUES (?, ?, ?, ?)');
91+
insertSpot.run('spot-2025-07-26', 'Attibele Toll Plaza', '2025-07-26T10:00:00.000Z', 'u-1');
92+
93+
const insertCatalogItem = db.prepare('INSERT INTO catalog_items (id, category, name, price) VALUES (?, ?, ?, ?)');
94+
insertCatalogItem.run('d-1', 'drinks', 'Brocode Beer', 180);
95+
insertCatalogItem.run('d-2', 'drinks', 'Kingfisher Beer', 170);
96+
insertCatalogItem.run('f-1', 'food', 'Beef Biriyani', 220);
97+
insertCatalogItem.run('f-2', 'food', 'Parotta', 30);
98+
insertCatalogItem.run('c-1', 'cigarettes', 'Marlboro', 25);
99+
insertCatalogItem.run('c-2', 'cigarettes', 'Classic', 20);
100+
101+
db.prepare(
102+
'INSERT INTO orders (id, spot_id, user_id, total_amount, created_at) VALUES (?, ?, ?, ?, ?)'
103+
).run('ord-1', 'spot-2025-07-26', 'u-2', 360, '2025-07-26T10:30:00.000Z');
104+
105+
db.prepare(
106+
`INSERT INTO order_items (order_id, product_id, name, quantity, unit_price, total)
107+
VALUES (?, ?, ?, ?, ?, ?)`
108+
).run('ord-1', 'd-1', 'Brocode Beer', 2, 180, 360);
109+
}
37110
const seedData = () => ({
38111
users: [
39112
{ id: 'u-1', username: 'brocode', password: hashPassword('changeme'), name: 'Ram', role: 'admin' },
@@ -123,6 +196,42 @@ const mapOrder = (order, items) => ({
123196
})),
124197
});
125198

199+
const fetchOrderItemsByOrderIds = (orderIds) => {
200+
if (orderIds.length === 0) return new Map();
201+
202+
const placeholders = orderIds.map(() => '?').join(',');
203+
const rows = db
204+
.prepare(
205+
`SELECT order_id, product_id, name, quantity, unit_price, total
206+
FROM order_items
207+
WHERE order_id IN (${placeholders})
208+
ORDER BY id ASC`
209+
)
210+
.all(...orderIds);
211+
212+
const itemsByOrderId = new Map();
213+
rows.forEach((row) => {
214+
if (!itemsByOrderId.has(row.order_id)) {
215+
itemsByOrderId.set(row.order_id, []);
216+
}
217+
itemsByOrderId.get(row.order_id).push(row);
218+
});
219+
220+
return itemsByOrderId;
221+
};
222+
223+
const getCatalogItemByIdStatement = db.prepare(
224+
'SELECT id, category, name, price FROM catalog_items WHERE id = ?'
225+
);
226+
227+
const userExistsStatement = db.prepare('SELECT 1 AS found FROM users WHERE id = ? LIMIT 1');
228+
const spotExistsStatement = db.prepare('SELECT 1 AS found FROM spots WHERE id = ? LIMIT 1');
229+
const getUserByUsernameStatement = db.prepare('SELECT id, username, password, name, role FROM users WHERE username = ?');
230+
const updateUserPasswordStatement = db.prepare('UPDATE users SET password = ? WHERE id = ?');
231+
const deleteUserByIdStatement = db.prepare('DELETE FROM users WHERE id = ?');
232+
const deleteOrdersByUserIdStatement = db.prepare('DELETE FROM orders WHERE user_id = ?');
233+
const deleteSpotsByHostUserIdStatement = db.prepare('DELETE FROM spots WHERE host_user_id = ?');
234+
126235
export const database = {
127236
getUserByCredentials(username, password) {
128237
const user = state.users.find((entry) => entry.username === username);
@@ -251,6 +360,21 @@ export const database = {
251360
};
252361
},
253362

363+
deleteUserCompletely(userId) {
364+
db.exec('BEGIN');
365+
366+
try {
367+
deleteOrdersByUserIdStatement.run(userId);
368+
deleteSpotsByHostUserIdStatement.run(userId);
369+
deleteUserByIdStatement.run(userId);
370+
371+
db.exec('COMMIT');
372+
} catch (error) {
373+
db.exec('ROLLBACK');
374+
throw error;
375+
}
376+
},
377+
254378
getBillBySpotId(spotId) {
255379
const summaryRows = state.orders.filter((order) => order.spot_id === spotId);
256380

backend/server.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const sendJson = (res, statusCode, body) => {
6363
'Content-Type': 'application/json',
6464
'Access-Control-Allow-Origin': '*',
6565
'Access-Control-Allow-Headers': 'Content-Type',
66-
'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
66+
'Access-Control-Allow-Methods': 'GET,POST,DELETE,OPTIONS',
6767
});
6868
res.end(JSON.stringify(body));
6969
};
@@ -240,6 +240,24 @@ const server = createServer(async (req, res) => {
240240
return;
241241
}
242242

243+
if (method === 'DELETE' && path.startsWith('/api/users/')) {
244+
const userId = path.replace('/api/users/', '');
245+
246+
if (!userId) {
247+
sendJson(res, 400, { error: 'userId is required' });
248+
return;
249+
}
250+
251+
if (!database.userExists(userId)) {
252+
sendJson(res, 404, { error: `Unknown userId: ${userId}` });
253+
return;
254+
}
255+
256+
database.deleteUserCompletely(userId);
257+
sendJson(res, 200, { success: true, deletedUserId: userId });
258+
return;
259+
}
260+
243261
sendJson(res, 404, { error: 'Route not found' });
244262
});
245263

0 commit comments

Comments
 (0)