commit da5a008931704a50a94695e816b485140c3133d7 Author: mustafa_98 Date: Sat Oct 4 14:32:44 2025 +0000 رفع الملفات إلى "/" diff --git a/app (copy 1).py b/app (copy 1).py new file mode 100644 index 0000000..2be20f2 --- /dev/null +++ b/app (copy 1).py @@ -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/ +@app.route("/books/", 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/ +@app.route("/books/", 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//return -> return a loan +@app.route("/loans//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) diff --git a/monitor (copy 1).sh b/monitor (copy 1).sh new file mode 100644 index 0000000..dae37ed --- /dev/null +++ b/monitor (copy 1).sh @@ -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 <&2 + } + fi +done diff --git a/test._api.sh b/test._api.sh new file mode 100644 index 0000000..0122a9a --- /dev/null +++ b/test._api.sh @@ -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" diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..b0f8cba --- /dev/null +++ b/test.sh @@ -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"