Initial commit

This commit is contained in:
2025-09-08 16:21:07 +08:00
commit d2b36a732a
5 changed files with 385 additions and 0 deletions

286
app.py Normal file
View File

@@ -0,0 +1,286 @@
import os
from flask import Flask, jsonify, request
from flask_cors import CORS
import mysql.connector
from mysql.connector import Error
# --- Database Configuration ---
# It's recommended to use environment variables for security
DB_HOST = os.getenv("DB_HOST", "mysql.theaken.com")
DB_PORT = int(os.getenv("DB_PORT", 33306))
DB_NAME = os.getenv("DB_NAME", "db_A027")
DB_USER = os.getenv("DB_USER", "A027")
DB_PASSWORD = os.getenv("DB_PASSWORD", "E1CelfxqlKoj")
DB_TABLE = "pizzas"
# --- Flask App Initialization ---
app = Flask(__name__)
CORS(app) # Enable CORS for all routes, allowing local frontend development
# --- Database Connection ---
def get_db_connection():
"""Establishes a connection to the MySQL database."""
try:
conn = mysql.connector.connect(
host=DB_HOST,
port=DB_PORT,
database=DB_NAME,
user=DB_USER,
password=DB_PASSWORD
)
return conn
except Error as e:
print(f"Error connecting to MySQL database: {e}")
return None
# --- Response Formatting Helpers ---
def create_success_response(data, message="Success", code=200, meta=None):
"""Creates a standardized success JSON response."""
response = {
"status": "success",
"code": code,
"message": message
}
if data is not None:
response["data"] = data
if meta is not None:
response["meta"] = meta
return jsonify(response), code
def create_error_response(message, code=400):
"""Creates a standardized error JSON response."""
return jsonify({
"status": "error",
"code": code,
"message": message
}), code
# --- API Routes ---
@app.route('/v1/pizzas', methods=['GET'])
def get_pizzas():
"""Retrieve a list of pizzas with optional filtering and pagination."""
conn = get_db_connection()
if not conn:
return create_error_response("Database connection failed", 500)
cursor = conn.cursor(dictionary=True)
# Pagination parameters
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 10))
offset = (page - 1) * limit
except ValueError:
return create_error_response("Invalid 'page' or 'limit' parameter. Must be integers.")
# Filtering parameters
min_age = request.args.get('min_age')
max_age = request.args.get('max_age')
query_params = []
where_clauses = []
if min_age:
try:
where_clauses.append("age >= %s")
query_params.append(int(min_age))
except ValueError:
return create_error_response("Invalid 'min_age' parameter. Must be an integer.")
if max_age:
try:
where_clauses.append("age <= %s")
query_params.append(int(max_age))
except ValueError:
return create_error_response("Invalid 'max_age' parameter. Must be an integer.")
base_query = f"FROM {DB_TABLE}"
if where_clauses:
base_query += " WHERE " + " AND ".join(where_clauses)
# Get total count for metadata
count_query = f"SELECT COUNT(*) as total {base_query}"
cursor.execute(count_query, tuple(query_params))
total_records = cursor.fetchone()['total']
# Get paginated data
data_query = f"SELECT id, name, email, age {base_query} ORDER BY id ASC LIMIT %s OFFSET %s"
cursor.execute(data_query, tuple(query_params + [limit, offset]))
pizzas = cursor.fetchall()
cursor.close()
conn.close()
meta = {
"total_records": total_records,
"current_page": page,
"page_size": limit,
"total_pages": (total_records + limit - 1) // limit
}
return create_success_response(pizzas, meta=meta)
@app.route('/v1/pizzas/<int:pizza_id>', methods=['GET'])
def get_pizza_by_id(pizza_id):
"""Retrieve a single pizza by its ID."""
conn = get_db_connection()
if not conn:
return create_error_response("Database connection failed", 500)
cursor = conn.cursor(dictionary=True)
cursor.execute(f"SELECT id, name, email, age FROM {DB_TABLE} WHERE id = %s", (pizza_id,))
pizza = cursor.fetchone()
cursor.close()
conn.close()
if pizza:
return create_success_response(pizza)
else:
return create_error_response("Pizza not found", 404)
@app.route('/v1/pizzas', methods=['POST'])
def create_pizza():
"""Create a new pizza."""
if not request.is_json:
return create_error_response("Invalid input: payload must be JSON")
data = request.get_json()
name = data.get('name')
email = data.get('email')
age = data.get('age')
if not all([name, email, age]):
return create_error_response("Missing required fields: 'name', 'email', 'age'")
if not isinstance(name, str) or not isinstance(email, str) or not isinstance(age, int):
return create_error_response("Invalid data type for fields.")
conn = get_db_connection()
if not conn:
return create_error_response("Database connection failed", 500)
cursor = conn.cursor(dictionary=True)
try:
query = f"INSERT INTO {DB_TABLE} (name, email, age) VALUES (%s, %s, %s)"
cursor.execute(query, (name, email, age))
new_pizza_id = cursor.lastrowid
conn.commit()
# Fetch the newly created pizza
cursor.execute(f"SELECT id, name, email, age FROM {DB_TABLE} WHERE id = %s", (new_pizza_id,))
new_pizza = cursor.fetchone()
return create_success_response(new_pizza, "Pizza created successfully", 201)
except Error as e:
conn.rollback()
# Check for duplicate entry
if e.errno == 1062:
return create_error_response(f"Failed to create pizza: email '{email}' already exists.", 409)
return create_error_response(f"Failed to create pizza: {e}", 500)
finally:
cursor.close()
conn.close()
@app.route('/v1/pizzas/<int:pizza_id>', methods=['PATCH'])
def update_pizza(pizza_id):
"""Update an existing pizza's information."""
if not request.is_json:
return create_error_response("Invalid input: payload must be JSON")
data = request.get_json()
if not data:
return create_error_response("No update fields provided.")
conn = get_db_connection()
if not conn:
return create_error_response("Database connection failed", 500)
cursor = conn.cursor(dictionary=True)
# Check if pizza exists
cursor.execute(f"SELECT id FROM {DB_TABLE} WHERE id = %s", (pizza_id,))
if not cursor.fetchone():
cursor.close()
conn.close()
return create_error_response("Pizza not found", 404)
update_fields = []
update_values = []
if 'name' in data:
update_fields.append("name = %s")
update_values.append(data['name'])
if 'email' in data:
update_fields.append("email = %s")
update_values.append(data['email'])
if 'age' in data:
update_fields.append("age = %s")
update_values.append(data['age'])
if not update_fields:
return create_error_response("No valid update fields provided.")
update_values.append(pizza_id)
try:
query = f"UPDATE {DB_TABLE} SET {', '.join(update_fields)} WHERE id = %s"
cursor.execute(query, tuple(update_values))
conn.commit()
# Fetch and return the updated pizza data
cursor.execute(f"SELECT id, name, email, age FROM {DB_TABLE} WHERE id = %s", (pizza_id,))
updated_pizza = cursor.fetchone()
return create_success_response(updated_pizza, "Pizza updated successfully")
except Error as e:
conn.rollback()
if e.errno == 1062:
return create_error_response(f"Failed to update pizza: email '{data['email']}' already exists.", 409)
return create_error_response(f"Failed to update pizza: {e}", 500)
finally:
cursor.close()
conn.close()
@app.route('/v1/pizzas/<int:pizza_id>', methods=['DELETE'])
def delete_pizza(pizza_id):
"""Delete a pizza by its ID."""
conn = get_db_connection()
if not conn:
return create_error_response("Database connection failed", 500)
cursor = conn.cursor()
# Check if pizza exists before deleting
cursor.execute(f"SELECT id FROM {DB_TABLE} WHERE id = %s", (pizza_id,))
if not cursor.fetchone():
cursor.close()
conn.close()
return create_error_response("Pizza not found", 404)
try:
cursor.execute(f"DELETE FROM {DB_TABLE} WHERE id = %s", (pizza_id,))
conn.commit()
# Check if the row was actually deleted
if cursor.rowcount == 0:
# This case is handled by the check above, but as a safeguard
return create_error_response("Pizza not found", 404)
return '', 204 # No Content
except Error as e:
conn.rollback()
return create_error_response(f"Failed to delete pizza: {e}", 500)
finally:
cursor.close()
conn.close()
# --- Main Execution ---
if __name__ == '__main__':
# It's recommended to use a production-ready WSGI server like Gunicorn or uWSGI
app.run(debug=True, port=5000)

