هذا الالتزام موجود في:
ahmedgamalyousef
2025-09-28 00:07:19 +03:00
الأصل 598ec506a2
التزام 75166ab287
5 ملفات معدلة مع 262 إضافات و134 حذوفات

22
Dockerfile Normal file
عرض الملف

@@ -0,0 +1,22 @@
# -----------------------------
# Dockerfile for Fruit API
# -----------------------------
# Use official Python image
FROM python:3.10-slim
# Set working directory
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy the API code
COPY fruit_api.py .
# Expose port
EXPOSE 5000
# Run the API
CMD ["python", "fruit_api.py"]

128
README.md
عرض الملف

@@ -0,0 +1,128 @@
# 🍎 Fruit API
A simple **RESTful API** built with Flask to manage fruits.
This API supports full **CRUD operations**, categorized fruit queries, and **search functionality**.
It also includes a **logging system** that saves logs locally and sends them to a remote dashboard for monitoring.
---
## 🚀 Features
- Get all fruits, get fruit by ID, add, update, and delete fruits.
- Search fruits by name or filter by category.
- Automatic **logging of every request and response**.
- Logs are stored in a local file (`fruit_api.log`) and sent to a remote dashboard (`https://deploy.ghaymah.systems/dashboard`).
- Health-check endpoint for monitoring.
- Dockerized for easy deployment.
---
## 📦 Requirements
- Python 3.9+
- Pip
Install dependencies:
```bash
pip install -r requirements.txt
```
---
## ▶️ Running Locally
1. Clone the repo:
```bash
git clone https://github.com/yourusername/fruit-api.git
cd fruit-api
```
2. Run the API:
```bash
python fruit_api.py
```
3. API starts at:
```
http://localhost:5000
```
---
## 🐳 Run with Docker
1. Build the image:
```bash
docker build -t fruit-api .
```
2. Run the container:
```bash
docker run -d -p 5000:5000 fruit-api
```
---
## 🌐 Endpoints
### Root
- `GET /` → Welcome message with available endpoints.
### Fruits
- `GET /fruits` → Get all fruits.
- `GET /fruits/<id>` → Get fruit by ID.
- `POST /fruits` → Add a new fruit.
Example body:
```json
{
"name": "Mango",
"color": "Yellow",
"price": 2.5,
"quantity": 50,
"category": "Tropical"
}
```
- `PUT /fruits/<id>` → Update an existing fruit.
- `DELETE /fruits/<id>` → Delete a fruit.
### Advanced Queries
- `GET /fruits/category/<category>` → Get fruits by category.
- `GET /fruits/search?name=<name>` → Search fruits by name.
### Health
- `GET /health` → Returns API health status.
---
## 📝 Logging
- Logs are stored locally in:
```
fruit_api.log
```
- Example log entry:
```
2025-09-27 23:01:15 [INFO] REQUEST: {'event': 'REQUEST', 'method': 'GET', 'path': '/fruits', 'ip': '127.0.0.1'}
```
## 🔍 Monitoring Script
A sample `monitor_logs.sh` is included to check log stats (200, 404, etc.) every 2 hours:
```bash
#!/bin/bash
LOG_FILE="fruit_api.log"
echo "📊 Log Report - $(date)"
grep "RESPONSE" $LOG_FILE | awk '{print $0}' | grep -oP "status': \K\d+" | sort | uniq -c
```
Run manually:
```bash
bash monitor_logs.sh
```
Or add to cron (every 2 hours):
```bash
0 */2 * * * /path/to/monitor_logs.sh >> log_report.txt
```
---

عرض الملف

