From 85d1a9f04690faad9f6d1444b541453f6b78105a Mon Sep 17 00:00:00 2001 From: ParkSuMin Date: Sat, 3 Jan 2026 18:15:52 +0300 Subject: [PATCH 1/3] API check --- server/server.js | 184 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 139 insertions(+), 45 deletions(-) diff --git a/server/server.js b/server/server.js index 00c82c9..3324887 100644 --- a/server/server.js +++ b/server/server.js @@ -40,7 +40,30 @@ app.get('/api/current-date', async (req, res) => { res.json({ currentDate }); }); -app.get('/api/products', async(req, res) => { +app.post('/api/current-date/advance', async (req, res) => { + let currentDate = getCurrentDate(); + try { + const raw_ids = await adapter.getOrdersByDate(currentDate); + if (raw_ids && raw_ids.length > 0) { + const ids = raw_ids.map(item => item.id); + await adapter.clearOrders(ids); + } + let nextDate = AddDays(currentDate, 1); + await adapter.stock(); + await setCurrentDate(nextDate); + res.json({ currentDate: nextDate }); + } catch (err){ + res.statusCode = 500; + res.message = "WHOOPS"; + res.json({ + timeStamp: new Date().toISOString(), + statusCode: res.statusCode, + error : `${err}` + }) + } +}); + +app.get('/api/products', async (req, res) => { try { const db_products = await adapter.getProducts(); @@ -65,30 +88,7 @@ app.get('/api/products', async(req, res) => { } }); -app.post('/api/current-date/advance', async (req, res) => { - let currentDate = getCurrentDate(); - try { - const raw_ids = await adapter.getOrdersByDate(currentDate); - if (raw_ids && raw_ids.length > 0) { - const ids = raw_ids.map(item => item.id); - await adapter.clearOrders(ids); - } - let nextDate = AddDays(currentDate, 1); - await adapter.stock(); - await setCurrentDate(nextDate); - res.json({ currentDate: nextDate }); - } catch (err){ - res.statusCode = 500; - res.message = "WHOOPS"; - res.json({ - timeStamp: new Date().toISOString(), - statusCode: res.statusCode, - error : `${err}` - }) - } -}); - -app.get('/api/orders', async(req, res) => { +app.get('/api/orders', async (req, res) => { try { const db_orders = await adapter.getOrders(); @@ -100,6 +100,7 @@ app.get('/api/orders', async(req, res) => { status: status, total: total_amount, items: items ? items.map(item => ({ + id: item.id, product_id: item.product_id, product_name: item.product_name || item.name, quantity: item.quantity, @@ -170,6 +171,64 @@ app.post('/api/orders', async (req, res) => { } }); +app.put('/api/orders/:id', async (req, res) => { + const { customerName, orderDate } = req.body; + const orderId = req.params.id; + + if (!customerName || !orderDate) { + return res.status(400).json({ + timeStamp: new Date().toISOString(), + statusCode: 400, + error: "Customer name and order date are required" + }); + } + + try { + await adapter.changeOrderInfo({ + id: orderId, + customer_name: customerName, + orderDate + }); + + return res.json({ + id: orderId, + customer_name: customerName, + order_date: orderDate + }); + + } catch(err) { + console.error('Error updating order:', err); + + const errorMessage = err.message || + err.error?.message || + "Unknown error occurred"; + + switch(err.type) { + case DB_INTERNAL_ERROR: + return res.status(500).json({ + timeStamp: new Date().toISOString(), + statusCode: 500, + error: errorMessage, + details: err.details || "Internal server error" + }); + + case DB_USER_ERROR: + return res.status(400).json({ + timeStamp: new Date().toISOString(), + statusCode: 400, + error: errorMessage + }); + + default: + return res.status(500).json({ + timeStamp: new Date().toISOString(), + statusCode: 500, + error: errorMessage + }); + } + } +}); + app.delete('/api/orders/:id', async (req, res) => { try { await adapter.deleteOrderById(req.params.id); @@ -191,25 +250,52 @@ app.post('/api/orders/:orderId/items', async (req, res) => { let orderId = req.params.orderId; let { productId, quantity } = req.body; + if (!productId || !quantity || quantity <= 0) { + return res.status(400).json({ + timeStamp: new Date().toISOString(), + statusCode: 400, + error: "Invalid productId or quantity" + }); + } + try { - await adapter.addOrderItem({ + const itemId = await adapter.addOrderItem({ orderId, productId, quantity }); - res.status(201).json({ + return res.status(201).json({ + itemId: itemId.toString(), orderId, productId, quantity }); } catch (err) { - // TODO - res.status(500).json({ - timeStamp: new Date().toISOString(), - statusCode: 500, - }); + console.error('Error adding order item:', err); + + if (err.type === DB_USER_ERROR) { + return res.status(400).json({ + timeStamp: new Date().toISOString(), + statusCode: 400, + error: err.error?.message || "Invalid request", + details: err.details + }); + } else if (err.type === DB_INTERNAL_ERROR) { + return res.status(500).json({ + timeStamp: new Date().toISOString(), + statusCode: 500, + error: "Internal server error", + details: err.error?.message + }); + } else { + return res.status(500).json({ + timeStamp: new Date().toISOString(), + statusCode: 500, + error: "Internal server error" + }); + } } }); @@ -229,10 +315,10 @@ app.put('/api/orders/:orderId/items/:itemId', async (req, res) => { }); } catch (err) { - // TODO - res.status(500).json({ + res.json({ timeStamp: new Date().toISOString(), statusCode: 500, + error: err.message }); } }); @@ -240,17 +326,25 @@ app.put('/api/orders/:orderId/items/:itemId', async (req, res) => { app.delete('/api/orders/:orderId/items/:itemId', async (req, res) => { try { await adapter.deleteOrderItem(req.params.itemId); - res.status(204).end(); - } catch (err){ - res.statusCode = 500; - res.message = "WHOOPS"; - res.json({ - timeStamp: new Date().toISOString(), - statusCode: 500, - error: `${err}` - }); + return res.status(204).end(); + } catch (err) { + if (err.type === DB_INTERNAL_ERROR) { + return res.status(500).json({ + timeStamp: new Date().toISOString(), + statusCode: 500, + error: "Server error", + details: err.error.message + }); + } else { + return res.status(500).json({ + timeStamp: new Date().toISOString(), + statusCode: 500, + error: "Internal server error", + details: err.message + }); + } } -}) +}); app.post('/api/order-items/:itemId/move', async (req, res) => { let itemId = req.params.itemId; @@ -298,4 +392,4 @@ process.on('SIGTERM', () => { await adapter.disconnect(); console.log("DB DISCONNECTED"); }); -}); +}); \ No newline at end of file From 58b88c05a734129c06a82e416619b3e18c15418f Mon Sep 17 00:00:00 2001 From: ParkSuMin Date: Sat, 3 Jan 2026 18:16:18 +0300 Subject: [PATCH 2/3] CORS problem resolve --- server/server.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/server/server.js b/server/server.js index 3324887..a429520 100644 --- a/server/server.js +++ b/server/server.js @@ -20,6 +20,16 @@ const appPort = 3000; const app = express(); app.use(express.json()); +app.use((req, res, next) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); + res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,DELETE'); + if (req.method === 'OPTIONS') { + res.sendStatus(204); + return; + } + next(); +}); const adapter = new DBAdapter({ dbHost: HOST, From 13fe841e5467c75acfcbb498854a47eb44a5aca3 Mon Sep 17 00:00:00 2001 From: ParkSuMin Date: Sat, 3 Jan 2026 18:17:29 +0300 Subject: [PATCH 3/3] Database --- server/db/database.js | 363 +++++++++++++++++++++++++++++++----------- 1 file changed, 273 insertions(+), 90 deletions(-) diff --git a/server/db/database.js b/server/db/database.js index 693f083..5d11592 100644 --- a/server/db/database.js +++ b/server/db/database.js @@ -24,7 +24,7 @@ export default class DBAdapter { #dbName = 'N'; #dbUserLogin = 'u'; #dbUserPassword = 'p'; - #dbClient = null; + #pool = null; constructor({ dbHost, @@ -38,40 +38,43 @@ export default class DBAdapter { this.#dbName = dbName; this.#dbUserLogin = dbUserLogin; this.#dbUserPassword = dbUserPassword; - this.#dbClient = new mdb.createPool({ + this.#pool = mdb.createPool({ host: this.#dbhost, port: this.#dbPort, database: this.#dbName, user: this.#dbUserLogin, password: this.#dbUserPassword, - dateStrings: true + dateStrings: true, + acquireTimeout: 10000, }); } + async #getConnection() { + return await this.#pool.getConnection(); + } + async connect() { try { - // Проверяем подключение, выполняя простой запрос - const conn = await this.#dbClient.getConnection(); + const conn = await this.#pool.getConnection(); console.log("✅ Database connection test successful"); conn.release(); } catch (err) { console.error("❌ Database connection failed:", err.message); - // Пробрасываем ошибку дальше return Promise.reject(); } } async disconnect() { - await this.#dbClient.end(); + await this.#pool.end(); console.log("DISCONNECT"); } async stock(){ try { - const products = await this.#dbClient.query('SELECT id FROM products'); + const products = await this.#pool.query('SELECT id FROM products'); for (const product of products) { const increment = Random_Increase(); - await this.#dbClient.query('UPDATE products SET quantity = quantity + ? WHERE id = ?', [ + await this.#pool.query('UPDATE products SET quantity = quantity + ? WHERE id = ?', [ increment, product.id ]); @@ -86,8 +89,7 @@ export default class DBAdapter { async getProducts() { try { - const products = await this.#dbClient.query('SELECT * FROM products ORDER BY name'); - console.log("All products:", products); + const products = await this.#pool.query('SELECT * FROM products ORDER BY name'); return products; } catch(err){ console.log(`WHOOPS`); @@ -97,14 +99,12 @@ export default class DBAdapter { async getOrders() { try { - const raw_orders = await this.#dbClient.query('SELECT * FROM orders ORDER BY order_date ASC'); - console.log("All orders:", raw_orders); + const raw_orders = await this.#pool.query('SELECT * FROM orders ORDER BY order_date ASC'); - const items = await this.#dbClient.query( + const items = await this.#pool.query( `SELECT order_items.*, products.name AS product_name FROM order_items JOIN products ON order_items.product_id = products.id` ); - console.log("All order items:", items); const groupedItems = {}; if (items && items.length > 0) { @@ -132,7 +132,7 @@ export default class DBAdapter { } async addOrder({ id, customer_name, orderDate} ){ - if (!customer_name || !orderDate){ + if (!id || !customer_name || !orderDate){ return Promise.reject({ type: DB_USER_ERROR, error: new Error("Empty in customer name or order date") @@ -146,7 +146,7 @@ export default class DBAdapter { }); } try { - await this.#dbClient.query('INSERT INTO orders (id, customer_name, order_date) VALUES (?, ?, ?)', + await this.#pool.query('INSERT INTO orders (id, customer_name, order_date) VALUES (?, ?, ?)', [id, customer_name, orderDate] ); } catch(err){ @@ -159,7 +159,7 @@ export default class DBAdapter { async getOrdersByDate(date){ try { - const order_ids = await this.#dbClient.query('SELECT id FROM orders WHERE order_date = ?', [date]); + const order_ids = await this.#pool.query('SELECT id FROM orders WHERE order_date = ?', [date]); return order_ids; } catch(err){ return Promise.reject(); @@ -175,7 +175,7 @@ export default class DBAdapter { try { const placeholders = orderIds.map(() => '?').join(','); - const result = await this.#dbClient.query( + const result = await this.#pool.query( `DELETE FROM orders WHERE id IN (${placeholders})`, orderIds ); @@ -188,11 +188,12 @@ export default class DBAdapter { } } - async changeOrderInfo({ id, customer_name, orderDate }){ - if (!id) { + async changeOrderInfo({ id, customer_name, orderDate }) { + if (!id || !customer_name || !orderDate) { return Promise.reject({ type: DB_USER_ERROR, - error: new Error("Order ID is required") + error: new Error("Order ID, name or date is required!"), + message: "Order ID, name or date is required!" // Добавляем message напрямую }); } @@ -200,100 +201,205 @@ export default class DBAdapter { if (orderDate < currentDate) { return Promise.reject({ type: DB_USER_ERROR, - error: new Error("Order date cannot be less than current date") + error: new Error("Order date cannot be less than current date"), + message: "Order date cannot be less than current date" }); } try { - const order = await this.#dbClient.query('SELECT * FROM orders WHERE id = ?', [id]); + const order = await this.#pool.query('SELECT * FROM orders WHERE id = ?', [id]); - if (!order){ + if (!order || order.length === 0) { // Исправлено: проверяем length return Promise.reject({ type: DB_USER_ERROR, - error: new Error("Order not found") + error: new Error("Order not found"), + message: "Order not found" }); } const updatedName = customer_name; const updatedDate = orderDate; - await this.#dbClient.query('UPDATE orders SET customer_name = ?, order_date = ? WHERE id = ?', + await this.#pool.query('UPDATE orders SET customer_name = ?, order_date = ? WHERE id = ?', [updatedName, updatedDate, id] ); - } catch(err){ + } catch(err) { + console.error('Database error in changeOrderInfo:', err); return Promise.reject({ type: DB_INTERNAL_ERROR, - error: new Error("Server error") + error: new Error("Server error"), + message: err.message || "Database operation failed", + details: err.message }); } } - async deleteOrderById(order_id){ + async deleteOrderById(order_id) { + let connection; + try { - const orderItems = await this.#dbClient.query( + connection = await this.#pool.getConnection(); + await connection.beginTransaction(); + + const orderItems = await connection.query( `SELECT product_id, quantity FROM order_items WHERE order_id = ?`, [order_id] ); - for (const item of orderItems){ - await this.#dbClient.query('UPDATE products SET quantity = quantity + ? WHERE id = ?', + for (const item of orderItems) { + await connection.query( + 'UPDATE products SET quantity = quantity + ? WHERE id = ?', [item.quantity, item.product_id] ); } - await this.#dbClient.query( + + await connection.query( + `DELETE FROM order_items WHERE order_id = ?`, + [order_id] + ); + + await connection.query( `DELETE FROM orders WHERE id = ?`, [order_id] ); - } catch(err){ + + await connection.commit(); + connection.release(); + + } catch (err) { + if (connection) { + try { + await connection.rollback(); + } catch (rollbackErr) { + console.error('Rollback error:', rollbackErr.message); + } + connection.release(); + } + return Promise.reject({ type: DB_INTERNAL_ERROR, - error: new Error("Server error") + error: new Error("Failed to delete order"), + details: err.message }); } } - async addOrderItem({ orderId, productId, quantity }){ - await this.#dbClient.query('BEGIN'); + async addOrderItem({ orderId, productId, quantity }) { + let connection; + try { - const product = await this.#dbClient.query( - 'SELECT quantity FROM products WHERE id = ?', [productId] + connection = await this.#pool.getConnection(); + + await connection.beginTransaction(); + + const orderCheck = await connection.query( + 'SELECT id FROM orders WHERE id = ?', + [orderId] + ); + + if (!orderCheck || orderCheck.length === 0) { + await connection.rollback(); + connection.release(); + return Promise.reject({ + type: DB_USER_ERROR, + error: new Error("Order not found") + }); + } + + const product = await connection.query( + 'SELECT id, quantity FROM products WHERE id = ? FOR UPDATE', + [productId] ); const productRow = product?.[0]; - if (!productRow || productRow.quantity < quantity){ + + if (!productRow) { + await connection.rollback(); + connection.release(); + return Promise.reject({ + type: DB_USER_ERROR, + error: new Error("Product not found") + }); + } + + if (productRow.quantity < quantity) { + await connection.rollback(); + connection.release(); return Promise.reject({ type: DB_USER_ERROR, error: new Error("Insufficient product quantity") }); } - const result = await this.#dbClient.query( - 'INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)', - [orderId, productId, quantity] - ); - - await this.#dbClient.query( + await connection.query( 'UPDATE products SET quantity = quantity - ? WHERE id = ?', [quantity, productId] ); - await this.#dbClient.query('COMMIT'); - } catch (err){ - await this.#dbClient.query('ROLLBACK'); - return Promise.reject(); + const result = await connection.query( + 'INSERT INTO order_items (order_id, product_id, quantity) VALUES (?, ?, ?)', + [orderId, productId, quantity] + ); + + await connection.commit(); + + connection.release(); + + return result.insertId; + + } catch (err) { + if (connection) { + try { + await connection.rollback(); + } catch (rollbackErr) { + console.error('Rollback error:', rollbackErr.message); + } + connection.release(); + } + + console.error('Transaction error:', err); + + let errorType = DB_INTERNAL_ERROR; + let userMessage = "Database operation failed"; + let retryable = false; + + if (err.code === 'ER_LOCK_WAIT_TIMEOUT' || err.errno === 1205) { + errorType = 'DB_CONFLICT'; + userMessage = "System is busy. Please try again in a moment."; + retryable = true; + } else if (err.code === 'ER_DUP_ENTRY') { + errorType = DB_USER_ERROR; + userMessage = "Item already exists in the order"; + } + + return Promise.reject({ + type: errorType, + error: new Error(userMessage), + details: err.message, + code: err.code, + retryable: retryable, + originalError: err + }); } } - async updateOrderItem({ itemId, quantity }){ - await this.#dbClient.query('BEGIN'); + async updateOrderItem({ itemId, quantity }) { + let connection; + try { - const item = await this.#dbClient.query( - 'SELECT product_id, quantity FROM order_items WHERE id = ?', [itemId] + connection = await this.#pool.getConnection(); + await connection.beginTransaction(); + + const item = await connection.query( + 'SELECT product_id, quantity FROM order_items WHERE id = ? FOR UPDATE', + [itemId] ); const itemRow = item?.[0]; - if (!itemRow){ + if (!itemRow) { + await connection.rollback(); + connection.release(); return Promise.reject({ type: DB_USER_ERROR, error: new Error("Order item not found") @@ -302,100 +408,177 @@ export default class DBAdapter { const diff = quantity - itemRow.quantity; - if (diff > 0){ - const product = await this.#dbClient.query( - 'SELECT quantity FROM products WHERE id = ?', [itemRow.product_id] + if (diff > 0) { + const product = await connection.query( + 'SELECT quantity FROM products WHERE id = ? FOR UPDATE', + [itemRow.product_id] ); const productRow = product?.[0]; - if (!productRow || productRow.quantity < diff){ + if (!productRow || productRow.quantity < diff) { + await connection.rollback(); + connection.release(); return Promise.reject({ type: DB_USER_ERROR, error: new Error("Insufficient product quantity") }); } - await this.#dbClient.query( + await connection.query( 'UPDATE products SET quantity = quantity - ? WHERE id = ?', [diff, itemRow.product_id] ); } if (diff < 0) { - await this.#dbClient.query( + await connection.query( 'UPDATE products SET quantity = quantity + ? WHERE id = ?', [-diff, itemRow.product_id] ); } - await this.#dbClient.query( + await connection.query( 'UPDATE order_items SET quantity = ? WHERE id = ?', [quantity, itemId] - ); + ); - await this.#dbClient.query('COMMIT'); - } catch (err){ - await this.#dbClient.query('ROLLBACK'); - return Promise.reject(); + await connection.commit(); + connection.release(); + + } catch (err) { + if (connection) { + try { + await connection.rollback(); + } catch (rollbackErr) { + console.error('Rollback error:', rollbackErr.message); + } + connection.release(); + } + + return Promise.reject({ + type: DB_INTERNAL_ERROR, + error: new Error("Failed to update order item"), + details: err.message + }); } } - async deleteOrderItem(itemId){ - await this.#dbClient.query('BEGIN'); + async deleteOrderItem(itemId) { + let connection; + try { - const item = await this.#dbClient.query( - 'SELECT product_id, quantity FROM order_items WHERE id = ?', [itemId] + connection = await this.#pool.getConnection(); + await connection.beginTransaction(); + + const item = await connection.query( + 'SELECT product_id, quantity FROM order_items WHERE id = ? FOR UPDATE', + [itemId] ); const itemRow = item?.[0]; - if (!itemRow){ + if (!itemRow) { + await connection.rollback(); + connection.release(); return Promise.reject({ type: DB_USER_ERROR, error: new Error("Order item not found") }); } - await this.#dbClient.query( + await connection.query( 'UPDATE products SET quantity = quantity + ? WHERE id = ?', [itemRow.quantity, itemRow.product_id] ); - await this.#dbClient.query( + await connection.query( 'DELETE FROM order_items WHERE id = ?', [itemId] ); - await this.#dbClient.query('COMMIT'); - } catch (err){ - await this.#dbClient.query('ROLLBACK'); - return Promise.reject(); + await connection.commit(); + connection.release(); + + } catch (err) { + if (connection) { + try { + await connection.rollback(); + } catch (rollbackErr) { + console.error('Rollback error:', rollbackErr.message); + } + connection.release(); + } + + console.error('Delete order item error:', err); + + return Promise.reject({ + type: DB_INTERNAL_ERROR, + error: new Error("Failed to delete order item"), + details: err.message, + itemId: itemId + }); } } - async moveOrderItem( {itemId, targetOrderId }){ - await this.#dbClient.query('BEGIN'); + async moveOrderItem({ itemId, targetOrderId }) { + let connection; + try { - const item = await this.#dbClient.query( - 'SELECT id, order_id FROM order_items WHERE id = ?', [itemId] + connection = await this.#pool.getConnection(); + await connection.beginTransaction(); + + // Проверяем существование целевого заказа + const targetOrderCheck = await connection.query( + 'SELECT id FROM orders WHERE id = ?', + [targetOrderId] + ); + + if (!targetOrderCheck || targetOrderCheck.length === 0) { + await connection.rollback(); + connection.release(); + return Promise.reject({ + type: DB_USER_ERROR, + error: new Error("Target order not found") + }); + } + + const item = await connection.query( + 'SELECT id, order_id FROM order_items WHERE id = ? FOR UPDATE', + [itemId] ); const itemRow = item?.[0]; - if (!itemRow){ + if (!itemRow) { + await connection.rollback(); + connection.release(); return Promise.reject({ type: DB_USER_ERROR, error: new Error("Order item not found") }); } - await this.#dbClient.query( + await connection.query( 'UPDATE order_items SET order_id = ? WHERE id = ?', [targetOrderId, itemId] ); - await this.#dbClient.query('COMMIT'); - } catch (err){ - await this.#dbClient.query('ROLLBACK'); - return Promise.reject(); + await connection.commit(); + connection.release(); + + } catch (err) { + if (connection) { + try { + await connection.rollback(); + } catch (rollbackErr) { + console.error('Rollback error:', rollbackErr.message); + } + connection.release(); + } + + return Promise.reject({ + type: DB_INTERNAL_ERROR, + error: new Error("Failed to move order item"), + details: err.message + }); } } -} +} \ No newline at end of file