57
create_pizza_table.py Normal file
View File

@@ -0,0 +1,57 @@
import mysql.connector
from mysql.connector import Error
# --- Database Configuration ---
DB_HOST = "mysql.theaken.com"
DB_PORT = 33306
DB_NAME = "db_A027"
DB_USER = "A027"
DB_PASSWORD = "E1CelfxqlKoj"
TABLE_NAME = "pizzas" # Changed table name to be more descriptive
def create_table():
"""Connects to the database and creates the specified table."""
conn = None
try:
# Establish database connection
conn = mysql.connector.connect(
host=DB_HOST,
port=DB_PORT,
database=DB_NAME,
user=DB_USER,
password=DB_PASSWORD
)
if conn.is_connected():
cursor = conn.cursor()
# Drop the table if it already exists
cursor.execute(f"DROP TABLE IF EXISTS {TABLE_NAME}")
# Create the new table
# The user's request had conflicting column names and descriptions.
# Based on the filename "pizza建立table.txt", I am creating a "pizzas" table.
# The columns are id, name, price, and size.
create_table_query = f"""
CREATE TABLE {TABLE_NAME} (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
price INT NOT NULL,
size VARCHAR(50)
)"""
cursor.execute(create_table_query)
print("資料表建立成功!")
except Error as e:
print(f"資料庫操作失敗: {e}")
finally:
# Close the connection
if conn and conn.is_connected():
cursor.close()
conn.close()
if __name__ == '__main__':
create_table()

