From 8b777f57adbd8e192c31558c2467775183d013cf Mon Sep 17 00:00:00 2001 From: weijing Date: Tue, 16 Sep 2025 12:38:41 +0800 Subject: [PATCH] Initial commit --- api_server.py | 738 ++++++++++++++++------------------------- creat_api.txt | 27 +- create_menu_table.py | 39 +++ create_order_table.py | 46 +++ create_order_table.txt | 14 + requirements.txt | Bin 62164 -> 30699 bytes update_table.py | 35 ++ update_table.txt | 14 + 8 files changed, 443 insertions(+), 470 deletions(-) create mode 100644 create_menu_table.py create mode 100644 create_order_table.py create mode 100644 create_order_table.txt create mode 100644 update_table.py create mode 100644 update_table.txt diff --git a/api_server.py b/api_server.py index f109488..b027b3f 100644 --- a/api_server.py +++ b/api_server.py @@ -1,533 +1,363 @@ - +import mysql.connector from flask import Flask, request, jsonify from flask_cors import CORS -import mysql.connector -from mysql.connector import pooling -import os -# --- Database Configuration --- -DB_HOST = "mysql.theaken.com" -DB_PORT = 33306 -DB_NAME = "db_A027" -DB_USER = "A027" -DB_PASSWORD = "E1CelfxqlKoj" - -# --- Create a connection pool --- -try: - db_pool = pooling.MySQLConnectionPool( - pool_name="api_pool", - pool_size=5, - host=DB_HOST, - port=DB_PORT, - database=DB_NAME, - user=DB_USER, - password=DB_PASSWORD - ) - print("Database connection pool created successfully.") -except mysql.connector.Error as err: - print(f"Error creating connection pool: {err}") - exit() - -# --- Flask App Initialization --- app = Flask(__name__) -CORS(app) # Enable CORS for all routes +CORS(app) # Enable CORS for all routes + +# Database configuration +db_config = { + 'host': 'mysql.theaken.com', + 'port': 33306, + 'user': 'A027', + 'password': 'E1CelfxqlKoj', + 'database': 'db_A027' +} -# --- Helper Functions --- def get_db_connection(): - """Get a connection from the pool.""" + """Establishes a new database connection.""" try: - return db_pool.get_connection() + conn = mysql.connector.connect(**db_config) + return conn except mysql.connector.Error as err: - print(f"Error getting connection from pool: {err}") + print(f"Database connection error: {err}") return None -def make_response(status, code, message, data=None): - """Standardized response format.""" +def make_success_response(data, code, message="Success"): + """Wrapper for a successful API response.""" response = { - "status": status, + "status": "success", "code": code, - "message": message + "message": message, } if data is not None: response["data"] = data return jsonify(response), code -# --- Error Handlers --- -@app.errorhandler(404) -def not_found(error): - return make_response("error", 404, "The requested resource was not found.") +def make_error_response(message, code): + """Wrapper for an error API response.""" + return jsonify({ + "status": "error", + "code": code, + "message": message + }), code -@app.errorhandler(500) -def internal_server_error(error): - return make_response("error", 500, "An internal server error occurred.") +def query_to_dict(cursor, data): + """Converts query result to a list of dictionaries.""" + columns = [desc[0] for desc in cursor.description] + return [dict(zip(columns, row)) for row in data] -# --- Employee API Endpoints --- +# --- Orders API Endpoints --- -@app.route('/v1/employee', methods=['GET']) -def get_employees(): - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - query = "SELECT * FROM employee WHERE 1=1" - params = [] - - if 'id' in request.args: - query += " AND id = %s" - params.append(request.args['id']) - if 'name' in request.args: - query += " AND name LIKE %s" - params.append(f"%{request.args['name']}%") - - try: - cursor.execute(query, tuple(params)) - employees = cursor.fetchall() - return make_response("success", 200, "Employees retrieved successfully.", {"employees": employees, "meta": {"total": len(employees)}}) - except mysql.connector.Error as err: - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -@app.route('/v1/employee/', methods=['GET']) -def get_employee(emp_id): - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - try: - cursor.execute("SELECT * FROM employee WHERE id = %s", (emp_id,)) - employee = cursor.fetchone() - if employee: - return make_response("success", 200, "Employee found.", employee) - else: - return make_response("error", 404, "Employee not found.") - except mysql.connector.Error as err: - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -@app.route('/v1/employee', methods=['POST']) -def create_employee(): - data = request.get_json() - if not data or 'id' not in data or 'name' not in data: - return make_response("error", 400, "Missing required fields: id, name.") - - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - try: - cursor.execute("INSERT INTO employee (id, name) VALUES (%s, %s)", (data['id'], data['name'])) - conn.commit() - cursor.execute("SELECT * FROM employee WHERE id = %s", (data['id'],)) - new_employee = cursor.fetchone() - return make_response("success", 201, "Employee created successfully.", new_employee) - except mysql.connector.Error as err: - conn.rollback() - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -@app.route('/v1/employee/', methods=['PATCH']) -def update_employee(emp_id): +@app.route('/v1/orders', methods=['POST']) +def create_order(): data = request.get_json() if not data: - return make_response("error", 400, "No update data provided.") + return make_error_response("Invalid JSON", 400) + required_fields = ['emp_id', 'emp_name', 'menu_item_id', 'main_course', 'order_date', 'order_qty'] + if not all(field in data for field in required_fields): + return make_error_response(f"Missing required fields: {required_fields}", 400) + + sql = """ + INSERT INTO orders (emp_id, emp_name, menu_item_id, main_course, order_date, order_qty) + VALUES (%(emp_id)s, %(emp_name)s, %(menu_item_id)s, %(main_course)s, %(order_date)s, %(order_qty)s) + """ + conn = get_db_connection() if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - updates = [] - params = [] - if 'name' in data: - updates.append("name = %s") - params.append(data['name']) - - if not updates: - return make_response("error", 400, "No valid fields to update.") - - params.append(emp_id) - query = f"UPDATE employee SET {', '.join(updates)} WHERE id = %s" - - try: - cursor.execute(query, tuple(params)) - conn.commit() - if cursor.rowcount == 0: - return make_response("error", 404, "Employee not found or data is the same.") + return make_error_response("Database connection failed", 500) - cursor.execute("SELECT * FROM employee WHERE id = %s", (emp_id,)) - updated_employee = cursor.fetchone() - return make_response("success", 200, "Employee updated successfully.", updated_employee) + try: + cursor = conn.cursor(dictionary=True) + cursor.execute(sql, data) + conn.commit() + new_id = cursor.lastrowid + cursor.execute("SELECT * FROM orders WHERE id = %s", (new_id,)) + new_order = cursor.fetchone() + return make_success_response(new_order, 201, "Order created successfully.") except mysql.connector.Error as err: - conn.rollback() - return make_response("error", 500, str(err)) + return make_error_response(f"Database error: {err}", 500) finally: - cursor.close() - conn.close() + if conn.is_connected(): + conn.close() -@app.route('/v1/employee/', methods=['DELETE']) -def delete_employee(emp_id): +@app.route('/v1/orders/', methods=['GET']) +def get_order_by_id(order_id): conn = get_db_connection() if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor() + return make_error_response("Database connection failed", 500) + + try: + cursor = conn.cursor(dictionary=True) + cursor.execute("SELECT * FROM orders WHERE id = %s", (order_id,)) + order = cursor.fetchone() + if order: + return make_success_response(order, 200) + else: + return make_error_response("Order not found", 404) + except mysql.connector.Error as err: + return make_error_response(f"Database error: {err}", 500) + finally: + if conn.is_connected(): + conn.close() + +@app.route('/v1/orders', methods=['GET']) +def get_orders(): + query_params = request.args.to_dict() + + sql = "SELECT * FROM orders" + conditions = [] + params = {} + + for key, value in query_params.items(): + # Basic filtering, can be expanded + conditions.append(f"{key} = %({key})s") + params[key] = value + + if conditions: + sql += " WHERE " + " AND ".join(conditions) + + conn = get_db_connection() + if not conn: + return make_error_response("Database connection failed", 500) try: - # Note: Deleting an employee might fail if they have existing votes due to foreign key constraints. - # This should be handled by the application logic (e.g., delete votes first or prevent deletion). - cursor.execute("DELETE FROM employee WHERE id = %s", (emp_id,)) - conn.commit() - if cursor.rowcount == 0: - return make_response("error", 404, "Employee not found.") - return make_response("success", 204, "Employee deleted successfully.") + cursor = conn.cursor(dictionary=True) + cursor.execute(sql, params) + orders = cursor.fetchall() + + meta = { + "total_items": len(orders), + "query_params": query_params + } + data = { + "orders": orders, + "meta": meta + } + return make_success_response(data, 200) except mysql.connector.Error as err: - conn.rollback() - # Check for foreign key constraint violation - if err.errno == 1451: - return make_response("error", 409, "Cannot delete employee with existing votes. Please remove their votes first.") - return make_response("error", 500, str(err)) + return make_error_response(f"Database error: {err}", 500) finally: - cursor.close() - conn.close() + if conn.is_connected(): + conn.close() +@app.route('/v1/orders/', methods=['PATCH']) +def update_order(order_id): + data = request.get_json() + if not data: + return make_error_response("Invalid JSON", 400) + + set_clauses = [] + params = {} + for key, value in data.items(): + set_clauses.append(f"{key} = %({key})s") + params[key] = value + + if not set_clauses: + return make_error_response("No fields to update", 400) + + params['id'] = order_id + sql = f"UPDATE orders SET {', '.join(set_clauses)} WHERE id = %(id)s" + + conn = get_db_connection() + if not conn: + return make_error_response("Database connection failed", 500) + + try: + cursor = conn.cursor(dictionary=True) + cursor.execute("SELECT * FROM orders WHERE id = %s", (order_id,)) + if not cursor.fetchone(): + return make_error_response("Order not found", 404) + + cursor.execute(sql, params) + conn.commit() + + cursor.execute("SELECT * FROM orders WHERE id = %s", (order_id,)) + updated_order = cursor.fetchone() + return make_success_response(updated_order, 200, "Order updated successfully.") + except mysql.connector.Error as err: + return make_error_response(f"Database error: {err}", 500) + finally: + if conn.is_connected(): + conn.close() + +@app.route('/v1/orders/', methods=['DELETE']) +def delete_order(order_id): + conn = get_db_connection() + if not conn: + return make_error_response("Database connection failed", 500) + + try: + cursor = conn.cursor() + cursor.execute("SELECT * FROM orders WHERE id = %s", (order_id,)) + if not cursor.fetchone(): + return make_error_response("Order not found", 404) + + cursor.execute("DELETE FROM orders WHERE id = %s", (order_id,)) + conn.commit() + return make_success_response(None, 204, "Order deleted successfully.") + except mysql.connector.Error as err: + return make_error_response(f"Database error: {err}", 500) + finally: + if conn.is_connected(): + conn.close() + # --- Menu Items API Endpoints --- -@app.route('/v1/menu_items', methods=['GET']) -def get_menu_items(): - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - query = "SELECT * FROM menu_items WHERE 1=1" - params = [] - - if 'id' in request.args: - query += " AND id = %s" - params.append(request.args['id']) - if 'main_course' in request.args: - query += " AND main_course LIKE %s" - params.append(f"%{request.args['main_course']}%") - if 'side_dish' in request.args: - query += " AND side_dish LIKE %s" - params.append(f"%{request.args['side_dish']}%") - if 'addon' in request.args: - query += " AND addon LIKE %s" - params.append(f"%{request.args['addon']}%") - if 'menu_date' in request.args: - query += " AND menu_date = %s" - params.append(request.args['menu_date']) - - try: - cursor.execute(query, tuple(params)) - items = cursor.fetchall() - return make_response("success", 200, "Menu items retrieved successfully.", {"menu_items": items, "meta": {"total": len(items)}}) - except mysql.connector.Error as err: - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -@app.route('/v1/menu_items/', methods=['GET']) -def get_menu_item(item_id): - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - try: - cursor.execute("SELECT * FROM menu_items WHERE id = %s", (item_id,)) - item = cursor.fetchone() - if item: - return make_response("success", 200, "Menu item found.", item) - else: - return make_response("error", 404, "Menu item not found.") - except mysql.connector.Error as err: - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - @app.route('/v1/menu_items', methods=['POST']) def create_menu_item(): data = request.get_json() - if not data or not all(k in data for k in ['main_course', 'side_dish', 'addon', 'menu_date']): - return make_response("error", 400, "Missing required fields: main_course, side_dish, addon, menu_date.") - + if not data: + return make_error_response("Invalid JSON", 400) + + required_fields = ['main_course', 'menu_date'] + if not all(field in data for field in required_fields): + return make_error_response(f"Missing required fields: {required_fields}", 400) + + sql = """ + INSERT INTO menu_items (main_course, side_dish, addon, menu_date) + VALUES (%(main_course)s, %(side_dish)s, %(addon)s, %(menu_date)s) + """ + conn = get_db_connection() if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) + return make_error_response("Database connection failed", 500) + + try: + cursor = conn.cursor(dictionary=True) + cursor.execute(sql, data) + conn.commit() + new_id = cursor.lastrowid + cursor.execute("SELECT * FROM menu_items WHERE id = %s", (new_id,)) + new_item = cursor.fetchone() + return make_success_response(new_item, 201, "Menu item created successfully.") + except mysql.connector.Error as err: + return make_error_response(f"Database error: {err}", 500) + finally: + if conn.is_connected(): + conn.close() + +@app.route('/v1/menu_items/', methods=['GET']) +def get_menu_item_by_id(item_id): + conn = get_db_connection() + if not conn: + return make_error_response("Database connection failed", 500) + + try: + cursor = conn.cursor(dictionary=True) + cursor.execute("SELECT * FROM menu_items WHERE id = %s", (item_id,)) + item = cursor.fetchone() + if item: + return make_success_response(item, 200) + else: + return make_error_response("Menu item not found", 404) + except mysql.connector.Error as err: + return make_error_response(f"Database error: {err}", 500) + finally: + if conn.is_connected(): + conn.close() + +@app.route('/v1/menu_items', methods=['GET']) +def get_menu_items(): + query_params = request.args.to_dict() + + sql = "SELECT * FROM menu_items" + conditions = [] + params = {} + + for key, value in query_params.items(): + conditions.append(f"{key} = %({key})s") + params[key] = value + + if conditions: + sql += " WHERE " + " AND ".join(conditions) + + conn = get_db_connection() + if not conn: + return make_error_response("Database connection failed", 500) try: - sql = "INSERT INTO menu_items (main_course, side_dish, addon, menu_date) VALUES (%s, %s, %s, %s)" - params = (data['main_course'], data['side_dish'], data['addon'], data['menu_date']) + cursor = conn.cursor(dictionary=True) cursor.execute(sql, params) - new_item_id = cursor.lastrowid - conn.commit() + items = cursor.fetchall() - cursor.execute("SELECT * FROM menu_items WHERE id = %s", (new_item_id,)) - new_item = cursor.fetchone() - return make_response("success", 201, "Menu item created successfully.", new_item) + meta = { + "total_items": len(items), + "query_params": query_params + } + data = { + "menu_items": items, + "meta": meta + } + return make_success_response(data, 200) except mysql.connector.Error as err: - conn.rollback() - return make_response("error", 500, str(err)) + return make_error_response(f"Database error: {err}", 500) finally: - cursor.close() - conn.close() + if conn.is_connected(): + conn.close() @app.route('/v1/menu_items/', methods=['PATCH']) def update_menu_item(item_id): data = request.get_json() if not data: - return make_response("error", 400, "No update data provided.") + return make_error_response("Invalid JSON", 400) + + set_clauses = [] + params = {} + for key, value in data.items(): + set_clauses.append(f"{key} = %({key})s") + params[key] = value + + if not set_clauses: + return make_error_response("No fields to update", 400) + + params['id'] = item_id + sql = f"UPDATE menu_items SET {', '.join(set_clauses)} WHERE id = %(id)s" conn = get_db_connection() if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - updates = [] - params = [] - for key in ['main_course', 'side_dish', 'addon', 'menu_date']: - if key in data: - updates.append(f"{key} = %s") - params.append(data[key]) - - if not updates: - return make_response("error", 400, "No valid fields to update.") - - params.append(item_id) - query = f"UPDATE menu_items SET {', '.join(updates)} WHERE id = %s" + return make_error_response("Database connection failed", 500) try: - cursor.execute(query, tuple(params)) + cursor = conn.cursor(dictionary=True) + cursor.execute("SELECT * FROM menu_items WHERE id = %s", (item_id,)) + if not cursor.fetchone(): + return make_error_response("Menu item not found", 404) + + cursor.execute(sql, params) conn.commit() - if cursor.rowcount == 0: - return make_response("error", 404, "Menu item not found or data is the same.") cursor.execute("SELECT * FROM menu_items WHERE id = %s", (item_id,)) updated_item = cursor.fetchone() - return make_response("success", 200, "Menu item updated successfully.", updated_item) + return make_success_response(updated_item, 200, "Menu item updated successfully.") except mysql.connector.Error as err: - conn.rollback() - return make_response("error", 500, str(err)) + return make_error_response(f"Database error: {err}", 500) finally: - cursor.close() - conn.close() + if conn.is_connected(): + conn.close() @app.route('/v1/menu_items/', methods=['DELETE']) def delete_menu_item(item_id): conn = get_db_connection() if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor() + return make_error_response("Database connection failed", 500) try: + cursor = conn.cursor() + cursor.execute("SELECT * FROM menu_items WHERE id = %s", (item_id,)) + if not cursor.fetchone(): + return make_error_response("Menu item not found", 404) + cursor.execute("DELETE FROM menu_items WHERE id = %s", (item_id,)) conn.commit() - if cursor.rowcount == 0: - return make_response("error", 404, "Menu item not found.") - return make_response("success", 204, "Menu item deleted successfully.") + return make_success_response(None, 204, "Menu item deleted successfully.") except mysql.connector.Error as err: - conn.rollback() - if err.errno == 1451: - return make_response("error", 409, "Cannot delete menu item that has been voted on.") - return make_response("error", 500, str(err)) + return make_error_response(f"Database error: {err}", 500) finally: - cursor.close() - conn.close() + if conn.is_connected(): + conn.close() - -# --- Employee Votes API Endpoints --- - -@app.route('/v1/employee_votes', methods=['GET']) -def get_employee_votes(): - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - query = """ - SELECT v.*, e.name - FROM employee_votes v - JOIN employee e ON v.emp_id = e.id - WHERE 1=1 - """ - params = [] - - if 'id' in request.args: - query += " AND v.id = %s" - params.append(request.args['id']) - if 'emp_id' in request.args: - query += " AND v.emp_id = %s" - params.append(request.args['emp_id']) - if 'name' in request.args: # Search by employee name - query += " AND e.name LIKE %s" - params.append(f"%{request.args['name']}%") - if 'order_date' in request.args: - query += " AND v.order_date = %s" - params.append(request.args['order_date']) - if 'menu_item_id' in request.args: - query += " AND v.menu_item_id = %s" - params.append(request.args['menu_item_id']) - if 'order_qty' in request.args: - query += " AND v.order_qty = %s" - params.append(request.args['order_qty']) - - try: - cursor.execute(query, tuple(params)) - votes = cursor.fetchall() - return make_response("success", 200, "Employee votes retrieved successfully.", {"employee_votes": votes, "meta": {"total": len(votes)}}) - except mysql.connector.Error as err: - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -@app.route('/v1/employee_votes/', methods=['GET']) -def get_employee_vote(vote_id): - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - try: - cursor.execute(""" - SELECT v.*, e.name - FROM employee_votes v - JOIN employee e ON v.emp_id = e.id - WHERE v.id = %s - """, (vote_id,)) - vote = cursor.fetchone() - if vote: - return make_response("success", 200, "Employee vote found.", vote) - else: - return make_response("error", 404, "Employee vote not found.") - except mysql.connector.Error as err: - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -@app.route('/v1/employee_votes', methods=['POST']) -def create_employee_vote(): - data = request.get_json() - required_fields = ['emp_id', 'order_date', 'menu_item_id', 'order_qty'] - if not data or not all(k in data for k in required_fields): - return make_response("error", 400, f"Missing required fields: {', '.join(required_fields)}.") - - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - try: - sql = "INSERT INTO employee_votes (emp_id, order_date, menu_item_id, order_qty) VALUES (%s, %s, %s, %s)" - params = (data['emp_id'], data['order_date'], data['menu_item_id'], data['order_qty']) - cursor.execute(sql, params) - new_vote_id = cursor.lastrowid - conn.commit() - - cursor.execute(""" - SELECT v.*, e.name - FROM employee_votes v - JOIN employee e ON v.emp_id = e.id - WHERE v.id = %s - """, (new_vote_id,)) - new_vote = cursor.fetchone() - return make_response("success", 201, "Employee vote created successfully.", new_vote) - except mysql.connector.Error as err: - conn.rollback() - if err.errno == 1452: # Foreign key constraint fails - return make_response("error", 400, "Invalid emp_id or menu_item_id. The specified employee or menu item does not exist.") - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -@app.route('/v1/employee_votes/', methods=['PATCH']) -def update_employee_vote(vote_id): - data = request.get_json() - if not data: - return make_response("error", 400, "No update data provided.") - - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor(dictionary=True) - - updates = [] - params = [] - for key in ['emp_id', 'order_date', 'menu_item_id', 'order_qty']: - if key in data: - updates.append(f"{key} = %s") - params.append(data[key]) - - if not updates: - return make_response("error", 400, "No valid fields to update.") - - params.append(vote_id) - query = f"UPDATE employee_votes SET {', '.join(updates)} WHERE id = %s" - - try: - cursor.execute(query, tuple(params)) - conn.commit() - if cursor.rowcount == 0: - return make_response("error", 404, "Employee vote not found or data is the same.") - - cursor.execute(""" - SELECT v.*, e.name - FROM employee_votes v - JOIN employee e ON v.emp_id = e.id - WHERE v.id = %s - """, (vote_id,)) - updated_vote = cursor.fetchone() - return make_response("success", 200, "Employee vote updated successfully.", updated_vote) - except mysql.connector.Error as err: - conn.rollback() - if err.errno == 1452: - return make_response("error", 400, "Invalid emp_id or menu_item_id. The specified employee or menu item does not exist.") - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -@app.route('/v1/employee_votes/', methods=['DELETE']) -def delete_employee_vote(vote_id): - conn = get_db_connection() - if not conn: - return make_response("error", 503, "Database service unavailable.") - cursor = conn.cursor() - - try: - cursor.execute("DELETE FROM employee_votes WHERE id = %s", (vote_id,)) - conn.commit() - if cursor.rowcount == 0: - return make_response("error", 404, "Employee vote not found.") - return make_response("success", 204, "Employee vote deleted successfully.") - except mysql.connector.Error as err: - conn.rollback() - return make_response("error", 500, str(err)) - finally: - cursor.close() - conn.close() - -# --- Main Execution --- if __name__ == '__main__': - # To run this server: - # 1. Make sure you have Flask, Flask-Cors, and mysql-connector-python installed: - # pip install Flask Flask-Cors mysql-connector-python - # 2. Run the script: - # python api_server.py - # The server will start on http://127.0.0.1:5000 - app.run(debug=True, host='0.0.0.0', port=5000) + app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file diff --git a/creat_api.txt b/creat_api.txt index 537113d..87e6e30 100644 --- a/creat_api.txt +++ b/creat_api.txt @@ -8,32 +8,27 @@ DB_NAME = db_A027 DB_USER = A027 DB_PASSWORD = E1CelfxqlKoj -TABLE = employee -- 路由: -- GET /v1/employee:支援 ?Id、?main_course、?side_dish、?addon、?is_active,要多回傳 {meta} -- GET /v1/employee/:找不到回 404 -- POST /v1/employee:接收 JSON {ID,main_course,side_dish,addon,is_active},欄位驗證,成功回 201 並回新資料 -- PATCH /v1/employee/:可更新任一欄位(ID/main_course/side_dish/addon/is_active),成功回 200 回更新後資料 -- DELETE /v1/employee/:成功回 204 + -TABLE = employee_votes +TABLE = orders - 路由: -- GET /v1/employee_votes:支援 ?Id、?emp_id、?name、?order_date、?menu_item_id、?order_qty,要多回傳 {meta} -- GET /v1/employee_votes/:找不到回 404 -- POST /v1/employee_votes:接收 JSON {Id,emp_id,name,order_date,menu_item_id,order_qty},欄位驗證,成功回 201 並回新資料 -- PATCH /v1/employee_votes/:可更新任一欄位(Id/emp_id/name/order_date/menu_item_id/order_qty),成功回 200 回更新後資料 -- DELETE /v1/employee_votes/:成功回 204 +- GET /v1/orders:支援 ?Id、?emp_id、?emp_name、?menu_item_id、?main_course、?order_date、?order_qty,要多回傳 {meta} +- GET /v1/orders/:找不到回 404 +- POST /v1/orders:接收 JSON {Id,emp_id,emp_name,menu_item_id,main_course,order_date,order_qty},欄位驗證,成功回 201 並回新資料 +- PATCH /v1/orders/:可更新任一欄位(id/emp_id/name/menu_item_id/main_course/order_date/order_qty),成功回 200 回更新後資料 +- DELETE /v1/orders/:成功回 204 TABLE = menu_items - 路由: -- GET /v1/menu_items:支援 ?Id、?main_course、?side_dish、?addon、?is_active,要多回傳 {meta} +- GET /v1/menu_items:支援 ?id、?main_course、?side_dish、?addon、?menu_date,要多回傳 {meta} - GET /v1/menu_items/:找不到回 404 -- POST /v1/menu_items:接收 JSON {ID,main_course,side_dish,addon,is_active},欄位驗證,成功回 201 並回新資料 -- PATCH /v1/menu_items/:可更新任一欄位(ID/main_course/side_dish/addon/is_active),成功回 200 回更新後資料 +- POST /v1/menu_items:接收 JSON {id,main_course,side_dish,addon,menu_date},欄位驗證,成功回 201 並回新資料 +- PATCH /v1/menu_items/:可更新任一欄位(id/main_course/side_dish/addon/menu_date),成功回 200 回更新後資料 - DELETE /v1/menu_items/:成功回 204 + - 所有寫入請用參數化查詢避免 SQL 注入 diff --git a/create_menu_table.py b/create_menu_table.py new file mode 100644 index 0000000..23dc1a5 --- /dev/null +++ b/create_menu_table.py @@ -0,0 +1,39 @@ + +import mysql.connector + +# DB credentials +config = { + 'user': 'A027', + 'password': 'E1CelfxqlKoj', + 'host': 'mysql.theaken.com', + 'port': 33306, + 'database': 'db_A027' +} + +TABLES = {} +TABLES['menu_items'] = ( + "CREATE TABLE `menu_items` (" + " `id` int NOT NULL AUTO_INCREMENT," + " `main_course` varchar(255) NOT NULL," + " `side_dish` varchar(255) DEFAULT NULL," + " `addon` varchar(255) DEFAULT NULL," + " `menu_date` date NOT NULL," + " PRIMARY KEY (`id`)" + ") ENGINE=InnoDB") + +try: + cnx = mysql.connector.connect(**config) + cursor = cnx.cursor() + + cursor.execute("DROP TABLE IF EXISTS menu_items") + table_description = TABLES['menu_items'] + cursor.execute(table_description) + print("資料表 'menu_items' 建立成功!") + +except mysql.connector.Error as err: + print(f"資料庫錯誤: {err}") +finally: + if 'cursor' in locals() and cursor: + cursor.close() + if 'cnx' in locals() and cnx.is_connected(): + cnx.close() diff --git a/create_order_table.py b/create_order_table.py new file mode 100644 index 0000000..d2ca0ee --- /dev/null +++ b/create_order_table.py @@ -0,0 +1,46 @@ + +import mysql.connector +from mysql.connector import errorcode + +# Assuming the same DB credentials as the previous task +config = { + 'user': 'A027', + 'password': 'E1CelfxqlKoj', + 'host': 'mysql.theaken.com', + 'port': 33306, + 'database': 'db_A027' +} + +TABLES = {} +TABLES['orders'] = ( + "CREATE TABLE `orders` (" + " `id` int NOT NULL AUTO_INCREMENT," + " `emp_id` varchar(10) NOT NULL," + " `emp_name` varchar(10) NOT NULL," + " `menu_item_id` int NOT NULL," + " `main_course` int NOT NULL," + " `order_date` date NOT NULL," + " `order_qty` int NOT NULL," + " PRIMARY KEY (`id`)" + ") ENGINE=InnoDB") + +try: + cnx = mysql.connector.connect(**config) + cursor = cnx.cursor() + + # Drop the table if it exists + cursor.execute("DROP TABLE IF EXISTS orders") + print("資料表 'orders' 已刪除 (如果存在的話)。") + + # Create the table + table_description = TABLES['orders'] + cursor.execute(table_description) + print("資料表 'orders' 建立成功!") + +except mysql.connector.Error as err: + print(f"資料庫錯誤: {err}") +finally: + if 'cursor' in locals() and cursor: + cursor.close() + if 'cnx' in locals() and cnx.is_connected(): + cnx.close() diff --git a/create_order_table.txt b/create_order_table.txt new file mode 100644 index 0000000..c9aeeeb --- /dev/null +++ b/create_order_table.txt @@ -0,0 +1,14 @@ +請根據以下需求幫我產生一段 Python 程式碼,能夠連線到資料庫並建立一張 orders 資料表。 +資料庫資訊 參考 ".env檔" +需求: +1. 使用 mysql.connector 套件。 +2. 建立一張名稱為 orders 的資料表,包含以下欄位: + - id: 訂購id (NUMBER) + - emp_id: 員工id (VARCHAR(10)) + - emp_name: 員工姓名 (VARCHAR(10)) + - menu_item_id: 餐點id (NUMBER) + - main_course: 主餐點名稱 (NUMBER) + - order_date: 訂購日期 (DATE) + - order_qty: 訂購數量 (NUMBER) +3. 執行成功後,印出「資料表建立成功!」。 +4. 若資料表已存在,先刪除再重建。 diff --git a/requirements.txt b/requirements.txt index 2b307912fd1da1a31c36ead751d2959915011394..523fd843b13400c9a08f7fe2c9ed5b8581109dee 100644 GIT binary patch literal 30699 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 E|B-9oA^-pY literal 62164 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 NORfhx@}Lse{{i>N^o0Nb diff --git a/update_table.py b/update_table.py new file mode 100644 index 0000000..ef6117d --- /dev/null +++ b/update_table.py @@ -0,0 +1,35 @@ +import mysql.connector + +# Database connection details from the user's file +config = { + 'user': 'A027', + 'password': 'E1CelfxqlKoj', + 'host': 'mysql.theaken.com', + 'port': 33306, + 'database': 'db_A027' +} + +try: + # Connect to the database + cnx = mysql.connector.connect(**config) + cursor = cnx.cursor() + + # SQL statement to modify the column + alter_table_query = "ALTER TABLE orders MODIFY COLUMN main_course VARCHAR(255)" + + # Execute the query + cursor.execute(alter_table_query) + + # Commit the changes + cnx.commit() + + print("資料表修改成功!") + +except mysql.connector.Error as err: + print(f"資料庫錯誤: {err}") +finally: + # Close the connection + if 'cursor' in locals() and cursor: + cursor.close() + if 'cnx' in locals() and cnx.is_connected(): + cnx.close() \ No newline at end of file diff --git a/update_table.txt b/update_table.txt new file mode 100644 index 0000000..d321d48 --- /dev/null +++ b/update_table.txt @@ -0,0 +1,14 @@ +請根據以下需求幫我產生一段 Python 程式碼,能夠連線到資料庫並在orders的資料表修改欄位main_course型態為varchar(255) + +資料庫資訊: +DB_HOST = mysql.theaken.com +DB_PORT = 33306 +DB_NAME = db_A027 +DB_USER = A027 +DB_PASSWORD = E1CelfxqlKoj + +需求: +1. 使用 mysql.connector 套件 +2. 資料表在orders 幫我修改 + - main_course : 主餐點名稱(varchar(255)) +3. 執行成功後,印出「資料表修改成功!」。