diff --git a/README .md b/README .md new file mode 100644 index 0000000..b51b44a --- /dev/null +++ b/README .md @@ -0,0 +1,176 @@ +# 🍎 Fruit Store API + +A simple **RESTful API** built with Flask that manages fruits in a store. +The project also includes **logging**, a **log monitoring script**, and **automated testing script**. + +--- + +## 📌 Features +- CRUD operations on fruits (`GET`, `POST`, `PUT`, `DELETE`) +- Search fruits by name +- Filter fruits by category +- Health check endpoint +- Logging of all requests & responses to `api.log` +- Bash script for monitoring logs (`monitor.sh`) +- Bash script for automated API testing (`test_api.sh`) +- Swagger/OpenAPI documentation + +--- + +## ⚙️ Setup Instructions + +### 1. Clone the Repository +```bash +git clone https://github.com/YOUR-USERNAME/fruit-api.git +cd fruit-api +``` + +### 2. Create Virtual Environment & Install Dependencies +```bash +python3 -m venv venv +source venv/bin/activate # Linux/Mac +venv\Scripts\activate # Windows + +pip install -r requirements.txt +``` + +### 3. Run the API +```bash +python fruit_api.py +``` + +API will start at: +👉 `http://localhost:5000` + +--- + +## 🚀 API Endpoints + +### Base URL +``` +http://localhost:5000 +``` + +### Endpoints + +| Method | Endpoint | Description | +|--------|----------------------------------|------------------------------| +| GET | `/fruits` | Get all fruits | +| GET | `/fruits/` | Get fruit by ID | +| POST | `/fruits` | Add a new fruit | +| PUT | `/fruits/` | Update an existing fruit | +| DELETE | `/fruits/` | Delete a fruit | +| GET | `/fruits/category/` | Get fruits by category | +| GET | `/fruits/search?name=` | Search fruits by name | +| GET | `/health` | Health check | + +--- + +## 📑 Example Usage (with curl) + +- **Get all fruits** +```bash +curl http://localhost:5000/fruits +``` + +- **Add a fruit** +```bash +curl -X POST http://localhost:5000/fruits -H "Content-Type: application/json" -d '{"name":"Mango","color":"Yellow","price":2.5,"quantity":50,"category":"Tropical"}' +``` + +- **Update a fruit** +```bash +curl -X PUT http://localhost:5000/fruits/1 -H "Content-Type: application/json" -d '{"name":"Green Apple","price":1.8}' +``` + +--- + +## 📝 Logging + +- All API requests and responses are saved in: +``` +api.log +``` + +Example log: +``` +2025-09-29 18:05:22 - IP:127.0.0.1 - METHOD:GET - ENDPOINT:/fruits - STATUS:200 +``` + +--- + +## 🔎 Monitoring (monitor.sh) + +The `monitor.sh` script: +- Monitors `api.log` every 2 hours +- Detects **errors (4XX, 5XX)** and unusual keywords (`failed`, `timeout`) +- Captures server metrics (IP, CPU, RAM, Disk) +- Sends an **alert JSON** to Ghaymah endpoint + +Run it in background: +```bash +bash monitor.sh & +``` + +--- + +## 🤖 API Testing (test_api.sh) + +The `test_api.sh` script: +- Runs automated `GET`, `POST`, `PUT`, `DELETE` requests +- Logs results in `test_results.log` + +Run tests: +```bash +bash test_api.sh +``` + +--- + +## 📘 Swagger Documentation +After starting the API, open: +👉 [http://localhost:5000/docs](http://localhost:5000/docs) + +--- + +## 🐳 Docker Support + +### Build the image +```bash +docker build -t fruit-api . +``` + +### Run container +```bash +docker run -p 5000:5000 fruit-api +``` + +--- + +## 📂 Project Structure +``` +fruit-api/ +│── fruit_api.py # Main Flask API +│── requirements.txt # Dependencies +│── Dockerfile # Docker support +│── monitor.sh # Log monitoring script +│── test_api.sh # Automated testing script +│── api.log # API logs +│── test_results.log # Test logs +│── README.md # Documentation +│── .gitignore # Ignore venv/logs +``` + +--- + +## 📌 Submission Guidelines +- Push project to **GitPasha**: [https://app.gitpasha.com/](https://app.gitpasha.com/) +- Repository must include: + - API source code + - Bash scripts (`monitor.sh`, `test_api.sh`) + - Sample logs + - Documentation (`README.md`) +- Demo should include: + - Running the API + - Triggering errors + - Showing alerts in action diff --git a/fruit_api.log b/fruit_api.log index 6ebc3f3..0f7ceab 100644 --- a/fruit_api.log +++ b/fruit_api.log @@ -54,3 +54,76 @@ 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 - +2025-09-28 15:21:56,247 [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-28 15:21:56,258 [INFO] Press CTRL+C to quit +2025-09-28 15:22:01,830 [INFO] REQUEST: method=GET path=/ args={} body=None ip=127.0.0.1 +2025-09-28 15:22:01,830 [INFO] RESPONSE: method=GET path=/ status=200 ip=127.0.0.1 +2025-09-28 15:22:01,831 [INFO] 127.0.0.1 - - [28/Sep/2025 15:22:01] "GET / HTTP/1.1" 200 - +2025-09-28 15:22:03,048 [INFO] REQUEST: method=GET path=/favicon.ico args={} body=None ip=127.0.0.1 +2025-09-28 15:22:03,049 [INFO] RESPONSE: method=GET path=/favicon.ico status=404 ip=127.0.0.1 +2025-09-28 15:22:03,049 [INFO] 127.0.0.1 - - [28/Sep/2025 15:22:03] "GET /favicon.ico HTTP/1.1" 404 - +2025-09-29 19:49:26,854 [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-29 19:49:26,865 [INFO] Press CTRL+C to quit +2025-09-29 19:49:31,949 [INFO] REQUEST: method=GET path=/ args={} body=None ip=127.0.0.1 +2025-09-29 19:49:31,950 [INFO] RESPONSE: method=GET path=/ status=200 ip=127.0.0.1 +2025-09-29 19:49:31,951 [INFO] 127.0.0.1 - - [29/Sep/2025 19:49:31] "GET / HTTP/1.1" 200 - +2025-09-29 19:49:32,958 [INFO] REQUEST: method=GET path=/favicon.ico args={} body=None ip=127.0.0.1 +2025-09-29 19:49:32,959 [INFO] RESPONSE: method=GET path=/favicon.ico status=404 ip=127.0.0.1 +2025-09-29 19:49:32,960 [INFO] 127.0.0.1 - - [29/Sep/2025 19:49:32] "GET /favicon.ico HTTP/1.1" 404 - +2025-09-29 19:49:39,141 [INFO] REQUEST: method=GET path=/docs/ args={} body=None ip=127.0.0.1 +2025-09-29 19:49:39,142 [INFO] RESPONSE: method=GET path=/docs/ status=404 ip=127.0.0.1 +2025-09-29 19:49:39,143 [INFO] 127.0.0.1 - - [29/Sep/2025 19:49:39] "GET /docs/ HTTP/1.1" 404 - +2025-09-29 19:49:49,569 [INFO] REQUEST: method=GET path=/ args={} body=None ip=127.0.0.1 +2025-09-29 19:49:49,570 [INFO] RESPONSE: method=GET path=/ status=200 ip=127.0.0.1 +2025-09-29 19:49:49,570 [INFO] 127.0.0.1 - - [29/Sep/2025 19:49:49] "GET / HTTP/1.1" 200 - +2025-09-29 19:49:49,656 [INFO] REQUEST: method=GET path=/favicon.ico args={} body=None ip=127.0.0.1 +2025-09-29 19:49:49,657 [INFO] RESPONSE: method=GET path=/favicon.ico status=404 ip=127.0.0.1 +2025-09-29 19:49:49,657 [INFO] 127.0.0.1 - - [29/Sep/2025 19:49:49] "GET /favicon.ico HTTP/1.1" 404 - +2025-09-29 19:51:31,227 [INFO] REQUEST: method=GET path=/items args={} body=None ip=127.0.0.1 +2025-09-29 19:51:31,228 [INFO] RESPONSE: method=GET path=/items status=404 ip=127.0.0.1 +2025-09-29 19:51:31,228 [INFO] 127.0.0.1 - - [29/Sep/2025 19:51:31] "GET /items HTTP/1.1" 404 - +2025-09-29 19:51:51,563 [INFO] REQUEST: method=GET path=/items/1 args={} body=None ip=127.0.0.1 +2025-09-29 19:51:51,563 [INFO] RESPONSE: method=GET path=/items/1 status=404 ip=127.0.0.1 +2025-09-29 19:51:51,564 [INFO] 127.0.0.1 - - [29/Sep/2025 19:51:51] "GET /items/1 HTTP/1.1" 404 - +2025-09-29 19:52:37,279 [INFO] REQUEST: method=GET path=/fruits args={} body=None ip=127.0.0.1 +2025-09-29 19:52:37,279 [INFO] RESPONSE: method=GET path=/fruits status=200 ip=127.0.0.1 +2025-09-29 19:52:37,280 [INFO] 127.0.0.1 - - [29/Sep/2025 19:52:37] "GET /fruits HTTP/1.1" 200 - +2025-09-29 19:53:04,750 [INFO] REQUEST: method=GET path=/fruit args={} body=None ip=127.0.0.1 +2025-09-29 19:53:04,751 [INFO] RESPONSE: method=GET path=/fruit status=404 ip=127.0.0.1 +2025-09-29 19:53:04,752 [INFO] 127.0.0.1 - - [29/Sep/2025 19:53:04] "GET /fruit HTTP/1.1" 404 - +2025-09-29 19:53:15,728 [INFO] REQUEST: method=GET path=/items/1 args={} body=None ip=127.0.0.1 +2025-09-29 19:53:15,728 [INFO] RESPONSE: method=GET path=/items/1 status=404 ip=127.0.0.1 +2025-09-29 19:53:15,729 [INFO] 127.0.0.1 - - [29/Sep/2025 19:53:15] "GET /items/1 HTTP/1.1" 404 - +2025-09-29 19:53:23,708 [INFO] REQUEST: method=GET path=/fruit/1 args={} body=None ip=127.0.0.1 +2025-09-29 19:53:23,709 [INFO] RESPONSE: method=GET path=/fruit/1 status=404 ip=127.0.0.1 +2025-09-29 19:53:23,709 [INFO] 127.0.0.1 - - [29/Sep/2025 19:53:23] "GET /fruit/1 HTTP/1.1" 404 - +2025-09-29 19:53:37,805 [INFO] REQUEST: method=GET path=/fruits/1 args={} body=None ip=127.0.0.1 +2025-09-29 19:53:37,805 [INFO] RESPONSE: method=GET path=/fruits/1 status=200 ip=127.0.0.1 +2025-09-29 19:53:37,806 [INFO] 127.0.0.1 - - [29/Sep/2025 19:53:37] "GET /fruits/1 HTTP/1.1" 200 - +2025-09-29 19:53:51,028 [INFO] REQUEST: method=GET path=/fruits args={} body=None ip=127.0.0.1 +2025-09-29 19:53:51,029 [INFO] RESPONSE: method=GET path=/fruits status=200 ip=127.0.0.1 +2025-09-29 19:53:51,030 [INFO] 127.0.0.1 - - [29/Sep/2025 19:53:51] "GET /fruits HTTP/1.1" 200 - +2025-09-29 19:53:59,807 [INFO] REQUEST: method=GET path=/fruits/1 args={} body=None ip=127.0.0.1 +2025-09-29 19:53:59,807 [INFO] RESPONSE: method=GET path=/fruits/1 status=200 ip=127.0.0.1 +2025-09-29 19:53:59,808 [INFO] 127.0.0.1 - - [29/Sep/2025 19:53:59] "GET /fruits/1 HTTP/1.1" 200 - +2025-09-29 19:54:05,916 [INFO] REQUEST: method=GET path=/fruits/2 args={} body=None ip=127.0.0.1 +2025-09-29 19:54:05,917 [INFO] RESPONSE: method=GET path=/fruits/2 status=200 ip=127.0.0.1 +2025-09-29 19:54:05,917 [INFO] 127.0.0.1 - - [29/Sep/2025 19:54:05] "GET /fruits/2 HTTP/1.1" 200 - +2025-09-29 19:54:10,593 [INFO] REQUEST: method=GET path=/fruits/3 args={} body=None ip=127.0.0.1 +2025-09-29 19:54:10,594 [INFO] RESPONSE: method=GET path=/fruits/3 status=200 ip=127.0.0.1 +2025-09-29 19:54:10,594 [INFO] 127.0.0.1 - - [29/Sep/2025 19:54:10] "GET /fruits/3 HTTP/1.1" 200 - +2025-09-29 19:54:14,309 [INFO] REQUEST: method=GET path=/fruits/4 args={} body=None ip=127.0.0.1 +2025-09-29 19:54:14,310 [INFO] RESPONSE: method=GET path=/fruits/4 status=404 ip=127.0.0.1 +2025-09-29 19:54:14,310 [INFO] 127.0.0.1 - - [29/Sep/2025 19:54:14] "GET /fruits/4 HTTP/1.1" 404 - +2025-09-29 19:54:57,939 [INFO] REQUEST: method=GET path=/fruits/1 args={} body=None ip=127.0.0.1 +2025-09-29 19:54:57,939 [INFO] RESPONSE: method=GET path=/fruits/1 status=200 ip=127.0.0.1 +2025-09-29 19:54:57,940 [INFO] 127.0.0.1 - - [29/Sep/2025 19:54:57] "GET /fruits/1 HTTP/1.1" 200 - +2025-09-29 19:55:02,233 [INFO] REQUEST: method=GET path=/fruits args={} body=None ip=127.0.0.1 +2025-09-29 19:55:02,233 [INFO] RESPONSE: method=GET path=/fruits status=200 ip=127.0.0.1 +2025-09-29 19:55:02,234 [INFO] 127.0.0.1 - - [29/Sep/2025 19:55:02] "GET /fruits HTTP/1.1" 200 -