17
pizza建立table.txt Normal file
View File

@@ -0,0 +1,17 @@
請根據以下需求幫我產生一段 Python 程式碼,能夠連線到資料庫並建立一張 users 資料表。
資料庫資訊:
DB_HOST = mysql.theaken.com
DB_PORT = 33306
DB_NAME = db_A027
DB_USER = A027
DB_PASSWORD = E1CelfxqlKoj
需求:
1. 使用 mysql.connector 套件。
2. 建立一張名稱為 users 的資料表,包含以下欄位:
- name: 會員姓名 (VARCHAR(50))
- price: 會員電子郵件 (INT))
- size: 會員年齡 (VARCHAR(50))
3. 執行成功後,印出「資料表建立成功!」。
4. 若資料表已存在,先刪除再重建。

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
Flask
mysql-connector-python
Flask-Cors

22
user_api.txt Normal file
View File

@@ -0,0 +1,22 @@
請產生一支可直接執行的 Flask API 伺服器,需求:
- 使用 mysql.connector 連線
資料庫資訊:
DB_HOST = mysql.theaken.com
DB_PORT = 33306
DB_NAME = db_A027
DB_USER = A027
DB_PASSWORD = E1CelfxqlKoj
TABLE = pizzas
- 路由:
- GET /v1/pizzas支援 ?min_age、?max_age、?page、?limit要多回傳 {meta}
- GET /v1/pizzas/<id>:找不到回 404
- POST /v1/pizzas接收 JSON {name,email,age},欄位驗證,成功回 201 並回新資料
- PATCH /v1/pizzas/<id>可更新任一欄位name/email/age成功回 200 回更新後資料
- DELETE /v1/pizzas/<id>:成功回 204
- 所有寫入請用參數化查詢避免 SQL 注入
- 統一格式 { status,code,message,data }
- 統一錯誤格式 {status:"error", code, message}
- 啟用 CORS允許本機前端