@@ -0,0 +1,56 @@
2025-09-27 23:51:46,899 [INFO] WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.43.139:5000
2025-09-27 23:51:46,899 [INFO] Press CTRL+C to quit
2025-09-27 23:52:54,409 [INFO] REQUEST: method=GET path=/ args={} body=None ip=127.0.0.1
2025-09-27 23:52:54,409 [INFO] RESPONSE: method=GET path=/ status=200 ip=127.0.0.1
2025-09-27 23:52:54,410 [INFO] 127.0.0.1 - - [27/Sep/2025 23:52:54] "GET / HTTP/1.1" 200 -
2025-09-27 23:53:05,875 [INFO] REQUEST: method=GET path=/fruits/35520 args={} body=None ip=127.0.0.1
2025-09-27 23:53:05,875 [INFO] RESPONSE: method=GET path=/fruits/35520 status=404 ip=127.0.0.1
2025-09-27 23:53:05,875 [INFO] 127.0.0.1 - - [27/Sep/2025 23:53:05] "GET /fruits/35520 HTTP/1.1" 404 -
2025-09-27 23:53:10,015 [INFO] REQUEST: method=GET path=/fruits/2 args={} body=None ip=127.0.0.1
2025-09-27 23:53:10,015 [INFO] RESPONSE: method=GET path=/fruits/2 status=200 ip=127.0.0.1
2025-09-27 23:53:10,016 [INFO] 127.0.0.1 - - [27/Sep/2025 23:53:10] "GET /fruits/2 HTTP/1.1" 200 -
2025-09-27 23:53:13,339 [INFO] REQUEST: method=GET path=/fruits/1 args={} body=None ip=127.0.0.1
2025-09-27 23:53:13,339 [INFO] RESPONSE: method=GET path=/fruits/1 status=200 ip=127.0.0.1
2025-09-27 23:53:13,340 [INFO] 127.0.0.1 - - [27/Sep/2025 23:53:13] "GET /fruits/1 HTTP/1.1" 200 -
2025-09-27 23:53:44,634 [INFO] REQUEST: method=GET path=/fruits/88 args={} body=None ip=127.0.0.1
2025-09-27 23:53:44,635 [INFO] RESPONSE: method=GET path=/fruits/88 status=404 ip=127.0.0.1
2025-09-27 23:53:44,636 [INFO] 127.0.0.1 - - [27/Sep/2025 23:53:44] "GET /fruits/88 HTTP/1.1" 404 -
2025-09-27 23:53:55,207 [INFO] REQUEST: method=GET path=/fruits/1 args={} body=None ip=127.0.0.1
2025-09-27 23:53:55,207 [INFO] RESPONSE: method=GET path=/fruits/1 status=200 ip=127.0.0.1
2025-09-27 23:53:55,208 [INFO] 127.0.0.1 - - [27/Sep/2025 23:53:55] "GET /fruits/1 HTTP/1.1" 200 -
2025-09-27 23:54:00,771 [INFO] REQUEST: method=GET path=/fruits/3 args={} body=None ip=127.0.0.1
2025-09-27 23:54:00,772 [INFO] RESPONSE: method=GET path=/fruits/3 status=200 ip=127.0.0.1
2025-09-27 23:54:00,772 [INFO] 127.0.0.1 - - [27/Sep/2025 23:54:00] "GET /fruits/3 HTTP/1.1" 200 -
2025-09-27 23:55:31,303 [INFO] REQUEST: method=GET path=/ args={} body=None ip=127.0.0.1
2025-09-27 23:55:31,303 [INFO] RESPONSE: method=GET path=/ status=200 ip=127.0.0.1
2025-09-27 23:55:31,304 [INFO] 127.0.0.1 - - [27/Sep/2025 23:55:31] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:34,297 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:34,297 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:34,297 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:34] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:34,973 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:34,973 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:34,973 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:34] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:35,569 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:35,569 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:35,569 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:35] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:35,736 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:35,736 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:35,736 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:35] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:35,929 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:35,929 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:35,929 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:35] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:36,112 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:36,113 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:36,113 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:36] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:36,416 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:36,416 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:36,417 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:36] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:36,562 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:36,562 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:36,562 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:36] "GET / HTTP/1.1" 200 -
2025-09-27 23:55:36,704 [INFO] REQUEST: method=GET path=/ args={} body=None ip=192.168.43.139
2025-09-27 23:55:36,704 [INFO] RESPONSE: method=GET path=/ status=200 ip=192.168.43.139
2025-09-27 23:55:36,704 [INFO] 192.168.43.139 - - [27/Sep/2025 23:55:36] "GET / HTTP/1.1" 200 -

عرض الملف

