Compare commits
10 الالتزامات
375e2bceea
...
884bebed09
المؤلف | SHA1 | التاريخ | |
---|---|---|---|
884bebed09 | |||
7b1edc7deb | |||
814f32ded3 | |||
386932b2c8 | |||
5529f66cb5 | |||
d8f12591aa | |||
369ebf8187 | |||
1b7e574cf4 | |||
c480016be2 | |||
8e3e93a2ea |
5
.gitignore
مباع
5
.gitignore
مباع
@@ -8,3 +8,8 @@ wheels/
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
# Log files (generated by the application)
|
||||
api.log
|
||||
alerts.log
|
||||
test_results.log
|
||||
|
4
app.py
4
app.py
@@ -1,6 +1,7 @@
|
||||
from flask import Flask, request, jsonify
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from swagger import setup_swagger
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
@@ -11,7 +12,6 @@ logging.basicConfig(
|
||||
format='%(asctime)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
# Simple in-memory database (just a list)
|
||||
products = [
|
||||
{"id": 1, "name": "Laptop", "price": 5000, "quantity": 10},
|
||||
{"id": 2, "name": "Mouse", "price": 150, "quantity": 50}
|
||||
@@ -143,5 +143,7 @@ def get_orders():
|
||||
logging.info("GET /orders - Fetching all orders")
|
||||
return jsonify({"orders": orders})
|
||||
|
||||
setup_swagger(app)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=5000)
|
||||
|
105
monitor.sh
Executable file
105
monitor.sh
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
LOG_FILE="api.log"
|
||||
GHAYMAH_URL="YOUR_GHAYMAH_ENDPOINT_HERE" # Will be provided later
|
||||
|
||||
echo "Starting log monitor..."
|
||||
echo "Watching file: $LOG_FILE"
|
||||
|
||||
# Check if log file exists
|
||||
if [ ! -f "$LOG_FILE" ]; then
|
||||
echo "Warning: $LOG_FILE not found. Creating it..."
|
||||
touch "$LOG_FILE"
|
||||
fi
|
||||
|
||||
# Function to get server info
|
||||
get_server_info() {
|
||||
# Get IP address - try different methods
|
||||
if command -v hostname &> /dev/null; then
|
||||
IP=$(hostname -I | awk '{print $1}')
|
||||
elif command -v ip &> /dev/null; then
|
||||
IP=$(ip addr show | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d'/' -f1 | head -n1)
|
||||
else
|
||||
IP="127.0.0.1"
|
||||
fi
|
||||
|
||||
# Get CPU usage
|
||||
if command -v top &> /dev/null; then
|
||||
CPU=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
|
||||
else
|
||||
CPU="N/A"
|
||||
fi
|
||||
|
||||
# Get available RAM
|
||||
if command -v free &> /dev/null; then
|
||||
RAM=$(free -h | grep Mem | awk '{print $7}')
|
||||
else
|
||||
RAM="N/A"
|
||||
fi
|
||||
|
||||
# Get available disk space
|
||||
if command -v df &> /dev/null; then
|
||||
DISK=$(df -h / | tail -1 | awk '{print $4}')
|
||||
else
|
||||
DISK="N/A"
|
||||
fi
|
||||
|
||||
echo "$IP|$CPU|$RAM|$DISK"
|
||||
}
|
||||
|
||||
# Function to send alert
|
||||
send_alert() {
|
||||
ERROR_MSG=$1
|
||||
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
|
||||
# Get server metrics
|
||||
SERVER_INFO=$(get_server_info)
|
||||
IP=$(echo $SERVER_INFO | cut -d'|' -f1)
|
||||
CPU=$(echo $SERVER_INFO | cut -d'|' -f2)
|
||||
RAM=$(echo $SERVER_INFO | cut -d'|' -f3)
|
||||
DISK=$(echo $SERVER_INFO | cut -d'|' -f4)
|
||||
|
||||
# Create JSON payload
|
||||
JSON_PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
"error": "$ERROR_MSG",
|
||||
"timestamp": "$TIMESTAMP",
|
||||
"message": "Error detected in API logs",
|
||||
"server_metrics": {
|
||||
"ip": "$IP",
|
||||
"cpu_usage": "${CPU}%",
|
||||
"ram_available": "$RAM",
|
||||
"disk_space": "$DISK"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "=========================================="
|
||||
echo "⚠️ ALERT DETECTED!"
|
||||
echo "Error: $ERROR_MSG"
|
||||
echo "Server IP: $IP | CPU: ${CPU}% | RAM: $RAM | Disk: $DISK"
|
||||
echo "=========================================="
|
||||
|
||||
# Send to Ghaymah endpoint
|
||||
# Uncomment when endpoint URL is provided
|
||||
# curl -X POST "$GHAYMAH_URL" \
|
||||
# -H "Content-Type: application/json" \
|
||||
# -d "$JSON_PAYLOAD"
|
||||
|
||||
# For now, just save to alert file
|
||||
echo "$JSON_PAYLOAD" >> alerts.log
|
||||
echo "Alert saved to alerts.log"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Monitor log file continuously
|
||||
# Only trigger on actual ERROR lines with 4XX or 5XX codes
|
||||
tail -f "$LOG_FILE" | while read line
|
||||
do
|
||||
# Only check lines that have ERROR level AND contain error codes
|
||||
if echo "$line" | grep "ERROR" | grep -E "(400|404|500|503|failed|timeout)" > /dev/null; then
|
||||
send_alert "$line"
|
||||
fi
|
||||
done
|
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "simple-store"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
description = "a demo API (e.g., for a store, booking system, or any domain of your choice) with 6-7 documented endpoints, implement logging, monitor errors via a Bash script, and automate API requests."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
|
224
swagger.py
Normal file
224
swagger.py
Normal file
@@ -0,0 +1,224 @@
|
||||
from flask import Flask
|
||||
from flask_swagger_ui import get_swaggerui_blueprint
|
||||
import json
|
||||
|
||||
# Swagger configuration
|
||||
SWAGGER_URL = '/docs'
|
||||
API_URL = '/swagger.json'
|
||||
|
||||
swagger_ui_blueprint = get_swaggerui_blueprint(
|
||||
SWAGGER_URL,
|
||||
API_URL,
|
||||
config={
|
||||
'app_name': "My First Store API"
|
||||
}
|
||||
)
|
||||
|
||||
# Swagger JSON specification
|
||||
swagger_spec = {
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"title": "Simple Store API",
|
||||
"description": "My first API project - A simple store management system",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://localhost:5000",
|
||||
"description": "Development server"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/": {
|
||||
"get": {
|
||||
"summary": "Home page",
|
||||
"description": "Welcome message",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Welcome message",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {"message": "Welcome to my first API!"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/products": {
|
||||
"get": {
|
||||
"summary": "Get all products",
|
||||
"description": "Returns a list of all products in the store",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of products",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"products": [
|
||||
{"id": 1, "name": "Laptop", "price": 5000, "quantity": 10}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"summary": "Add new product",
|
||||
"description": "Create a new product in the store",
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "example": "Keyboard"},
|
||||
"price": {"type": "number", "example": 300},
|
||||
"quantity": {"type": "integer", "example": 20}
|
||||
},
|
||||
"required": ["name", "price"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Product created successfully"
|
||||
},
|
||||
"400": {
|
||||
"description": "Missing required fields"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/products/{product_id}": {
|
||||
"get": {
|
||||
"summary": "Get single product",
|
||||
"description": "Get details of a specific product by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "product_id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"schema": {"type": "integer"},
|
||||
"example": 1
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product details"
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"summary": "Update product",
|
||||
"description": "Update an existing product",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "product_id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"schema": {"type": "integer"}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"price": {"type": "number"},
|
||||
"quantity": {"type": "integer"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product updated"
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"summary": "Delete product",
|
||||
"description": "Remove a product from the store",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "product_id",
|
||||
"in": "path",
|
||||
"required": True,
|
||||
"schema": {"type": "integer"}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product deleted"
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/orders": {
|
||||
"get": {
|
||||
"summary": "Get all orders",
|
||||
"description": "Returns a list of all orders",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of orders"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"summary": "Create order",
|
||||
"description": "Place a new order for a product",
|
||||
"requestBody": {
|
||||
"required": True,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"product_id": {"type": "integer", "example": 1},
|
||||
"quantity": {"type": "integer", "example": 2}
|
||||
},
|
||||
"required": ["product_id", "quantity"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Order created successfully"
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid order data or not enough stock"
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def setup_swagger(app):
|
||||
"""Add Swagger UI to Flask app"""
|
||||
app.register_blueprint(swagger_ui_blueprint, url_prefix=SWAGGER_URL)
|
||||
|
||||
@app.route('/swagger.json')
|
||||
def swagger_json():
|
||||
return swagger_spec
|
117
test_api.sh
Executable file
117
test_api.sh
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Configuration
|
||||
API_URL="http://localhost:5000"
|
||||
LOG_FILE="test_results.log"
|
||||
|
||||
# Clear previous log
|
||||
> "$LOG_FILE"
|
||||
|
||||
echo "Starting API tests..."
|
||||
echo "Results will be saved to: $LOG_FILE"
|
||||
echo "========================================"
|
||||
|
||||
# Function to log results
|
||||
log_result() {
|
||||
echo "$1" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# Test 1: Home page
|
||||
log_result ""
|
||||
log_result "Test 1: GET / (Home page)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X GET "$API_URL/" | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test 2: Get all products
|
||||
log_result ""
|
||||
log_result "Test 2: GET /products (All products)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X GET "$API_URL/products" | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test 3: Get single product
|
||||
log_result ""
|
||||
log_result "Test 3: GET /products/1 (Single product)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X GET "$API_URL/products/1" | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test 4: Add new product
|
||||
log_result ""
|
||||
log_result "Test 4: POST /products (Add new product)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X POST "$API_URL/products" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Keyboard",
|
||||
"price": 300,
|
||||
"quantity": 20
|
||||
}' | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test 5: Update product
|
||||
log_result ""
|
||||
log_result "Test 5: PUT /products/1 (Update product)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X PUT "$API_URL/products/1" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"price": 4500,
|
||||
"quantity": 15
|
||||
}' | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test 6: Create order
|
||||
log_result ""
|
||||
log_result "Test 6: POST /orders (Create order)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X POST "$API_URL/orders" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"product_id": 2,
|
||||
"quantity": 5
|
||||
}' | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test 7: Get all orders
|
||||
log_result ""
|
||||
log_result "Test 7: GET /orders (All orders)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X GET "$API_URL/orders" | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test 8: Delete product
|
||||
log_result ""
|
||||
log_result "Test 8: DELETE /products/2 (Delete product)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X DELETE "$API_URL/products/2" | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
sleep 1
|
||||
|
||||
# Test 9: Try to get deleted product (should fail)
|
||||
log_result ""
|
||||
log_result "Test 9: GET /products/2 (Should return 404)"
|
||||
log_result "----------------------------"
|
||||
curl -s -X GET "$API_URL/products/2" | tee -a "$LOG_FILE"
|
||||
log_result ""
|
||||
|
||||
log_result ""
|
||||
log_result "========================================"
|
||||
log_result "All tests completed!"
|
||||
log_result "Check $LOG_FILE for full results"
|
المرجع في مشكلة جديدة
حظر مستخدم