{"id":19930635,"url":"https://github.com/kariemseiam/geoegy","last_synced_at":"2026-02-13T08:11:20.739Z","repository":{"id":262312598,"uuid":"886266136","full_name":"kariemSeiam/GeoEgy","owner":"kariemSeiam","description":"An innovative and responsive dashboard to discover, filter, and analyze places across Egypt. Featuring advanced search, interactive maps with Leaflet.js, real-time analytics, dark mode, and seamless data export—all wrapped in a sleek, modern design with RTL support.","archived":false,"fork":false,"pushed_at":"2024-11-11T18:30:04.000Z","size":73,"stargazers_count":0,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-11T19:29:03.434Z","etag":null,"topics":["accessibility","data-analysis","data-visualization","es6-modules","geojson","javascript","leaflet","mapping","openstreetmap","places-data","responsive-design","web-development"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kariemSeiam.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-10T16:17:15.000Z","updated_at":"2024-11-11T18:39:54.000Z","dependencies_parsed_at":"2024-11-11T19:29:22.147Z","dependency_job_id":"897265b4-7eeb-47ef-9a9d-c57ac63b6906","html_url":"https://github.com/kariemSeiam/GeoEgy","commit_stats":null,"previous_names":["kariemseiam/geoegy"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kariemSeiam%2FGeoEgy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kariemSeiam%2FGeoEgy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kariemSeiam%2FGeoEgy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kariemSeiam%2FGeoEgy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kariemSeiam","download_url":"https://codeload.github.com/kariemSeiam/GeoEgy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224360358,"owners_count":17298319,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["accessibility","data-analysis","data-visualization","es6-modules","geojson","javascript","leaflet","mapping","openstreetmap","places-data","responsive-design","web-development"],"created_at":"2024-11-12T23:04:40.811Z","updated_at":"2026-02-13T08:11:20.705Z","avatar_url":"https://github.com/kariemSeiam.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GeoEgy Backend Documentation 🚀\n\n![License](https://img.shields.io/badge/license-MIT-blue.svg)\n![Version](https://img.shields.io/badge/version-1.0.0-blue)\n![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen)\n\n---\n\n## 📖 Table of Contents\n\n1. [🌟 Project Overview](#-project-overview)\n2. [🛠️ Setup and Installation](#️-setup-and-installation)\n3. [⚙️ Configuration](#️-configuration)\n4. [🚀 Running the Server](#-running-the-server)\n5. [📡 API Endpoints](#-api-endpoints)\n   - [👤 User Endpoints](#-user-endpoints)\n   - [📦 Order Endpoints](#-order-endpoints)\n   - [🛡️ Admin Endpoints](#️-admin-endpoints)\n6. [🗃️ Database Models](#️-database-models)\n7. [🔄 Background Tasks](#-background-tasks)\n8. [🛑 Error Handling](#-error-handling)\n9. [⏱️ Rate Limiting](#️-rate-limiting)\n10. [🔒 Security](#-security)\n11. [📜 Database Migrations](#-database-migrations)\n12. [📚 Dependencies](#-dependencies)\n13. [🔧 Utilities](#-utilities)\n14. [📁 Project Structure](#-project-structure)\n15. [✍️ Contributing](#️-contributing)\n16. [📄 License](#-license)\n17. [📫 Contact](#-contact)\n\n---\n\n## 🌟 Project Overview\n\n**GeoEgy** is a robust backend system built with Flask, designed to manage and process geographical data related to places in Egypt. Leveraging modern technologies and best practices, it offers secure user authentication, efficient order management, administrative controls, and seamless integration with WhatsApp for real-time notifications.\n\n### 🎯 Key Objectives\n\n- **Scalability**: Designed to handle a growing number of users and orders.\n- **Security**: Implements JWT authentication, rate limiting, and security headers.\n- **Asynchronous Processing**: Utilizes Celery for handling long-running tasks without blocking the main application.\n- **User-Friendly API**: Provides clear and consistent API endpoints for frontend integration.\n- **Comprehensive Logging**: Ensures all actions and errors are logged for monitoring and debugging.\n\n---\n\n## 🛠️ Setup and Installation\n\n### 📝 Prerequisites\n\n- **Python 3.7+**\n- **Redis**: Required for Celery broker and backend.\n- **Git**: For version control (optional but recommended).\n\n### 🔧 Steps\n\n1. **Clone the Repository**\n\n   ```bash\n   git clone https://github.com/kariemSeiam/GeoEgy/.git\n   cd GeoEgy/Backend\n   ```\n\n2. **Create a Virtual Environment**\n\n   It's recommended to use a virtual environment to manage dependencies.\n\n   ```bash\n   python -m venv venv\n   source venv/bin/activate  # On Windows: venv\\Scripts\\activate\n   ```\n\n3. **Install Dependencies**\n\n   Install all required Python packages using `requirements.txt`.\n\n   ```bash\n   pip install -r requirements.txt\n   ```\n\n4. **Set Up Environment Variables**\n\n   Create a `.env` file in the `Backend` directory and populate it with the necessary environment variables. Below is a sample:\n\n   ```env\n   SECRET_KEY=your_secret_key\n   JWT_SECRET_KEY=your_jwt_secret_key\n   DATABASE_URI=sqlite:///database.db\n   CELERY_BROKER_URL=redis://localhost:6379/0\n   CELERY_RESULT_BACKEND=redis://localhost:6379/0\n   TWILIO_ACCOUNT_SID=your_twilio_account_sid\n   TWILIO_AUTH_TOKEN=your_twilio_auth_token\n   TWILIO_WHATSAPP_NUMBER=whatsapp:+14155238886\n   ```\n\n   **Note**: Replace the placeholder values with your actual credentials. For production environments, ensure that sensitive information is securely managed.\n\n5. **Initialize the Database**\n\n   Perform database migrations to set up the initial schema.\n\n   ```bash\n   flask db init\n   flask db migrate -m \"Initial migration.\"\n   flask db upgrade\n   ```\n\n6. **Start Redis Server**\n\n   Ensure that Redis is running, as it's required for Celery.\n\n   ```bash\n   redis-server\n   ```\n\n   **Note**: Install Redis from [https://redis.io/download](https://redis.io/download) if not already installed.\n\n---\n\n## ⚙️ Configuration\n\nThe application configuration is managed via the `config.py` file, which loads environment variables and sets default values.\n\n```python\nclass Config:\n    SECRET_KEY = os.getenv('SECRET_KEY', 'your_jwt_secret_key')\n    JWT_SECRET_KEY = os.getenv('JWT_SECRET_KEY', 'your_jwt_secret_key')\n    SQLALCHEMY_DATABASE_URI = os.getenv('DATABASE_URI', 'sqlite:///database.db')\n    JWT_ACCESS_TOKEN_EXPIRES = False  # Tokens never expire for simplicity\n    SQLALCHEMY_TRACK_MODIFICATIONS = False\n    # Celery Configuration\n    CELERY_BROKER_URL = os.getenv('CELERY_BROKER_URL', 'redis://localhost:6379/0')\n    CELERY_RESULT_BACKEND = os.getenv('CELERY_RESULT_BACKEND', 'redis://localhost:6379/0')\n    CELERY_INCLUDE = ['tasks']  # Provide directly as a list\n    CELERY_ACCEPT_CONTENT = ['json']\n    CELERY_TASK_SERIALIZER = 'json'\n    CELERY_RESULT_SERIALIZER = 'json'\n    CELERY_ENABLE_UTC = True\n    # Rate Limiting\n    RATELIMIT_DEFAULT = '200 per day;50 per hour'\n    # Security Headers via Flask-Talisman\n    TALISMAN_CONTENT_SECURITY_POLICY = None  # Customize as needed\n```\n\n**Key Configuration Parameters:**\n\n- **SECRET_KEY \u0026 JWT_SECRET_KEY**: Used for session management and JWT token encryption.\n- **DATABASE_URI**: Specifies the database location.\n- **Celery Settings**: Configure the broker and backend URLs, task serialization, and more.\n- **Rate Limiting**: Sets default rate limits for API requests.\n- **Security Headers**: Managed via Flask-Talisman.\n\n---\n\n## 🚀 Running the Server\n\n### 🔵 Starting the Flask Application\n\nEnsure that your virtual environment is activated and all dependencies are installed.\n\n```bash\npython app.py\n```\n\nBy default, the Flask server runs on `http://127.0.0.1:5000/`.\n\n### 🟢 Starting the Celery Worker\n\nIn a separate terminal, activate the virtual environment and start the Celery worker.\n\n```bash\npython celery_worker.py worker --loglevel=info\n```\n\n**Note**: Ensure that Redis is running before starting the Celery worker.\n\n---\n\n## 📡 API Endpoints\n\nThe API is organized into three main categories: **User**, **Order**, and **Admin**. Each category has its own set of endpoints to handle various functionalities.\n\n### 🔐 Authentication\n\nAll protected endpoints require a valid JWT access token. Include the token in the `Authorization` header as follows:\n\n```\nAuthorization: Bearer \u003caccess_token\u003e\n```\n\n---\n\n### 👤 User Endpoints\n\n#### 1. **Login**\n\n- **URL**: `/api/user/login`\n- **Method**: `POST`\n- **Description**: Authenticates a user using their phone number. If the user does not exist, it registers a new user.\n\n- **Request Body**:\n\n  ```json\n  {\n    \"phone_number\": \"01012345678\"\n  }\n  ```\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"message\": \"تم تسجيل الدخول بنجاح\",\n      \"data\": {\n        \"access_token\": \"\u003cjwt_token\u003e\",\n        \"user\": {\n          \"id\": 1,\n          \"phone_number\": \"01012345678\",\n          \"whatsapp_number\": null,\n          \"creation_date\": \"2024-11-23T02:50:35.367139\",\n          \"is_admin\": false,\n          \"is_blocked\": false,\n          \"orders\": [],\n          \"free_place_data_url\": \"https://example.com/api/free_place_data.json\"\n        }\n      }\n    }\n    ```\n\n  - **400 Bad Request**\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"رقم الهاتف مطلوب\"\n    }\n    ```\n\n  - **403 Forbidden**\n\n    ```json\n    {\n      \"code\": 403,\n      \"message\": \"حسابك محظور\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n#### 2. **Profile**\n\n- **URL**: `/api/user/profile`\n- **Method**: `GET`\n- **Description**: Retrieves the profile information of the authenticated user.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003caccess_token\u003e\n  ```\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"data\": {\n        \"id\": 1,\n        \"phone_number\": \"01012345678\",\n        \"whatsapp_number\": \"01087654321\",\n        \"creation_date\": \"2024-11-23T02:50:35.367139\",\n        \"is_admin\": false,\n        \"is_blocked\": false,\n        \"orders\": []\n      }\n    }\n    ```\n\n  - **404 Not Found**\n\n    ```json\n    {\n      \"code\": 404,\n      \"message\": \"المستخدم غير موجود\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n---\n\n### 📦 Order Endpoints\n\n#### 1. **Place Order**\n\n- **URL**: `/api/order/place`\n- **Method**: `POST`\n- **Description**: Allows authenticated users to place a new order.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003caccess_token\u003e\n  ```\n\n- **Request Body**:\n\n  ```json\n  {\n    \"place_name\": \"Cairo Office\",\n    \"business_details\": \"Details about the business.\",\n    \"selected_govs\": [\"Cairo\", \"Giza\"],\n    \"whatsapp_number\": \"01087654321\"\n  }\n  ```\n\n  **Parameters**:\n\n  - `place_name` (string, required): Name of the place.\n  - `business_details` (string, optional): Additional details about the business.\n  - `selected_govs` (array of strings, required): List of selected governorates. Can include \"كل محافظات مصر\" for all governorates.\n  - `whatsapp_number` (string, optional): User's WhatsApp number for notifications.\n\n- **Responses**:\n\n  - **201 Created**\n\n    ```json\n    {\n      \"code\": 201,\n      \"message\": \"تم تقديم الطلب بنجاح\",\n      \"data\": {\n        \"order_id\": 123\n      }\n    }\n    ```\n\n  - **400 Bad Request**\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"يرجى إدخال جميع الحقول المطلوبة\"\n    }\n    ```\n\n    Or for invalid governorates:\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"المحافظات المحددة غير صالحة\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n#### 2. **Order Status**\n\n- **URL**: `/api/order/status`\n- **Method**: `GET`\n- **Description**: Retrieves the status of all orders placed by the authenticated user.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003caccess_token\u003e\n  ```\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"data\": [\n        {\n          \"order_id\": 123,\n          \"user_id\": 1,\n          \"place_name\": \"Cairo Office\",\n          \"business_details\": \"Details about the business.\",\n          \"selected_govs\": [\"Cairo\", \"Giza\"],\n          \"total_price\": 5000.0,\n          \"status\": \"Processing\",\n          \"json_file_url\": \"https://example.com/api/orders/123/data.json\",\n          \"order_date\": \"2024-11-23T03:00:00.000000\",\n          \"queue_position\": 1\n        },\n        // More orders...\n      ]\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n---\n\n### 🛡️ Admin Endpoints\n\n**Note**: All admin endpoints require the authenticated user to have `is_admin` set to `true`.\n\n#### 1. **View Orders**\n\n- **URL**: `/api/admin/orders`\n- **Method**: `GET`\n- **Description**: Retrieves all orders, ordered by their queue position.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"data\": [\n        {\n          \"order_id\": 123,\n          \"user_id\": 1,\n          \"place_name\": \"Cairo Office\",\n          \"business_details\": \"Details about the business.\",\n          \"selected_govs\": [\"Cairo\", \"Giza\"],\n          \"total_price\": 5000.0,\n          \"status\": \"Processing\",\n          \"json_file_url\": \"https://example.com/api/orders/123/data.json\",\n          \"order_date\": \"2024-11-23T03:00:00.000000\",\n          \"queue_position\": 1\n        },\n        // More orders...\n      ]\n    }\n    ```\n\n  - **403 Forbidden**\n\n    ```json\n    {\n      \"code\": 403,\n      \"message\": \"غير مصرح\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n#### 2. **Accept Order**\n\n- **URL**: `/api/admin/orders/\u003corder_id\u003e/accept`\n- **Method**: `POST`\n- **Description**: Accepts an order and changes its status to \"Processing\". Initiates background processing.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Parameters**:\n\n  - `order_id` (integer, path parameter): ID of the order to accept.\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"message\": \"تم قبول الطلب وسيتم معالجته قريباً\"\n    }\n    ```\n\n  - **400 Bad Request**\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"لا يمكن قبول هذا الطلب في الوقت الحالي\"\n    }\n    ```\n\n  - **404 Not Found**\n\n    ```json\n    {\n      \"code\": 404,\n      \"message\": \"الطلب غير موجود\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n#### 3. **Complete Order**\n\n- **URL**: `/api/admin/orders/\u003corder_id\u003e/complete`\n- **Method**: `POST`\n- **Description**: Completes an order by updating its status to \"Completed\" and setting the JSON file URL.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Parameters**:\n\n  - `order_id` (integer, path parameter): ID of the order to complete.\n\n- **Request Body**:\n\n  ```json\n  {\n    \"json_file_url\": \"https://example.com/api/orders/123/data.json\"\n  }\n  ```\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"message\": \"تم إكمال الطلب بنجاح\"\n    }\n    ```\n\n  - **400 Bad Request**\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"يرجى توفير رابط ملف JSON\"\n    }\n    ```\n\n    Or for invalid status:\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"لا يمكن إكمال هذا الطلب في الوقت الحالي\"\n    }\n    ```\n\n  - **404 Not Found**\n\n    ```json\n    {\n      \"code\": 404,\n      \"message\": \"الطلب غير موجود\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n#### 4. **Manage Governorates**\n\n##### a. **Get Governorates**\n\n- **URL**: `/api/admin/governorates`\n- **Method**: `GET`\n- **Description**: Retrieves a list of all governorates.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"data\": [\n        {\n          \"id\": 1,\n          \"name\": \"Cairo\",\n          \"price\": 2000.0\n        },\n        // More governorates...\n      ]\n    }\n    ```\n\n  - **403 Forbidden**\n\n    ```json\n    {\n      \"code\": 403,\n      \"message\": \"غير مصرح\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n##### b. **Add Governorate**\n\n- **URL**: `/api/admin/governorates`\n- **Method**: `POST`\n- **Description**: Adds a new governorate.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Request Body**:\n\n  ```json\n  {\n    \"name\": \"Alexandria\",\n    \"price\": 1500.0\n  }\n  ```\n\n- **Responses**:\n\n  - **201 Created**\n\n    ```json\n    {\n      \"code\": 201,\n      \"message\": \"تم إضافة المحافظة بنجاح\"\n    }\n    ```\n\n  - **400 Bad Request**\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"يرجى إدخال جميع الحقول المطلوبة\"\n    }\n    ```\n\n    Or if the governorate already exists:\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"اسم المحافظة موجود مسبقاً\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n##### c. **Edit Governorate**\n\n- **URL**: `/api/admin/governorates/\u003cgov_id\u003e/edit`\n- **Method**: `POST`\n- **Description**: Edits an existing governorate.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Parameters**:\n\n  - `gov_id` (integer, path parameter): ID of the governorate to edit.\n\n- **Request Body**:\n\n  ```json\n  {\n    \"name\": \"New Alexandria\",\n    \"price\": 1800.0\n  }\n  ```\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"message\": \"تم تعديل المحافظة بنجاح\"\n    }\n    ```\n\n  - **400 Bad Request**\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"يرجى إدخال جميع الحقول المطلوبة\"\n    }\n    ```\n\n    Or if there's a name conflict:\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"اسم المحافظة موجود مسبقاً\"\n    }\n    ```\n\n  - **404 Not Found**\n\n    ```json\n    {\n      \"code\": 404,\n      \"message\": \"المحافظة غير موجودة\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n##### d. **Delete Governorate**\n\n- **URL**: `/api/admin/governorates/\u003cgov_id\u003e/delete`\n- **Method**: `POST`\n- **Description**: Deletes an existing governorate.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Parameters**:\n\n  - `gov_id` (integer, path parameter): ID of the governorate to delete.\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"message\": \"تم حذف المحافظة بنجاح\"\n    }\n    ```\n\n  - **404 Not Found**\n\n    ```json\n    {\n      \"code\": 404,\n      \"message\": \"المحافظة غير موجودة\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n#### 5. **Block User**\n\n- **URL**: `/api/admin/users/\u003cuser_id\u003e/block`\n- **Method**: `POST`\n- **Description**: Blocks a user from accessing the system.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Parameters**:\n\n  - `user_id` (integer, path parameter): ID of the user to block.\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"message\": \"تم حظر المستخدم بنجاح\"\n    }\n    ```\n\n  - **400 Bad Request**\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"المستخدم محظور بالفعل\"\n    }\n    ```\n\n  - **404 Not Found**\n\n    ```json\n    {\n      \"code\": 404,\n      \"message\": \"المستخدم غير موجود\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n#### 6. **Unblock User**\n\n- **URL**: `/api/admin/users/\u003cuser_id\u003e/unblock`\n- **Method**: `POST`\n- **Description**: Unblocks a previously blocked user.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Parameters**:\n\n  - `user_id` (integer, path parameter): ID of the user to unblock.\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"message\": \"تم إلغاء حظر المستخدم بنجاح\"\n    }\n    ```\n\n  - **400 Bad Request**\n\n    ```json\n    {\n      \"code\": 400,\n      \"message\": \"المستخدم غير محظور\"\n    }\n    ```\n\n  - **404 Not Found**\n\n    ```json\n    {\n      \"code\": 404,\n      \"message\": \"المستخدم غير موجود\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n#### 7. **View Users**\n\n- **URL**: `/api/admin/users`\n- **Method**: `GET`\n- **Description**: Retrieves a list of all users.\n\n- **Headers**:\n\n  ```\n  Authorization: Bearer \u003cadmin_access_token\u003e\n  ```\n\n- **Responses**:\n\n  - **200 OK**\n\n    ```json\n    {\n      \"code\": 200,\n      \"data\": [\n        {\n          \"id\": 1,\n          \"phone_number\": \"01012345678\",\n          \"whatsapp_number\": \"01087654321\",\n          \"creation_date\": \"2024-11-23T02:50:35.367139\",\n          \"is_admin\": false,\n          \"is_blocked\": false,\n          \"orders\": []\n        },\n        // More users...\n      ]\n    }\n    ```\n\n  - **403 Forbidden**\n\n    ```json\n    {\n      \"code\": 403,\n      \"message\": \"غير مصرح\"\n    }\n    ```\n\n  - **500 Internal Server Error**\n\n    ```json\n    {\n      \"code\": 500,\n      \"message\": \"Internal Server Error\"\n    }\n    ```\n\n---\n\n## 🗃️ Database Models\n\nThe application uses SQLAlchemy for ORM (Object-Relational Mapping). Below are the primary models used in the application.\n\n### 1. **User**\n\nRepresents a user of the application.\n\n- **Table Name**: `user`\n\n- **Fields**:\n\n  | Field Name        | Type        | Constraints                                               |\n  | ----------------- | ----------- | --------------------------------------------------------- |\n  | `id`              | Integer     | Primary Key                                               |\n  | `phone_number`    | String(20)  | Unique, Not Nullable                                       |\n  | `whatsapp_number` | String(20)  | Nullable                                                   |\n  | `creation_date`   | DateTime    | Default: Current UTC Time, Not Nullable                   |\n  | `is_admin`        | Boolean     | Default: `False`, Not Nullable                             |\n  | `is_blocked`      | Boolean     | Default: `False`, Not Nullable                             |\n  | `orders`          | Relationship | One-to-Many with `Order` (cascade: all, delete-orphan) |\n\n- **Methods**:\n\n  - `to_dict()`: Returns a dictionary representation of the user.\n\n    ```python\n    def to_dict(self):\n        return {\n            'id': self.id,\n            'phone_number': self.phone_number,\n            'whatsapp_number': self.whatsapp_number,\n            'creation_date': self.creation_date.isoformat(),\n            'is_admin': self.is_admin,\n            'is_blocked': self.is_blocked,\n            \"orders\": self.orders\n        }\n    ```\n\n### 2. **Order**\n\nRepresents an order placed by a user.\n\n- **Table Name**: `order`\n\n- **Fields**:\n\n  | Field Name        | Type        | Constraints                                               |\n  | ----------------- | ----------- | --------------------------------------------------------- |\n  | `id`              | Integer     | Primary Key                                               |\n  | `user_id`         | Integer     | Foreign Key to `user.id`, Not Nullable, Indexed           |\n  | `place_name`      | String(100) | Not Nullable                                               |\n  | `business_details`| Text        | Nullable                                                   |\n  | `selected_govs`   | Text        | Not Nullable (Comma-separated governorates)               |\n  | `total_price`     | Float       | Not Nullable                                               |\n  | `status`          | String(50)  | Default: \"Awaiting Payment Confirmation\", Not Nullable    |\n  | `json_file_url`   | String(200) | Nullable                                                   |\n  | `order_date`      | DateTime    | Default: Current UTC Time, Not Nullable                   |\n  | `queue_position`  | Integer     | Default: 0, Not Nullable, Indexed                         |\n\n- **Methods**:\n\n  - `to_dict()`: Returns a dictionary representation of the order.\n\n    ```python\n    def to_dict(self):\n        return {\n            'order_id': self.id,\n            'user_id': self.user_id,\n            'place_name': self.place_name,\n            'business_details': self.business_details,\n            'selected_govs': self.selected_govs.split(','),\n            'total_price': self.total_price,\n            'status': self.status,\n            'json_file_url': self.json_file_url,\n            'order_date': self.order_date.isoformat(),\n            'queue_position': self.queue_position\n        }\n    ```\n\n### 3. **Governorate**\n\nRepresents a governorate (administrative division).\n\n- **Table Name**: `governorate`\n\n- **Fields**:\n\n  | Field Name | Type       | Constraints                      |\n  | ---------- | ---------- | -------------------------------- |\n  | `id`       | Integer    | Primary Key                      |\n  | `name`     | String(50) | Unique, Not Nullable, Indexed     |\n  | `price`    | Float      | Not Nullable                      |\n\n- **Methods**:\n\n  - `to_dict()`: Returns a dictionary representation of the governorate.\n\n    ```python\n    def to_dict(self):\n        return {\n            'id': self.id,\n            'name': self.name,\n            'price': self.price\n        }\n    ```\n\n---\n\n## 🔄 Background Tasks\n\n**Celery** is used for handling asynchronous background tasks, such as processing orders and sending WhatsApp notifications.\n\n### 1. **Process Order Task**\n\n- **Function**: `process_order_task(order_id)`\n- **Description**: Processes an order by generating a JSON file URL, updating the order status, and sending a WhatsApp notification to the user.\n\n- **Workflow**:\n  1. **Fetch Order**: Retrieve the order from the database using `order_id`.\n  2. **Simulate Processing**: Perform data preparation (e.g., data analysis, report generation). Currently simulated with a delay.\n  3. **Generate JSON URL**: Create a URL pointing to the generated JSON file.\n  4. **Update Status**: Change the order status to \"Completed\".\n  5. **Commit Changes**: Save the updates to the database.\n  6. **Send Notification**: Notify the user via WhatsApp about the order completion.\n\n- **Error Handling**:\n  - **Retries**: Retries the task up to 3 times in case of failure with a delay of 60 seconds between attempts.\n  - **Failure Handling**: If all retries fail, updates the order status to \"Failed\" and notifies the user about the failure.\n\n- **Code Snippet**:\n\n  ```python\n  @celery.task(bind=True, max_retries=3, default_retry_delay=60)\n  def process_order_task(self, order_id):\n      try:\n          with celery.flask_app.app_context():\n              order = Order.query.get(order_id)\n              if not order:\n                  logger.error(f\"Order not found: order_id={order_id}\")\n                  return\n\n              logger.info(f\"Processing order_id={order_id}\")\n\n              # Simulate data preparation\n              time.sleep(5)  # Replace with actual processing logic\n\n              # Generate JSON file URL\n              order.json_file_url = f\"https://example.com/api/orders/{order.id}/data.json\"\n\n              # Update order status to 'Completed'\n              order.status = 'Completed'\n              db.session.commit()\n              logger.info(f\"Order completed: order_id={order_id}\")\n\n              # Send WhatsApp notification to the user\n              message = f\"تم إكمال طلبك رقم {order.id}. يمكنك الآن الوصول إلى البيانات من خلال رابط JSON الخاص بك.\"\n              send_whatsapp_message(order.user.whatsapp_number or order.user.phone_number, message)\n              logger.info(f\"WhatsApp notification sent to user_id={order.user_id} for order_id={order_id}\")\n\n      except Exception as exc:\n          logger.error(f\"Error processing order_id={order_id}: {exc}\")\n          try:\n              self.retry(exc=exc)\n          except self.MaxRetriesExceededError:\n              with celery.flask_app.app_context():\n                  order = Order.query.get(order_id)\n                  if order:\n                      order.status = 'Failed'\n                      db.session.commit()\n                      logger.error(f\"Order failed after retries: order_id={order_id}\")\n\n                  # Notify user about the failure\n                  user = User.query.get(order.user_id)\n                  if user:\n                      failure_message = f\"عذراً، حدث خطأ أثناء معالجة طلبك رقم {order.id}. يرجى المحاولة مرة أخرى لاحقاً.\"\n                      send_whatsapp_message(user.whatsapp_number or user.phone_number, failure_message)\n                      logger.info(f\"WhatsApp failure notification sent to user_id={user.id} for order_id={order.id}\")\n  ```\n\n---\n\n## 🛑 Error Handling\n\nCustom error handlers are registered to provide consistent and informative error responses.\n\n### 1. **404 Not Found**\n\n- **Trigger**: When a requested resource is not found.\n- **Response**:\n\n  ```json\n  {\n    \"code\": 404,\n    \"message\": \"Not Found\"\n  }\n  ```\n\n### 2. **500 Internal Server Error**\n\n- **Trigger**: When an unexpected server error occurs.\n- **Response**:\n\n  ```json\n  {\n    \"code\": 500,\n    \"message\": \"Internal Server Error\"\n  }\n  ```\n\n**Logging**: All errors are logged with detailed information to aid in debugging and monitoring.\n\n---\n\n## ⏱️ Rate Limiting\n\n**Flask-Limiter** is implemented to prevent abuse by limiting the number of requests a user can make within a specified timeframe.\n\n### 🛠️ Configuration\n\n- **Default Limits**: `200 per day;50 per hour`\n- **Key Function**: Custom key function that uses the user's identity (if authenticated) or the remote IP address.\n\n### 🎯 Features\n\n- **User-Based Limiting**: Authenticated users are limited based on their user ID.\n- **IP-Based Limiting**: Unauthenticated requests are limited based on the client's IP address.\n- **Whitelisting**: Local requests (e.g., from `127.0.0.1`) are exempt from rate limiting.\n\n### 🔍 Implementation\n\nThe rate limiter is initialized in `utils/rate_limiter.py`:\n\n```python\nlimiter = Limiter(\n    key_func=get_user_identifier,\n    default_limits=[\"200 per day\", \"50 per hour\"],\n    storage_uri=\"memory://\",  # Replace with Redis for production\n)\n```\n\n**Note**: For production environments, it's recommended to use a persistent storage backend like Redis instead of in-memory storage.\n\n---\n\n## 🔒 Security\n\nSecurity best practices are implemented to protect the application from common vulnerabilities.\n\n### 1. **JWT Authentication**\n\n- **Library**: Flask-JWT-Extended\n- **Usage**: Provides secure token-based authentication.\n- **Token Management**: Tokens do not expire for simplicity (`JWT_ACCESS_TOKEN_EXPIRES = False`). Consider enabling expiration for enhanced security.\n\n### 2. **Flask-Talisman**\n\n- **Purpose**: Sets various HTTP security headers to protect against common web vulnerabilities.\n- **Configuration**: Managed in `utils/security.py`.\n\n  ```python\n  csp = {\n      'default-src': [\n          \"'self'\",\n          'https://stackpath.bootstrapcdn.com',\n          'https://cdnjs.cloudflare.com',\n          # Add other trusted sources\n      ]\n  }\n  Talisman(app, content_security_policy=csp)\n  ```\n\n### 3. **Rate Limiting**\n\n- Prevents brute-force attacks and API abuse.\n\n### 4. **Input Validation**\n\n- Ensures that all required fields are provided and valid in API requests.\n\n### 5. **Whitelisted IPs**\n\n- Certain IP addresses (e.g., localhost) can be exempted from rate limiting.\n\n---\n\n## 📜 Database Migrations\n\n**Flask-Migrate** and **Alembic** are used to handle database migrations.\n\n### 🛠️ Setup\n\n1. **Initialize Migrations**\n\n   ```bash\n   flask db init\n   ```\n\n2. **Create a Migration Script**\n\n   ```bash\n   flask db migrate -m \"Initial migration.\"\n   ```\n\n3. **Apply Migrations**\n\n   ```bash\n   flask db upgrade\n   ```\n\n### 📂 Migrations Directory\n\n- **env.py**: Configures the migration environment.\n- **versions/**: Contains migration scripts with unique revision IDs.\n\n**Example Migration Script** (`migrations/versions/4fdcd22313de_.py`):\n\n```python\ndef upgrade():\n    # Commands to create tables\n    op.create_table('governorate', ...)\n    op.create_table('user', ...)\n    op.create_table('order', ...)\n    ...\n\ndef downgrade():\n    # Commands to drop tables\n    op.drop_table('order')\n    op.drop_table('user')\n    op.drop_table('governorate')\n    ...\n```\n\n**Note**: Always review and adjust auto-generated migration scripts as needed before applying them.\n\n---\n\n## 📚 Dependencies\n\nAll project dependencies are listed in the `requirements.txt` file.\n\n### 📌 Core Dependencies\n\n- **Flask==2.0.1**: Web framework.\n- **Flask-JWT-Extended==4.3.1**: JWT authentication.\n- **Flask-Limiter==2.1.0**: Rate limiting.\n- **Flask-Migrate==3.1.0**: Database migrations.\n- **Flask-SQLAlchemy==2.5.1**: ORM.\n- **Flask-Cors==3.0.10**: Cross-Origin Resource Sharing.\n- **Flask-Talisman==0.8.1**: Security headers.\n- **Celery==5.2.3**: Asynchronous task queue.\n- **redis==4.1.0**: Redis client for Celery.\n- **python-dotenv==0.21.0**: Environment variable management.\n- **Werkzeug==2.0.3**: WSGI utility library.\n- **SQLAlchemy==1.4.46**: SQL toolkit and ORM.\n- **setuptools==59.6.0**, **click==8.1.7**, **click-didyoumean==0.3.1**, **click-plugins==1.1.1**: CLI utilities.\n\n### 📌 Development Dependencies\n\n- **Alembic**: Database migrations (included with Flask-Migrate).\n\n---\n\n## 🔧 Utilities\n\n### 1. **Authentication Decorator**\n\nLocated in `utils/auth.py`, the `admin_required` decorator ensures that only admin users can access certain endpoints.\n\n```python\ndef admin_required(fn):\n    @wraps(fn)\n    def wrapper(*args, **kwargs):\n        try:\n            user_id = get_jwt_identity()\n            user = User.query.get(user_id)\n            if not user or not user.is_admin:\n                logger.warning(f\"Unauthorized admin access attempt by user_id={user_id}\")\n                return {'code': 403, 'message': 'غير مصرح'}, 403\n        except Exception as e:\n            logger.error(f\"Error in admin_required decorator: {e}\")\n            return {'code': 500, 'message': 'Internal Server Error'}, 500\n        return fn(*args, **kwargs)\n    return wrapper\n```\n\n### 2. **Export Data to CSV**\n\nLocated in `utils/export_data.py`, the `export_to_csv` function allows exporting data as a CSV file.\n\n```python\ndef export_to_csv(data):\n    if not data:\n        logger.info(\"No data available to export.\")\n        return Response(\"No data available to export.\", mimetype='text/csv')\n\n    headers = data[0].keys()\n\n    def generate():\n        yield ','.join(headers) + '\\n'\n        for row in data:\n            yield ','.join([str(row.get(header, \"\")) for header in headers]) + '\\n'\n\n    response = Response(generate(), mimetype='text/csv')\n    response.headers.set(\"Content-Disposition\", \"attachment\", filename=\"places_data.csv\")\n    logger.info(\"CSV export initiated.\")\n    return response\n```\n\n### 3. **Rate Limiter**\n\nLocated in `utils/rate_limiter.py`, handles rate limiting based on user identity or IP address.\n\n```python\nlimiter = Limiter(\n    key_func=get_user_identifier,\n    default_limits=[\"200 per day\", \"50 per hour\"],\n    storage_uri=\"memory://\",  # Replace with Redis for production\n)\n```\n\n### 4. **Security Initialization**\n\nLocated in `utils/security.py`, initializes security headers using Flask-Talisman.\n\n```python\ndef init_security(app):\n    try:\n        csp = {\n            'default-src': [\n                \"'self'\",\n                'https://stackpath.bootstrapcdn.com',\n                'https://cdnjs.cloudflare.com',\n                # Add other trusted sources\n            ]\n        }\n        Talisman(app, content_security_policy=csp)\n        logger.info(\"Security headers initialized with Flask-Talisman.\")\n    except Exception as e:\n        logger.error(f\"Error initializing security headers: {e}\")\n```\n\n### 5. **WhatsApp Messaging**\n\nLocated in `utils/whatsapp.py`, handles sending WhatsApp messages via Twilio's API.\n\n```python\ndef send_whatsapp_message(phone_number, message):\n    pass\n    \"\"\"\n    Send a WhatsApp message using Twilio's WhatsApp API.\n\n    Args:\n        phone_number (str): Recipient's phone number in E.164 format (e.g., +2010XXXXXXX).\n        message (str): The message to send.\n\n    try:\n        account_sid = os.getenv('TWILIO_ACCOUNT_SID')\n        auth_token = os.getenv('TWILIO_AUTH_TOKEN')\n        whatsapp_from = os.getenv('TWILIO_WHATSAPP_NUMBER')  # e.g., 'whatsapp:+14155238886'\n\n        if not all([account_sid, auth_token, whatsapp_from]):\n            logger.error(\"Twilio credentials are not properly set in environment variables.\")\n            return\n\n        client = Client(account_sid, auth_token)\n        message = client.messages.create(\n            body=message,\n            from_=whatsapp_from,\n            to=f'whatsapp:{phone_number}'\n        )\n        logger.info(f\"WhatsApp message sent to {phone_number}: SID={message.sid}\")\n    except Exception as e:\n        logger.error(f\"Failed to send WhatsApp message to {phone_number}: {e}\")\n    \"\"\"\n```\n\n**Note**: The actual implementation is commented out. To enable WhatsApp messaging, uncomment and configure the Twilio client as shown in the commented section.\n\n---\n\n## 📁 Project Structure\n\nHere's a breakdown of the project's files and directories:\n\n```plaintext\nBackend/\n├── app.py\n├── celery_app.py\n├── celery_worker.py\n├── config.py\n├── extensions.py\n├── models.py\n├── requirements.txt\n├── tasks.py\n├── controllers/\n│   ├── admin_controller.py\n│   ├── order_controller.py\n│   └── user_controller.py\n├── migrations/\n│   ├── env.py\n│   └── versions/\n│       └── 4fdcd22313de_.py\n└── utils/\n    ├── auth.py\n    ├── export_data.py\n    ├── rate_limiter.py\n    ├── security.py\n    └── whatsapp.py\n```\n\n### 📂 Detailed Breakdown\n\n1. **app.py**\n   - Initializes the Flask application.\n   - Configures extensions (SQLAlchemy, Migrate, JWT, CORS, Limiter, Security).\n   - Registers blueprints for user, order, and admin controllers.\n   - Defines routes for serving HTML pages and static files.\n   - Registers custom error handlers.\n\n2. **celery_app.py \u0026 celery_worker.py**\n   - Configures and initializes Celery with the Flask application context.\n   - `celery_worker.py` serves as the entry point for running Celery workers.\n\n3. **config.py**\n   - Manages application configuration, loading from environment variables.\n\n4. **extensions.py**\n   - Initializes Flask extensions like SQLAlchemy.\n\n5. **models.py**\n   - Defines the database models: User, Order, Governorate.\n\n6. **tasks.py**\n   - Contains Celery tasks, such as processing orders asynchronously.\n\n7. **controllers/**\n   - **user_controller.py**: Handles user-related endpoints (login, profile).\n   - **order_controller.py**: Manages order-related endpoints (place order, order status).\n   - **admin_controller.py**: Manages admin-related endpoints (view orders, manage governorates, manage users).\n\n8. **migrations/**\n   - Manages database migrations using Alembic.\n   - **env.py**: Configures the migration environment.\n   - **versions/**: Contains migration scripts with unique revision IDs.\n\n9. **utils/**\n   - **auth.py**: Authentication decorators.\n   - **export_data.py**: Utility for exporting data to CSV.\n   - **rate_limiter.py**: Configures rate limiting.\n   - **security.py**: Initializes security headers.\n   - **whatsapp.py**: Handles WhatsApp messaging via Twilio.\n\n---\n\n## ✍️ Contributing\n\nWe appreciate your interest in contributing! Here's how you can help:\n\n1. **Fork the Repository**\n\n   Click on **Fork** at the top right corner of this page.\n\n2. **Clone Your Fork**\n\n   ```bash\n   git clone https://github.com/kariemSeiam/GeoEgy/.git\n   ```\n\n3. **Create a New Branch**\n\n   ```bash\n   git checkout -b feature/your-feature-name\n   ```\n\n4. **Make Your Changes**\n\n   Implement your feature or fix.\n\n5. **Commit and Push**\n\n   ```bash\n   git commit -m \"Add new feature\"\n   git push origin feature/your-feature-name\n   ```\n\n6. **Open a Pull Request**\n\n   Submit your pull request against the **main** branch with a detailed description.\n\n---\n\n## 📄 License\n\nThis project is released under the [MIT License](LICENSE).\n\n---\n\n## 📫 Contact\n\nHave questions or suggestions? We'd love to hear from you!\n\n- **Email**: [your.email@example.com](mailto:your.email@example.com)\n- **LinkedIn**: [Your Name](https://www.linkedin.com/in/yourprofile/)\n- **GitHub**: [yourusername](https://github.com/yourusername)\n\n---\n\n## 🙌 Acknowledgments\n\nA heartfelt thank you to all contributors and the following resources:\n\n- **[Flask](https://flask.palletsprojects.com/)**\n- **[Celery](https://docs.celeryproject.org/)**\n- **[Twilio](https://www.twilio.com/)**\n- **[Flask-JWT-Extended](https://flask-jwt-extended.readthedocs.io/)**\n- **[Flask-Limiter](https://flask-limiter.readthedocs.io/)**\n- **[Flask-Migrate](https://flask-migrate.readthedocs.io/)**\n- **[SQLAlchemy](https://www.sqlalchemy.org/)**\n- **[Alembic](https://alembic.sqlalchemy.org/)**\n- **Community Contributors**: For their valuable input and support.\n\n---\n\n## 💡 Final Thoughts\n\nThe **GeoEgy** backend is a powerful and flexible system designed to handle complex geographical data processing with ease and security. By following this documentation, developers and contributors can effectively set up, understand, and enhance the application's functionality.\n\n---\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkariemseiam%2Fgeoegy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkariemseiam%2Fgeoegy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkariemseiam%2Fgeoegy/lists"}