@@ -1,9 +1,21 @@
# fruit_api.py
from flask import Flask, jsonify, request
from datetime import datetime
import logging
app = Flask(__name__)
# -----------------------
# Logging Configuration
# -----------------------
LOG_FILE = "fruit_api.log"
logging.basicConfig(
filename=LOG_FILE,
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
)
# In-memory database
fruits = [
{"id": 1, "name": "Apple", "color": "Red", "price": 1.50, "quantity": 100, "category": "Tropical"},
@@ -11,33 +23,36 @@ fruits = [
{"id": 3, "name": "Orange", "color": "Orange", "price": 1.20, "quantity": 80, "category": "Citrus"}
]
# 1. GET / - Home endpoint
# -----------------------
# Logging Hooks
# -----------------------
@app.before_request
def log_request():
logging.info(
f"REQUEST: method={request.method} path={request.path} "
f"args={dict(request.args)} body={request.get_json(silent=True)} "
f"ip={request.remote_addr}"
)
@app.after_request
def log_response(response):
logging.info(
f"RESPONSE: method={request.method} path={request.path} "
f"status={response.status_code} ip={request.remote_addr}"
)
return response
# -----------------------
# API Endpoints
# -----------------------
@app.route('/')
def home():
return jsonify({
"message": "Fruit Store API",
"version": "1.0",
"endpoints": [
"GET /fruits - Get all fruits",
"GET /fruits/<id> - Get fruit by ID",
"POST /fruits - Add new fruit",
"PUT /fruits/<id> - Update fruit",
"DELETE /fruits/<id> - Delete fruit",
"GET /fruits/category/<category> - Get fruits by category",
"GET /fruits/search?name=<name> - Search fruits by name"
]
})
return jsonify({"message": "Fruit Store API", "version": "1.0"})
# 2. GET /fruits - Get all fruits
@app.route('/fruits', methods=['GET'])
def get_all_fruits():
return jsonify({
"fruits": fruits,
"total": len(fruits),
"timestamp": datetime.now().isoformat()
})
return jsonify({"fruits": fruits, "total": len(fruits)})
# 3. GET /fruits/<id> - Get specific fruit
@app.route('/fruits/<int:fruit_id>', methods=['GET'])
def get_fruit(fruit_id):
fruit = next((f for f in fruits if f['id'] == fruit_id), None)
@@ -45,17 +60,13 @@ def get_fruit(fruit_id):
return jsonify({"fruit": fruit})
return jsonify({"error": "Fruit not found"}), 404
# 4. POST /fruits - Create new fruit
@app.route('/fruits', methods=['POST'])
def create_fruit():
data = request.get_json()
if not data or 'name' not in data:
return jsonify({"error": "Name is required"}), 400
# Generate new ID
new_id = max([f['id'] for f in fruits]) + 1 if fruits else 1
new_fruit = {
"id": new_id,
"name": data['name'],
@@ -65,99 +76,16 @@ def create_fruit():
"category": data.get('category', 'General'),
"created_at": datetime.now().isoformat()
}
fruits.append(new_fruit)
return jsonify({
"message": "Fruit created successfully",
"fruit": new_fruit
}), 201
return jsonify({"message": "Fruit created successfully", "fruit": new_fruit}), 201
# 5. PUT /fruits/<id> - Update fruit
@app.route('/fruits/<int:fruit_id>', methods=['PUT'])
def update_fruit(fruit_id):
fruit = next((f for f in fruits if f['id'] == fruit_id), None)
if not fruit:
return jsonify({"error": "Fruit not found"}), 404
data = request.get_json()
# Update allowed fields
if 'name' in data:
fruit['name'] = data['name']
if 'color' in data:
fruit['color'] = data['color']
if 'price' in data:
fruit['price'] = data['price']
if 'quantity' in data:
fruit['quantity'] = data['quantity']
if 'category' in data:
fruit['category'] = data['category']
fruit['updated_at'] = datetime.now().isoformat()
return jsonify({
"message": "Fruit updated successfully",
"fruit": fruit
})
# 6. DELETE /fruits/<id> - Delete fruit
@app.route('/fruits/<int:fruit_id>', methods=['DELETE'])
def delete_fruit(fruit_id):
global fruits
fruit = next((f for f in fruits if f['id'] == fruit_id), None)
if not fruit:
return jsonify({"error": "Fruit not found"}), 404
fruits = [f for f in fruits if f['id'] != fruit_id]
return jsonify({
"message": "Fruit deleted successfully",
"deleted_fruit": fruit
})
# 7. GET /fruits/category/<category> - Get fruits by category
@app.route('/fruits/category/<category>', methods=['GET'])
def get_fruits_by_category(category):
category_fruits = [f for f in fruits if f['category'].lower() == category.lower()]
return jsonify({
"category": category,
"fruits": category_fruits,
"count": len(category_fruits)
})
# Search endpoint
@app.route('/fruits/search', methods=['GET'])
def search_fruits():
name_query = request.args.get('name', '').lower()
if not name_query:
return jsonify({"error": "Name parameter is required"}), 400
matching_fruits = [f for f in fruits if name_query in f['name'].lower()]
return jsonify({
"search_term": name_query,
"results": matching_fruits,
"count": len(matching_fruits)
})
# Health check endpoint
@app.route('/health', methods=['GET'])
def health_check():
return jsonify({
"status": "healthy",
"service": "fruit-api",
"timestamp": datetime.now().isoformat()
})
return jsonify({"status": "healthy", "service": "fruit-api"})
# -----------------------
# Run API
# -----------------------
if __name__ == '__main__':
print("Fruit API starting on http://localhost:5000")
print("Available endpoints:")
print("1. GET / - Home")
print("2. GET /fruits - All fruits")
print("3. GET /fruits/<id> - Fruit by ID")
print("4. POST /fruits - Create fruit")
print("5. PUT /fruits/<id> - Update fruit")
print("6. DELETE /fruits/<id> - Delete fruit")
print("7. GET /fruits/category/<category> - Fruits by category")
print("8. GET /fruits/search?name=X - Search fruits")
print("9. GET /health - Health check")
app.run(debug=True, host='0.0.0.0', port=5000)
app.run(debug=False, host='0.0.0.0', port=5000)

عرض الملف

@@ -7,26 +7,20 @@ if [[ ! -f "$LOG_FILE" ]]; then
exit 1
fi
echo "📊 Fruit API Log Report - $(date)"
echo "-----------------------------------------"
echo "📊 Fruit API Logs Report (last 2 hours) - $(date)"
echo "--------------------------------------------------"
# Time format for the last hour
last_hour=$(date --date='1 hour ago' +"%Y-%m-%d %H")
# Get timestamps from last 2 hours
time_filter=$(date --date='2 hours ago' +"%Y-%m-%d %H")
echo "🔹 Logs from last hour ($last_hour:*):"
echo ""
# Show all logs from last 2 hours
echo "🔹 Raw logs (last 2 hours):"
grep "$time_filter" "$LOG_FILE"
# Status codes
echo " Status codes count:"
grep "$last_hour" "$LOG_FILE" | grep "RESPONSE" | awk '{print $6}' | cut -d'=' -f2 | sort | uniq -c | sort -nr
echo ""
echo "--------------------------------------------------"
echo "🔹 Status Codes Summary:"
grep "$time_filter" "$LOG_FILE" | grep "RESPONSE" | awk '{print $NF}' | sort | uniq -c | sort -nr
# Top IPs
echo "🌍 Top Client IPs:"
grep "$last_hour" "$LOG_FILE" | grep "REQUEST" | awk '{for(i=1;i<=NF;i++){if($i ~ /^ip=/){print $i}}}' | cut -d'=' -f2 | sort | uniq -c | sort -nr
echo ""
# Endpoints
echo "📂 Endpoints accessed:"
grep "$last_hour" "$LOG_FILE" | grep "REQUEST" | awk '{for(i=1;i<=NF;i++){if($i ~ /^path=/){print $i}}}' | cut -d'=' -f2 | sort | uniq -c | sort -nr
echo "-----------------------------------------"
echo "--------------------------------------------------"
echo "🔹 Users & IPs:"
grep "$time_filter" "$LOG_FILE" | grep "REQUEST" | awk '{for(i=1;i<=NF;i++){if($i ~ /^ip=/){print $i}}}' | sort | uniq -c | sort -nr