Updated Files
هذا الالتزام موجود في:
22
Dockerfile
Normal file
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
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] [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||||
|
* 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] [33mPress CTRL+C to quit[0m
|
||||||
|
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] "[33mGET /fruits/35520 HTTP/1.1[0m" 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] "[33mGET /fruits/88 HTTP/1.1[0m" 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 -
|
||||||
|
156
fruit_api.py
156
fruit_api.py
@@ -1,9 +1,21 @@
|
|||||||
# fruit_api.py
|
# fruit_api.py
|
||||||
from flask import Flask, jsonify, request
|
from flask import Flask, jsonify, request
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
app = Flask(__name__)
|
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
|
# In-memory database
|
||||||
fruits = [
|
fruits = [
|
||||||
{"id": 1, "name": "Apple", "color": "Red", "price": 1.50, "quantity": 100, "category": "Tropical"},
|
{"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"}
|
{"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('/')
|
@app.route('/')
|
||||||
def home():
|
def home():
|
||||||
return jsonify({
|
return jsonify({"message": "Fruit Store API", "version": "1.0"})
|
||||||
"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"
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
# 2. GET /fruits - Get all fruits
|
|
||||||
@app.route('/fruits', methods=['GET'])
|
@app.route('/fruits', methods=['GET'])
|
||||||
def get_all_fruits():
|
def get_all_fruits():
|
||||||
return jsonify({
|
return jsonify({"fruits": fruits, "total": len(fruits)})
|
||||||
"fruits": fruits,
|
|
||||||
"total": len(fruits),
|
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
# 3. GET /fruits/<id> - Get specific fruit
|
|
||||||
@app.route('/fruits/<int:fruit_id>', methods=['GET'])
|
@app.route('/fruits/<int:fruit_id>', methods=['GET'])
|
||||||
def get_fruit(fruit_id):
|
def get_fruit(fruit_id):
|
||||||
fruit = next((f for f in fruits if f['id'] == fruit_id), None)
|
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({"fruit": fruit})
|
||||||
return jsonify({"error": "Fruit not found"}), 404
|
return jsonify({"error": "Fruit not found"}), 404
|
||||||
|
|
||||||
# 4. POST /fruits - Create new fruit
|
|
||||||
@app.route('/fruits', methods=['POST'])
|
@app.route('/fruits', methods=['POST'])
|
||||||
def create_fruit():
|
def create_fruit():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
|
|
||||||
if not data or 'name' not in data:
|
if not data or 'name' not in data:
|
||||||
return jsonify({"error": "Name is required"}), 400
|
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_id = max([f['id'] for f in fruits]) + 1 if fruits else 1
|
||||||
|
|
||||||
new_fruit = {
|
new_fruit = {
|
||||||
"id": new_id,
|
"id": new_id,
|
||||||
"name": data['name'],
|
"name": data['name'],
|
||||||
@@ -65,99 +76,16 @@ def create_fruit():
|
|||||||
"category": data.get('category', 'General'),
|
"category": data.get('category', 'General'),
|
||||||
"created_at": datetime.now().isoformat()
|
"created_at": datetime.now().isoformat()
|
||||||
}
|
}
|
||||||
|
|
||||||
fruits.append(new_fruit)
|
fruits.append(new_fruit)
|
||||||
return jsonify({
|
return jsonify({"message": "Fruit created successfully", "fruit": new_fruit}), 201
|
||||||
"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'])
|
@app.route('/health', methods=['GET'])
|
||||||
def health_check():
|
def health_check():
|
||||||
return jsonify({
|
return jsonify({"status": "healthy", "service": "fruit-api"})
|
||||||
"status": "healthy",
|
|
||||||
"service": "fruit-api",
|
|
||||||
"timestamp": datetime.now().isoformat()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
# -----------------------
|
||||||
|
# Run API
|
||||||
|
# -----------------------
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Fruit API starting on http://localhost:5000")
|
print("Fruit API starting on http://localhost:5000")
|
||||||
print("Available endpoints:")
|
app.run(debug=False, host='0.0.0.0', port=5000)
|
||||||
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)
|
|
||||||
|
@@ -7,26 +7,20 @@ if [[ ! -f "$LOG_FILE" ]]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "📊 Fruit API Log Report - $(date)"
|
echo "📊 Fruit API Logs Report (last 2 hours) - $(date)"
|
||||||
echo "-----------------------------------------"
|
echo "--------------------------------------------------"
|
||||||
|
|
||||||
# Time format for the last hour
|
# Get timestamps from last 2 hours
|
||||||
last_hour=$(date --date='1 hour ago' +"%Y-%m-%d %H")
|
time_filter=$(date --date='2 hours ago' +"%Y-%m-%d %H")
|
||||||
|
|
||||||
echo "🔹 Logs from last hour ($last_hour:*):"
|
# Show all logs from last 2 hours
|
||||||
echo ""
|
echo "🔹 Raw logs (last 2 hours):"
|
||||||
|
grep "$time_filter" "$LOG_FILE"
|
||||||
|
|
||||||
# Status codes
|
echo "--------------------------------------------------"
|
||||||
echo "✅ Status codes count:"
|
echo "🔹 Status Codes Summary:"
|
||||||
grep "$last_hour" "$LOG_FILE" | grep "RESPONSE" | awk '{print $6}' | cut -d'=' -f2 | sort | uniq -c | sort -nr
|
grep "$time_filter" "$LOG_FILE" | grep "RESPONSE" | awk '{print $NF}' | sort | uniq -c | sort -nr
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Top IPs
|
echo "--------------------------------------------------"
|
||||||
echo "🌍 Top Client IPs:"
|
echo "🔹 Users & 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
|
grep "$time_filter" "$LOG_FILE" | grep "REQUEST" | awk '{for(i=1;i<=NF;i++){if($i ~ /^ip=/){print $i}}}' | 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 "-----------------------------------------"
|
|
||||||
|
المرجع في مشكلة جديدة
حظر مستخدم