-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathindex.js
360 lines (304 loc) · 10.4 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
const pg = require('pg');
// Setup
const pool = new pg.Pool();
const query = (text, params, callback) => {
console.log('Making query...');
// https://node-postgres.com/features/pooling#single-query
return pool.query(text, params, callback);
};
// Users
const emailExists = async (email_address) => {
const res = await query(
'SELECT email_address FROM users WHERE email_address=$1',
[email_address]
);
return res.rowCount > 0;
};
const getUserByEmail = async (email_address, auth_method) => {
const baseQuery = 'SELECT id, email_address, hashed_pw, auth_method FROM users';
const filter = ' WHERE email_address=$1 AND auth_method=$2';
const res = await query(baseQuery + filter, [email_address, auth_method]);
return res.rows[0];
};
const addLocalUser = async (email_address, hashed_pw) => {
const res = await query(
'INSERT INTO users(email_address, hashed_pw, auth_method) VALUES($1, $2, $3) RETURNING id, email_address',
[email_address, hashed_pw, 'local']
);
return res.rows[0];
};
const addGoogleUser = async (email_address) => {
const res = await query(
'INSERT INTO users(email_address, auth_method) VALUES($1, $2) RETURNING id, email_address',
[email_address, 'google']
);
return res.rows[0];
};
const updateUserPassword = async (id, hashed_pw) => {
await query(
'UPDATE users SET hashed_pw = $1 WHERE id=$2',
[hashed_pw, id]
);
return;
};
// Products
const getProducts = async (category_id=undefined, search_term=undefined) => {
const baseQuery = 'SELECT id, name, price, available_stock_count, short_description, long_description, avg_rating, rating_count FROM products';
let res;
if (category_id) {
res = await query(
baseQuery + ' JOIN product_categories ON products.id=product_categories.product_id WHERE product_categories.category_id=$1',
[category_id]
);
} else if (search_term) {
res = await query(
baseQuery + ' WHERE LOWER(name) LIKE $1',
['%' + search_term.toLowerCase() + '%']
);
} else {
res = await query(baseQuery);
}
return res.rows;
};
const getProductById = async (id) => {
const baseQuery = 'SELECT id, name, price, available_stock_count, short_description, long_description, avg_rating, rating_count FROM products';
const res = await query(baseQuery + ' WHERE id=$1', [id]);
return res.rows[0];
};
// Categories
const getCategories = async () => {
res = await query('SELECT id, name, description, url_slug FROM categories');
return res.rows;
};
// Cart
const getCartItems = async (user_id) => {
const select = 'SELECT product_id, name AS product_name, price AS product_price, quantity AS product_quantity FROM cart_products';
const join = 'JOIN products ON cart_products.product_id = products.id';
res = await query(`${select} ${join} WHERE user_id=$1`, [user_id]);
return res.rows;
};
const cartItemExists = async (user_id, product_id) => {
res = await query(
'SELECT user_id, product_id FROM cart_products WHERE user_id=$1 AND product_id=$2',
[user_id, product_id]
);
return res.rowCount > 0;
};
const addCartItem = async (user_id, product_id, product_quantity=1) => {
const insert = 'INSERT INTO cart_products(user_id, product_id, quantity) VALUES($1, $2, $3)';
const update = 'UPDATE products SET available_stock_count = (available_stock_count - $3) WHERE id=$2 RETURNING name, price';
const res = await query(
`WITH product AS (${insert}) ${update}`,
[user_id, product_id, product_quantity]
);
const product_name = res.rows[0].name;
const product_price = res.rows[0].price;
return { product_id, product_name, product_price, product_quantity };
};
const deleteCartItem = async (user_id, product_id) => {
const deleteRes = await query(
'DELETE FROM cart_products WHERE user_id=$1 AND product_id=$2 RETURNING quantity',
[user_id, product_id]
);
try {
// TypeError if cart item didn't exist (quantity undefined)
const quantity = deleteRes.rows[0].quantity;
await query(
'UPDATE products SET available_stock_count = (available_stock_count + $1) WHERE id=$2',
[quantity, product_id]
);
} catch(err) {
console.log(err);
}
return;
};
// Addresses
const getAddressById = async (id) => {
const res = await query('SELECT address, postcode FROM addresses WHERE id=$1', [id]);
return res.rows[0];
};
const getAddressId = async (address, postcode) => {
const res = await query(
'SELECT id FROM addresses WHERE address=$1 AND postcode=$2',
[address, postcode]
);
return res.rows.length === 1 ? res.rows[0].id : undefined;
};
const addAddress = async (address, postcode) => {
const res = await query(
'INSERT INTO addresses(address, postcode) VALUES($1, $2) RETURNING id',
[address, postcode]
);
return res.rows[0].id;
};
// Checkout
const createPendingOrder = async (user_id, address_id) => {
// Create a pending order for all current cart items ahead of successful payment
// Get cart items
const cartItems = await getCartItems(user_id);
// https://node-postgres.com/features/transactions
const client = await pool.connect();
try {
await client.query('BEGIN');
// Create pending order record
let total_cost = 0;
const order_status = 'payment pending';
const orderCreationRes = await client.query(
'INSERT INTO orders(user_id, address_id, status, total_cost) VALUES($1, $2, $3, $4) RETURNING id',
[user_id, address_id, order_status, total_cost]
);
const order_id = orderCreationRes.rows[0].id;
// Update order_products table and calculate total order cost
for await (const p of cartItems) {
const { product_id, product_quantity, product_price } = p;
// Add product to order_products table
await client.query(
'INSERT INTO order_products(order_id, product_id, product_quantity) VALUES($1, $2, $3)',
[order_id, product_id, product_quantity]
);
// Increment total order cost
total_cost += Number(product_price.substring(1)) * product_quantity;
};
// Update order total_cost and retrieve order details
const orderSummaryRes = await client.query(
'UPDATE orders SET total_cost=$1 WHERE id=$2 RETURNING order_placed_time, total_cost',
[total_cost, order_id]
);
const order_placed_time = orderSummaryRes.rows[0].order_placed_time;
total_cost = orderSummaryRes.rows[0].total_cost;
// Retrieve address details
const addressRes = await client.query(
'SELECT address, postcode FROM addresses WHERE id=$1',
[address_id]
);
const { address, postcode } = addressRes.rows[0];
// Commit updates and return order details
await client.query('COMMIT');
return {
order_id,
user_id: Number(user_id),
order_items: cartItems,
order_placed_time,
order_status,
total_cost,
address,
postcode
};
} catch(err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
};
const confirmPaidOrder = async (order_id) => {
// Confirm an order after successful payment
// Update order status and time; reduce product stock count; clear cart
// Update order status and order placed time
const status = 'processing order';
await query(
'UPDATE orders SET order_placed_time=(SELECT LOCALTIMESTAMP), status=$1 WHERE id=$2',
[status, order_id]
);
const order = await getOrderById(order_id);
// https://node-postgres.com/features/transactions
const client = await pool.connect();
try {
await client.query('BEGIN');
// For each order item, reduce stock count and delete cart item
for await (const product of order.order_items) {
const { product_id, product_quantity } = product;
// Reduce the product's stock count
await client.query(
'UPDATE products SET stock_count = (stock_count - $1) WHERE id=$2',
[product_quantity, product_id]
);
// Delete the product from the user's cart
await client.query(
'DELETE FROM cart_products WHERE user_id=$1 AND product_id=$2',
[order.user_id, product_id]
);
};
// Commit updates and return order details
await client.query('COMMIT');
} catch(err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
};
// Orders
const getOrdersSummary = async (user_id) => {
const select = 'SELECT id AS order_id, order_placed_time, status AS order_status, total_cost';
res = await query(
`${select} FROM orders WHERE user_id=$1 ORDER BY order_id DESC`,
[user_id]
);
return res.rows;
};
const getOrderUserId = async (id) => {
const res = await query('SELECT user_id FROM orders WHERE id=$1', [id]);
return res.rows[0] ? res.rows[0].user_id : undefined;
};
const getOrderStatus = async (id) => {
const res = await query('SELECT status FROM orders WHERE id=$1', [id]);
return res.rows[0] ? res.rows[0].status : undefined;
};
const getOrderById = async (id) => {
const orderSelect = 'SELECT orders.id, user_id, order_placed_time, status, total_cost, address, postcode';
const addressesJoin = 'JOIN addresses ON orders.address_id = addresses.id';
const orderRes = await query(
`${orderSelect} FROM orders ${addressesJoin} WHERE orders.id=$1`,
[id]
);
const orderItemsSelect = 'SELECT product_id, name AS product_name, price AS product_price, product_quantity';
const productsJoin = 'JOIN products ON order_products.product_id = products.id'
const orderItemsRes = await query(
`${orderItemsSelect} FROM order_products ${productsJoin} WHERE order_id=$1`,
[id]
);
return {
order_id: orderRes.rows[0].id,
user_id: orderRes.rows[0].user_id,
order_items: orderItemsRes.rows,
order_placed_time: orderRes.rows[0].order_placed_time,
order_status: orderRes.rows[0].status,
total_cost: orderRes.rows[0].total_cost,
address: orderRes.rows[0].address,
postcode: orderRes.rows[0].postcode
};
};
const updateOrderStatus = async (id, status) => {
await query(
'UPDATE orders SET status=$1 WHERE id=$2',
[status, id]
);
return;
};
// Exports
module.exports = {
query,
emailExists,
getUserByEmail,
addLocalUser,
addGoogleUser,
updateUserPassword,
getProducts,
getProductById,
getCategories,
getCartItems,
cartItemExists,
addCartItem,
deleteCartItem,
getAddressById,
getAddressId,
addAddress,
createPendingOrder,
confirmPaidOrder,
getOrdersSummary,
getOrderUserId,
getOrderStatus,
getOrderById,
updateOrderStatus,
};