From 2cfed42886cab4d1174484a305e7522cf86c08ba Mon Sep 17 00:00:00 2001 From: weijing Date: Mon, 15 Sep 2025 17:36:13 +0800 Subject: [PATCH] Initial commit --- api_server.py | 533 +++++++++++++++++++++++++++++++++++++++++++++++ creat_api.txt | 44 ++++ creat_table.txt | 29 +++ create_tables.py | 70 +++++++ 4 files changed, 676 insertions(+) create mode 100644 api_server.py create mode 100644 creat_api.txt create mode 100644 creat_table.txt create mode 100644 create_tables.py diff --git a/api_server.py b/api_server.py new file mode 100644 index 0000000..f109488 --- /dev/null +++ b/api_server.py @@ -0,0 +1,533 @@ + +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 + +# --- Helper Functions --- +def get_db_connection(): + """Get a connection from the pool.""" + try: + return db_pool.get_connection() + except mysql.connector.Error as err: + print(f"Error getting connection from pool: {err}") + return None + +def make_response(status, code, message, data=None): + """Standardized response format.""" + response = { + "status": status, + "code": code, + "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.") + +@app.errorhandler(500) +def internal_server_error(error): + return make_response("error", 500, "An internal server error occurred.") + +# --- Employee 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): + 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 = [] + 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.") + + cursor.execute("SELECT * FROM employee WHERE id = %s", (emp_id,)) + updated_employee = cursor.fetchone() + return make_response("success", 200, "Employee updated successfully.", updated_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=['DELETE']) +def delete_employee(emp_id): + conn = get_db_connection() + if not conn: + return make_response("error", 503, "Database service unavailable.") + cursor = conn.cursor() + + 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.") + 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)) + finally: + cursor.close() + 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.") + + conn = get_db_connection() + if not conn: + return make_response("error", 503, "Database service unavailable.") + cursor = conn.cursor(dictionary=True) + + 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.execute(sql, params) + new_item_id = cursor.lastrowid + conn.commit() + + 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) + except mysql.connector.Error as err: + conn.rollback() + return make_response("error", 500, str(err)) + finally: + cursor.close() + 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.") + + 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" + + try: + cursor.execute(query, tuple(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) + except mysql.connector.Error as err: + conn.rollback() + return make_response("error", 500, str(err)) + finally: + cursor.close() + 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() + + try: + 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.") + 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)) + finally: + cursor.close() + 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) diff --git a/creat_api.txt b/creat_api.txt new file mode 100644 index 0000000..537113d --- /dev/null +++ b/creat_api.txt @@ -0,0 +1,44 @@ +請產生一支可直接執行的 Flask API 伺服器,需求: +- 使用 mysql.connector 連線 + +資料庫資訊: +DB_HOST = mysql.theaken.com +DB_PORT = 33306 +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 +- 路由: +- 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 + + +TABLE = menu_items +- 路由: +- GET /v1/menu_items:支援 ?Id、?main_course、?side_dish、?addon、?is_active,要多回傳 {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 回更新後資料 +- DELETE /v1/menu_items/:成功回 204 + + + +- 所有寫入請用參數化查詢避免 SQL 注入 + +- 統一格式 { status,code,message,data } + +- 統一錯誤格式 {status:"error", code, message} + +- 啟用 CORS(允許本機前端) \ No newline at end of file diff --git a/creat_table.txt b/creat_table.txt new file mode 100644 index 0000000..a7b47ef --- /dev/null +++ b/creat_table.txt @@ -0,0 +1,29 @@ +請根據以下需求幫我產生一段 Python 程式碼,能夠連線到資料庫並建立三張employee、 menu_items、employee_votes 資料表。 + +資料庫資訊: +DB_HOST = mysql.theaken.com +DB_PORT = 33306 +DB_NAME = db_A027 +DB_USER = A027 +DB_PASSWORD = E1CelfxqlKoj + +需求: +1. 使用 mysql.connector 套件。 +2. 建立三張名稱為employee、 menu_items、employee_votes 的資料表,包含以下欄位: + 2.1 TABLE名稱 employee(員工檔) + -id : 員工工號(NVARCHAR) + -name : 員工姓名(NVARCHAR) + + 2.2 menu_items(菜色基本資料) + -id : NUMBER(自動編號) + -main_course : 主餐(NVARCHAR) + -side_dish : 副餐(NVARCHAR) + -addon : 湯品/甜點(NVARCHAR) + -menu_date :當天菜色(DATE) + + 2.3 employee_votes(餐點訂購檔) + -id : NUMBER(自動編號) + -emp_id : 工號(NVARCHAR) + -order_date : 訂餐日期(DATE) + -menu_item_id: 投票選擇的餐點(NUMBER) + -order_qty : 訂餐份數(NUMBER) diff --git a/create_tables.py b/create_tables.py new file mode 100644 index 0000000..39a0928 --- /dev/null +++ b/create_tables.py @@ -0,0 +1,70 @@ + +import mysql.connector + +# Database connection details +DB_HOST = "mysql.theaken.com" +DB_PORT = 33306 +DB_NAME = "db_A027" +DB_USER = "A027" +DB_PASSWORD = "E1CelfxqlKoj" + +try: + # Establish the connection + conn = mysql.connector.connect( + host=DB_HOST, + port=DB_PORT, + database=DB_NAME, + user=DB_USER, + password=DB_PASSWORD + ) + cursor = conn.cursor() + + # SQL statements to create tables + create_employee_table = """ + CREATE TABLE IF NOT EXISTS employee ( + id NVARCHAR(255) PRIMARY KEY, + name NVARCHAR(255) + ) + """ + + create_menu_items_table = """ + CREATE TABLE IF NOT EXISTS menu_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + main_course NVARCHAR(255), + side_dish NVARCHAR(255), + addon NVARCHAR(255), + menu_date DATE + ) + """ + + create_employee_votes_table = """ + CREATE TABLE IF NOT EXISTS employee_votes ( + id INT AUTO_INCREMENT PRIMARY KEY, + emp_id NVARCHAR(255), + order_date DATE, + menu_item_id INT, + order_qty INT, + FOREIGN KEY (emp_id) REFERENCES employee(id), + FOREIGN KEY (menu_item_id) REFERENCES menu_items(id) + ) + """ + + # Execute the create table statements + cursor.execute(create_employee_table) + print("Table 'employee' created successfully.") + + cursor.execute(create_menu_items_table) + print("Table 'menu_items' created successfully.") + + cursor.execute(create_employee_votes_table) + print("Table 'employee_votes' created successfully.") + +except mysql.connector.Error as err: + print(f"Error: {err}") + +finally: + # Close the connection + if 'conn' in locals() and conn.is_connected(): + cursor.close() + conn.close() + print("MySQL connection is closed.")