رفع الملفات إلى "/"
هذا الالتزام موجود في:
327
app (copy 1).py
Normal file
327
app (copy 1).py
Normal file
@@ -0,0 +1,327 @@
|
||||
from flask import Flask, request, jsonify, g
|
||||
from flasgger import Swagger
|
||||
import logging
|
||||
import time
|
||||
import threading
|
||||
|
||||
app = Flask(__name__)
|
||||
swagger = Swagger(app)
|
||||
|
||||
# ---------- Logging setup ----------
|
||||
logger = logging.getLogger("library_loans")
|
||||
logger.setLevel(logging.INFO)
|
||||
fh = logging.FileHandler("api.log")
|
||||
formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s")
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
|
||||
# ---------- In-memory storage ----------
|
||||
# books: {id:int, title:str, author:str, year:int, copies:int}
|
||||
# users: {id:int, name:str, email:str}
|
||||
# loans: {id:int, user_id:int, book_id:int, timestamp:int, returned:bool}
|
||||
books = []
|
||||
users = []
|
||||
loans = []
|
||||
_next_loan_id = 1
|
||||
_storage_lock = threading.Lock()
|
||||
|
||||
# ---------- request timing & logging ----------
|
||||
@app.before_request
|
||||
def start_timer():
|
||||
g.start = time.time()
|
||||
|
||||
@app.after_request
|
||||
def after_request(response):
|
||||
latency = (time.time() - g.start) * 1000
|
||||
client = request.remote_addr or "-"
|
||||
msg = f"{client} {request.method} {request.path} {response.status_code} {latency:.2f}ms"
|
||||
if 400 <= response.status_code < 600:
|
||||
logger.error(msg)
|
||||
else:
|
||||
logger.info(msg)
|
||||
return response
|
||||
|
||||
# ---------- Helpers ----------
|
||||
def find_book(book_id):
|
||||
return next((b for b in books if b["id"] == book_id), None)
|
||||
|
||||
def find_user(user_id):
|
||||
return next((u for u in users if u["id"] == user_id), None)
|
||||
|
||||
def find_loan(loan_id):
|
||||
return next((l for l in loans if l["id"] == loan_id), None)
|
||||
|
||||
def available_copies(book_id):
|
||||
"""Return number of available (not loaned) copies for a book."""
|
||||
total = 0
|
||||
b = find_book(book_id)
|
||||
if not b:
|
||||
return 0
|
||||
total = b.get("copies", 1)
|
||||
borrowed_count = sum(1 for l in loans if l["book_id"] == book_id and not l.get("returned", False))
|
||||
return total - borrowed_count
|
||||
|
||||
# ---------- Endpoints ----------
|
||||
|
||||
# 1) GET /books
|
||||
@app.route("/books", methods=["GET"])
|
||||
def get_books():
|
||||
"""List all books
|
||||
---
|
||||
responses:
|
||||
200:
|
||||
description: list of books
|
||||
"""
|
||||
return jsonify(books), 200
|
||||
|
||||
# 2) POST /books
|
||||
@app.route("/books", methods=["POST"])
|
||||
def add_book():
|
||||
"""Add a new book (id, title, author, year, copies)
|
||||
---
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
schema:
|
||||
type: object
|
||||
required: [id, title, author]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
title:
|
||||
type: string
|
||||
author:
|
||||
type: string
|
||||
year:
|
||||
type: integer
|
||||
copies:
|
||||
type: integer
|
||||
responses:
|
||||
201:
|
||||
description: created book
|
||||
400:
|
||||
description: invalid payload
|
||||
409:
|
||||
description: duplicate id
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
if "id" not in data or "title" not in data or "author" not in data:
|
||||
return jsonify({"error": "id, title, author required"}), 400
|
||||
if find_book(data["id"]):
|
||||
return jsonify({"error": "book id exists"}), 409
|
||||
data.setdefault("year", None)
|
||||
data.setdefault("copies", 1)
|
||||
books.append({
|
||||
"id": int(data["id"]),
|
||||
"title": data["title"],
|
||||
"author": data["author"],
|
||||
"year": data["year"],
|
||||
"copies": int(data["copies"])
|
||||
})
|
||||
return jsonify(find_book(data["id"])), 201
|
||||
|
||||
# 3) PUT /books/<id>
|
||||
@app.route("/books/<int:book_id>", methods=["PUT"])
|
||||
def update_book(book_id):
|
||||
"""Update a book by id
|
||||
---
|
||||
parameters:
|
||||
- name: book_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
- in: body
|
||||
name: body
|
||||
schema:
|
||||
type: object
|
||||
responses:
|
||||
200:
|
||||
description: updated
|
||||
404:
|
||||
description: not found
|
||||
"""
|
||||
b = find_book(book_id)
|
||||
if not b:
|
||||
return jsonify({"error": "book not found"}), 404
|
||||
data = request.get_json() or {}
|
||||
# Allow updating title, author, year, copies
|
||||
for k in ("title", "author", "year", "copies"):
|
||||
if k in data:
|
||||
if k == "copies":
|
||||
b[k] = int(data[k])
|
||||
else:
|
||||
b[k] = data[k]
|
||||
return jsonify(b), 200
|
||||
|
||||
# 4) DELETE /books/<id>
|
||||
@app.route("/books/<int:book_id>", methods=["DELETE"])
|
||||
def delete_book(book_id):
|
||||
"""Delete book by id
|
||||
---
|
||||
parameters:
|
||||
- name: book_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
description: deleted
|
||||
404:
|
||||
description: not found
|
||||
"""
|
||||
global books
|
||||
if not find_book(book_id):
|
||||
return jsonify({"error": "book not found"}), 404
|
||||
books = [b for b in books if b["id"] != book_id]
|
||||
# optionally remove related loans? keep history (we keep loans)
|
||||
return jsonify({"message": "book deleted"}), 200
|
||||
|
||||
# 5) GET /users
|
||||
@app.route("/users", methods=["GET"])
|
||||
def get_users():
|
||||
"""List users
|
||||
---
|
||||
responses:
|
||||
200:
|
||||
description: list of users
|
||||
"""
|
||||
return jsonify(users), 200
|
||||
|
||||
# 6) POST /users
|
||||
@app.route("/users", methods=["POST"])
|
||||
def add_user():
|
||||
"""Add a new user (id, name, email)
|
||||
---
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
schema:
|
||||
type: object
|
||||
required: [id, name]
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
responses:
|
||||
201:
|
||||
description: created
|
||||
409:
|
||||
description: duplicate
|
||||
"""
|
||||
data = request.get_json() or {}
|
||||
if "id" not in data or "name" not in data:
|
||||
return jsonify({"error": "id and name required"}), 400
|
||||
if find_user(data["id"]):
|
||||
return jsonify({"error": "user id exists"}), 409
|
||||
users.append({
|
||||
"id": int(data["id"]),
|
||||
"name": data["name"],
|
||||
"email": data.get("email")
|
||||
})
|
||||
return jsonify(find_user(data["id"])), 201
|
||||
|
||||
# 7) POST /loans -> borrow a book
|
||||
@app.route("/loans", methods=["POST"])
|
||||
def create_loan():
|
||||
"""Borrow a book (user_id, book_id)
|
||||
---
|
||||
parameters:
|
||||
- in: body
|
||||
name: body
|
||||
schema:
|
||||
type: object
|
||||
required: [user_id, book_id]
|
||||
properties:
|
||||
user_id:
|
||||
type: integer
|
||||
book_id:
|
||||
type: integer
|
||||
responses:
|
||||
201:
|
||||
description: loan created
|
||||
404:
|
||||
description: user/book not found
|
||||
409:
|
||||
description: not available
|
||||
"""
|
||||
global _next_loan_id
|
||||
data = request.get_json() or {}
|
||||
user_id = data.get("user_id")
|
||||
book_id = data.get("book_id")
|
||||
if not find_user(user_id):
|
||||
return jsonify({"error": "user not found"}), 404
|
||||
if not find_book(book_id):
|
||||
return jsonify({"error": "book not found"}), 404
|
||||
if available_copies(book_id) <= 0:
|
||||
return jsonify({"error": "no copies available"}), 409
|
||||
with _storage_lock:
|
||||
loan = {
|
||||
"id": _next_loan_id,
|
||||
"user_id": int(user_id),
|
||||
"book_id": int(book_id),
|
||||
"timestamp": int(time.time()),
|
||||
"returned": False
|
||||
}
|
||||
loans.append(loan)
|
||||
_next_loan_id += 1
|
||||
return jsonify(loan), 201
|
||||
|
||||
# 8) GET /loans -> list loans (active/all via query)
|
||||
@app.route("/loans", methods=["GET"])
|
||||
def list_loans():
|
||||
"""List loans
|
||||
---
|
||||
parameters:
|
||||
- name: active
|
||||
in: query
|
||||
type: boolean
|
||||
description: if true, return only non-returned loans
|
||||
responses:
|
||||
200:
|
||||
description: loans
|
||||
"""
|
||||
active = request.args.get("active")
|
||||
if active is not None and active.lower() in ("1","true","yes"):
|
||||
res = [l for l in loans if not l.get("returned", False)]
|
||||
else:
|
||||
res = loans
|
||||
return jsonify(res), 200
|
||||
|
||||
# 9) PUT /loans/<id>/return -> return a loan
|
||||
@app.route("/loans/<int:loan_id>/return", methods=["PUT"])
|
||||
def return_loan(loan_id):
|
||||
"""Return a borrowed book (mark loan returned)
|
||||
---
|
||||
parameters:
|
||||
- name: loan_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
description: returned
|
||||
404:
|
||||
description: loan not found
|
||||
400:
|
||||
description: already returned
|
||||
"""
|
||||
l = find_loan(loan_id)
|
||||
if not l:
|
||||
return jsonify({"error": "loan not found"}), 404
|
||||
if l.get("returned"):
|
||||
return jsonify({"error": "already returned"}), 400
|
||||
l["returned"] = True
|
||||
l["returned_timestamp"] = int(time.time())
|
||||
return jsonify(l), 200
|
||||
|
||||
# 10) Health
|
||||
@app.route("/health", methods=["GET"])
|
||||
def health():
|
||||
return jsonify({"status": "ok"}), 200
|
||||
|
||||
# ---------- Run ----------
|
||||
if __name__ == "__main__":
|
||||
app.run(host="127.0.0.1", port=5000, debug=true)
|
42
monitor (copy 1).sh
Normal file
42
monitor (copy 1).sh
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
#!/usr/bin/env bash
|
||||
# monitor.sh íÑÕÏ api.log áÇßÊÔÇÝ ÃÎØÇÁ 4xx/5xx Ãæ ßáãÇÊ failed/timeout
|
||||
# æíÑÓá JSON Åáì CLOUD endpoint ÚÈÑ curl
|
||||
LOGFILE="./api.log"
|
||||
CLOUD_ENDPOINT="${CLOUD_ENDPOINT:-https://example.com/alert}"
|
||||
|
||||
if [ ! -f "$LOGFILE" ]; then
|
||||
echo "api.log not found, creating..."
|
||||
touch "$LOGFILE"
|
||||
fi
|
||||
|
||||
|
||||
tail -n0 -F "$LOGFILE" | while read -r line; do
|
||||
echo "$line" | grep -E "ERROR|4[0-9]{2}|5[0-9]{2}|failed|timeout" >/dev/null 2>&1
|
||||
if [ $? -eq 0 ]; then
|
||||
ts=$(date --iso-8601=seconds)
|
||||
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||||
cpu_line=$(top -bn1 | grep "Cpu(s)" 2>/dev/null || top -bn1 | head -n 5 | tail -n 1)
|
||||
ram_line=$(free -m | awk 'NR==2{printf "%sMB/%sMB (used/total)", $3,$2}')
|
||||
disk_line=$(df -h / | awk 'NR==2{print $3" used of " $2 " (" $5 " used)"}')
|
||||
log_excerpt="$line"
|
||||
|
||||
# ÓæöøÑ JSON (ÇÍÐÑ ÚáÇãÇÊ ÇáÇÞÊÈÇÓ)
|
||||
payload=$(cat <<EOF
|
||||
{
|
||||
"timestamp": "$ts",
|
||||
"server_ip": "$ip",
|
||||
"cpu": "$(echo "$cpu_line" | sed 's/\"/'"'"'/g')",
|
||||
"ram": "$ram_line",
|
||||
"disk": "$disk_line",
|
||||
"log_line": "$(echo "$log_excerpt" | sed 's/\"/'"'"'/g')"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "[$ts] ALERT: sending payload to $CLOUD_ENDPOINT"
|
||||
curl -s -X POST -H "Content-Type: application/json" -d "$payload" "$CLOUD_ENDPOINT" || {
|
||||
echo "[$(date --iso-8601=seconds)] Failed to send alert" >&2
|
||||
}
|
||||
fi
|
||||
done
|
48
test._api.sh
Normal file
48
test._api.sh
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
BASE="http://localhost:5000"
|
||||
OUT_LOG="test_results.log"
|
||||
echo "Starting API tests at $(date -u)" > "$OUT_LOG"
|
||||
|
||||
# helper
|
||||
do_request() {
|
||||
local method=$1
|
||||
local url=$2
|
||||
local data=$3
|
||||
echo "---- $method $url" | tee -a "$OUT_LOG"
|
||||
if [ -n "$data" ]; then
|
||||
curl -s -w "\nHTTP_CODE:%{http_code}\nTIME_TOTAL:%{time_total}\n" -X "$method" -H "Content-Type: application/json" -d "$data" "$url" >> "$OUT_LOG"
|
||||
else
|
||||
curl -s -w "\nHTTP_CODE:%{http_code}\nTIME_TOTAL:%{time_total}\n" -X "$method" "$url" >> "$OUT_LOG"
|
||||
fi
|
||||
echo -e "\n" >> "$OUT_LOG"
|
||||
sleep 0.5
|
||||
}
|
||||
|
||||
|
||||
do_request GET "$BASE/items"
|
||||
|
||||
# 2) POST create item
|
||||
do_request POST "$BASE/items" '{"name":"Test Item 1","price":12.5}'
|
||||
|
||||
# 3) POST create another
|
||||
do_request POST "$BASE/items" '{"name":"Test Item 2","price":5.0}'
|
||||
|
||||
# 4) GET items (should list 2)
|
||||
do_request GET "$BASE/items"
|
||||
|
||||
# 5) GET single item (id 1)
|
||||
do_request GET "$BASE/items/1"
|
||||
|
||||
# 6) PUT update item 1
|
||||
do_request PUT "$BASE/items/1" '{"name":"Test Item 1 updated","price":15.0}'
|
||||
|
||||
# 7) POST create order (valid)
|
||||
do_request POST "$BASE/orders" '{"item_id":1,"quantity":2}'
|
||||
|
||||
# 8) POST create order (invalid item -> triggers 400)
|
||||
do_request POST "$BASE/orders" '{"item_id":9999,"quantity":1}'
|
||||
do_request DELETE "$BASE/items/2"
|
||||
do_request GET "$BASE/items"
|
||||
|
||||
echo "Tests finished at $(date -u)" >> "$OUT_LOG"
|
||||
echo "Results written to $OUT_LOG"
|
75
test.sh
Normal file
75
test.sh
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
#!/usr/bin/env bash
|
||||
# test_api.sh íÌÑøÈ ÇáÜ API æíÏæøä ÇáäÊÇÆÌ Ýí test_results.log
|
||||
BASE_URL=${BASE_URL:-http://localhost:5000}
|
||||
LOGFILE="./test_results.log"
|
||||
|
||||
timestamp(){ date +"%Y-%m-%d %H:%M:%S"; }
|
||||
|
||||
echo "=== TEST RUN $(timestamp) ===" >> "$LOGFILE"
|
||||
|
||||
run(){
|
||||
method=$1; url=$2; data=$3
|
||||
if [ -n "$data" ]; then
|
||||
resp=$(curl -s -w "\n%{http_code}" -X "$method" -H "Content-Type: application/json" -d "$data" "$url")
|
||||
else
|
||||
resp=$(curl -s -w "\n%{http_code}" -X "$method" "$url")
|
||||
fi
|
||||
body=$(echo "$resp" | sed '$d')
|
||||
code=$(echo "$resp" | tail -n1)
|
||||
echo "[$(timestamp)] $method $url -> $code" >> "$LOGFILE"
|
||||
echo "$body" >> "$LOGFILE"
|
||||
echo "----" >> "$LOGFILE"
|
||||
}
|
||||
|
||||
# 1. health
|
||||
run GET "$BASE_URL/health"
|
||||
|
||||
# 2. list books (should be empty)
|
||||
run GET "$BASE_URL/books"
|
||||
|
||||
# 3. add books
|
||||
b1='{"id":1,"title":"Clean Code","author":"Robert C. Martin","year":2008,"copies":1}'
|
||||
b2='{"id":2,"title":"The Pragmatic Programmer","author":"Andrew Hunt","year":1999,"copies":2}'
|
||||
run POST "$BASE_URL/books" "$b1"
|
||||
run POST "$BASE_URL/books" "$b2"
|
||||
|
||||
# 4. add users
|
||||
u1='{"id":10,"name":"Mustafa","email":"m@example.com"}'
|
||||
u2='{"id":11,"name":"Sara","email":"s@example.com"}'
|
||||
run POST "$BASE_URL/users" "$u1"
|
||||
run POST "$BASE_URL/users" "$u2"
|
||||
|
||||
# 5. borrow book id=1 by user 10 (should succeed)
|
||||
loan1='{"user_id":10,"book_id":1}'
|
||||
run POST "$BASE_URL/loans" "$loan1"
|
||||
|
||||
# 6. borrow same book id=1 again -> copies=1 so should fail (409)
|
||||
run POST "$BASE_URL/loans" "$loan1"
|
||||
|
||||
# 7. borrow book id=2 twice (copies=2) by two users -> both succeed
|
||||
loan2='{"user_id":11,"book_id":2}'
|
||||
run POST "$BASE_URL/loans" "$loan2"
|
||||
run POST "$BASE_URL/loans" '{"user_id":10,"book_id":2}'
|
||||
|
||||
# 8. list active loans
|
||||
run GET "$BASE_URL/loans?active=true"
|
||||
|
||||
# 9. return loan id 1
|
||||
run PUT "$BASE_URL/loans/1/return"
|
||||
|
||||
# 10. try returning again (should get 400)
|
||||
run PUT "$BASE_URL/loans/1/return"
|
||||
|
||||
# 11. delete book 999 (not exist -> 404)
|
||||
run DELETE "$BASE_URL/books/999"
|
||||
|
||||
# 12. update book 2
|
||||
run PUT "$BASE_URL/books/2" '{"copies":1,"year":2000}'
|
||||
|
||||
# 13. list books & loans
|
||||
run GET "$BASE_URL/books"
|
||||
run GET "$BASE_URL/loans"
|
||||
|
||||
echo "=== END TEST RUN $(timestamp) ===" >> "$LOGFILE"
|
||||
echo "" >> "$LOGFILE"
|
المرجع في مشكلة جديدة
حظر مستخدم