WealthFromAI commited on
Commit
334de19
·
verified ·
1 Parent(s): 17a1dfe

FORGE-X: Upload source (2c211a42-api.zip)

Browse files
2c211a42/Dockerfile ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 8000
11
+
12
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
13
+ CMD python -c "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"
14
+
15
+ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]
2c211a42/README.md ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API
2
+
3
+ > Complex scheduling logic, conflict detection, and multi-timezone support. Calendly-level scheduling in your own app.
4
+
5
+ ## Features
6
+
7
+ - Full REST API
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ # 1. Install dependencies
13
+ pip install -r requirements.txt
14
+
15
+ # 2. Configure environment
16
+ cp .env.example .env
17
+ # Edit .env with your settings
18
+
19
+ # 3. Run locally
20
+ uvicorn main:app --reload --port 8000
21
+
22
+ # 4. View interactive docs
23
+ open http://localhost:8000/docs
24
+ ```
25
+
26
+ ## Docker Deployment
27
+
28
+ ```bash
29
+ # Build and run
30
+ docker compose up -d
31
+
32
+ # Check health
33
+ curl http://localhost:8000/health
34
+ ```
35
+
36
+ ## Authentication
37
+
38
+ Get a token first:
39
+ ```bash
40
+ curl -X POST "http://localhost:8000/auth/token?username=admin&password=admin123"
41
+ ```
42
+
43
+ Use the token in subsequent requests:
44
+ ```bash
45
+ curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8000/items
46
+ ```
47
+
48
+ ## API Endpoints
49
+
50
+ | Method | Path | Description |
51
+ |--------|------|-------------|
52
+ | GET | `/health` | System health |
53
+ | POST | `/auth/token` | Get JWT token |
54
+ | GET | `/items` | List all items |
55
+ | POST | `/items` | Create item |
56
+ | GET | `/items/{id}` | Get item |
57
+ | PATCH | `/items/{id}` | Update item |
58
+ | DELETE | `/items/{id}` | Delete item |
59
+ | GET | `/stats` | API statistics |
60
+
61
+ Full interactive docs: `http://localhost:8000/docs`
62
+
63
+ ## Rate Limits
64
+
65
+ | Endpoint | Limit |
66
+ |----------|-------|
67
+ | `/auth/token` | 10/minute |
68
+ | `GET /items` | 60/minute |
69
+ | `POST /items` | 30/minute |
70
+ | `DELETE /items` | 20/minute |
71
+
72
+ ## Running Tests
73
+
74
+ ```bash
75
+ pip install pytest httpx
76
+ pytest tests/ -v
77
+ ```
78
+
79
+ ## Production Notes
80
+
81
+ - Change `SECRET_KEY` in `.env` before deploying
82
+ - Replace in-memory `_db` with a real database
83
+ - Add proper user management to `auth.py`
84
+ - Configure `ALLOWED_ORIGINS` for CORS
85
+ - Use Nginx/Traefik as reverse proxy
86
+
87
+ ## License
88
+
89
+ MIT
2c211a42/auth.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simple JWT auth — swap for OAuth2/API keys in production.
3
+ """
4
+ import os
5
+ from datetime import datetime, timedelta, timezone
6
+ from typing import Optional
7
+ from fastapi import Depends, HTTPException, status
8
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
9
+ from jose import JWTError, jwt
10
+ from passlib.context import CryptContext
11
+
12
+ SECRET_KEY = os.getenv("SECRET_KEY", "changeme-in-production-please")
13
+ ALGORITHM = os.getenv("ALGORITHM", "HS256")
14
+ EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30"))
15
+
16
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
17
+ security = HTTPBearer()
18
+
19
+ # Demo users — replace with real DB lookup
20
+ USERS: dict[str, str] = {
21
+ "admin": pwd_context.hash("admin123"),
22
+ "demo": pwd_context.hash("demo123"),
23
+ }
24
+
25
+
26
+ def verify_password(username: str, password: str) -> bool:
27
+ hashed = USERS.get(username)
28
+ if not hashed:
29
+ return False
30
+ return pwd_context.verify(password, hashed)
31
+
32
+
33
+ def create_access_token(data: dict) -> str:
34
+ to_encode = data.copy()
35
+ expire = datetime.now(timezone.utc) + timedelta(minutes=EXPIRE_MINUTES)
36
+ to_encode["exp"] = expire
37
+ return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
38
+
39
+
40
+ def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)) -> str:
41
+ token = credentials.credentials
42
+ try:
43
+ payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
44
+ username: Optional[str] = payload.get("sub")
45
+ if not username:
46
+ raise HTTPException(status_code=401, detail="Invalid token payload")
47
+ return username
48
+ except JWTError:
49
+ raise HTTPException(
50
+ status_code=status.HTTP_401_UNAUTHORIZED,
51
+ detail="Invalid or expired token",
52
+ headers={"WWW-Authenticate": "Bearer"},
53
+ )
2c211a42/docker-compose.yml ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+ services:
3
+ api:
4
+ build: .
5
+ ports:
6
+ - "8000:8000"
7
+ env_file: .env
8
+ restart: unless-stopped
9
+ volumes:
10
+ - ./data:/app/data
2c211a42/main.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API — REST API Service
3
+ Complex scheduling logic, conflict detection, and multi-timezone support. Calendly-level scheduling in your own app.
4
+
5
+ Features:
6
+
7
+ """
8
+ import os
9
+ from contextlib import asynccontextmanager
10
+ from typing import Optional, List
11
+ from datetime import datetime, timezone
12
+
13
+ from fastapi import FastAPI, Depends, HTTPException, Request, status
14
+ from fastapi.middleware.cors import CORSMiddleware
15
+ from fastapi.responses import JSONResponse
16
+ from slowapi import Limiter
17
+ from slowapi.util import get_remote_address
18
+ from slowapi.errors import RateLimitExceeded
19
+ from dotenv import load_dotenv
20
+
21
+ from models import ItemCreate, ItemUpdate, ItemResponse, HealthResponse
22
+ from auth import get_current_user, create_access_token, verify_password
23
+
24
+ load_dotenv()
25
+
26
+ limiter = Limiter(key_func=get_remote_address)
27
+ _db: dict = {} # In-memory store — swap for real DB in production
28
+
29
+
30
+ @asynccontextmanager
31
+ async def lifespan(app: FastAPI):
32
+ print(f"{app.title} starting up...")
33
+ yield
34
+ print(f"{app.title} shutting down...")
35
+
36
+
37
+ app = FastAPI(
38
+ title="Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API",
39
+ description="Complex scheduling logic, conflict detection, and multi-timezone support. Calendly-level scheduling in your own app.",
40
+ version="1.0.0",
41
+ lifespan=lifespan,
42
+ docs_url="/docs",
43
+ redoc_url="/redoc",
44
+ openapi_url="/openapi.json",
45
+ )
46
+
47
+ app.state.limiter = limiter
48
+
49
+ app.add_middleware(
50
+ CORSMiddleware,
51
+ allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","),
52
+ allow_credentials=True,
53
+ allow_methods=["*"],
54
+ allow_headers=["*"],
55
+ )
56
+
57
+
58
+ @app.exception_handler(RateLimitExceeded)
59
+ async def rate_limit_handler(request: Request, exc: RateLimitExceeded):
60
+ return JSONResponse(
61
+ status_code=status.HTTP_429_TOO_MANY_REQUESTS,
62
+ content={"error": "Rate limit exceeded", "detail": str(exc)},
63
+ )
64
+
65
+
66
+ @app.exception_handler(Exception)
67
+ async def global_exception_handler(request: Request, exc: Exception):
68
+ return JSONResponse(
69
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
70
+ content={"error": "Internal server error", "detail": str(exc)},
71
+ )
72
+
73
+
74
+ # ── Health ────────────────────────────────────────────────────────────────────
75
+
76
+ @app.get("/health", response_model=HealthResponse, tags=["System"])
77
+ async def health():
78
+ return {
79
+ "status": "healthy",
80
+ "service": "Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API",
81
+ "version": "1.0.0",
82
+ "timestamp": datetime.now(timezone.utc).isoformat(),
83
+ "items_count": len(_db),
84
+ }
85
+
86
+
87
+ # ── Auth ──────────────────────────────────────────────────────────────────────
88
+
89
+ @app.post("/auth/token", tags=["Auth"], summary="Get API token")
90
+ @limiter.limit("10/minute")
91
+ async def login(request: Request, username: str, password: str):
92
+ if not verify_password(username, password):
93
+ raise HTTPException(status_code=401, detail="Invalid credentials")
94
+ token = create_access_token({"sub": username})
95
+ return {"access_token": token, "token_type": "bearer"}
96
+
97
+
98
+ # ── CRUD ──────────────────────────────────────────────────────────────────────
99
+
100
+ @app.get("/items", response_model=List[ItemResponse], tags=["Items"])
101
+ @limiter.limit("60/minute")
102
+ async def list_items(
103
+ request: Request,
104
+ skip: int = 0,
105
+ limit: int = 50,
106
+ search: Optional[str] = None,
107
+ current_user: str = Depends(get_current_user),
108
+ ):
109
+ items = list(_db.values())
110
+ if search:
111
+ items = [i for i in items if search.lower() in i.get("name", "").lower()]
112
+ return items[skip : skip + limit]
113
+
114
+
115
+ @app.post("/items", response_model=ItemResponse, status_code=201, tags=["Items"])
116
+ @limiter.limit("30/minute")
117
+ async def create_item(
118
+ request: Request,
119
+ item: ItemCreate,
120
+ current_user: str = Depends(get_current_user),
121
+ ):
122
+ item_id = f"item_{len(_db) + 1:06d}"
123
+ record = {
124
+ "id": item_id,
125
+ **item.model_dump(),
126
+ "created_by": current_user,
127
+ "created_at": datetime.now(timezone.utc).isoformat(),
128
+ "updated_at": datetime.now(timezone.utc).isoformat(),
129
+ }
130
+ _db[item_id] = record
131
+ return record
132
+
133
+
134
+ @app.get("/items/{item_id}", response_model=ItemResponse, tags=["Items"])
135
+ @limiter.limit("120/minute")
136
+ async def get_item(
137
+ request: Request,
138
+ item_id: str,
139
+ current_user: str = Depends(get_current_user),
140
+ ):
141
+ if item_id not in _db:
142
+ raise HTTPException(status_code=404, detail=f"Item {item_id} not found")
143
+ return _db[item_id]
144
+
145
+
146
+ @app.patch("/items/{item_id}", response_model=ItemResponse, tags=["Items"])
147
+ @limiter.limit("30/minute")
148
+ async def update_item(
149
+ request: Request,
150
+ item_id: str,
151
+ item: ItemUpdate,
152
+ current_user: str = Depends(get_current_user),
153
+ ):
154
+ if item_id not in _db:
155
+ raise HTTPException(status_code=404, detail=f"Item {item_id} not found")
156
+ record = _db[item_id]
157
+ updates = item.model_dump(exclude_unset=True)
158
+ record.update({**updates, "updated_at": datetime.now(timezone.utc).isoformat()})
159
+ _db[item_id] = record
160
+ return record
161
+
162
+
163
+ @app.delete("/items/{item_id}", status_code=204, tags=["Items"])
164
+ @limiter.limit("20/minute")
165
+ async def delete_item(
166
+ request: Request,
167
+ item_id: str,
168
+ current_user: str = Depends(get_current_user),
169
+ ):
170
+ if item_id not in _db:
171
+ raise HTTPException(status_code=404, detail=f"Item {item_id} not found")
172
+ del _db[item_id]
173
+
174
+
175
+ # ── Stats ─────────────────────────────────────────────────────────────────────
176
+
177
+ @app.get("/stats", tags=["System"])
178
+ @limiter.limit("30/minute")
179
+ async def stats(request: Request, current_user: str = Depends(get_current_user)):
180
+ return {
181
+ "total_items": len(_db),
182
+ "service": "Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API",
183
+ "niche": "cloud_infrastructure",
184
+ }
2c211a42/models.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional
3
+ from datetime import datetime
4
+
5
+
6
+ class ItemCreate(BaseModel):
7
+ name: str = Field(..., min_length=1, max_length=200, description="Item name")
8
+ description: Optional[str] = Field(None, max_length=2000)
9
+ category: Optional[str] = Field(None, max_length=100)
10
+ tags: list[str] = Field(default_factory=list)
11
+ metadata: dict = Field(default_factory=dict)
12
+
13
+ model_config = {"json_schema_extra": {"example": {
14
+ "name": "Sample Item",
15
+ "description": "A sample cloud_infrastructure item",
16
+ "category": "default",
17
+ "tags": ["cloud_infrastructure"],
18
+ "metadata": {},
19
+ }}}
20
+
21
+
22
+ class ItemUpdate(BaseModel):
23
+ name: Optional[str] = Field(None, min_length=1, max_length=200)
24
+ description: Optional[str] = Field(None, max_length=2000)
25
+ category: Optional[str] = None
26
+ tags: Optional[list[str]] = None
27
+ metadata: Optional[dict] = None
28
+
29
+
30
+ class ItemResponse(BaseModel):
31
+ id: str
32
+ name: str
33
+ description: Optional[str]
34
+ category: Optional[str]
35
+ tags: list[str]
36
+ metadata: dict
37
+ created_by: str
38
+ created_at: str
39
+ updated_at: str
40
+
41
+
42
+ class HealthResponse(BaseModel):
43
+ status: str
44
+ service: str
45
+ version: str
46
+ timestamp: str
47
+ items_count: int
2c211a42/requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi>=0.110.0
2
+ uvicorn[standard]>=0.27.0
3
+ pydantic>=2.0.0
4
+ python-jose[cryptography]>=3.3.0
5
+ passlib[bcrypt]>=1.7.4
6
+ python-multipart>=0.0.9
7
+ slowapi>=0.1.9
8
+ python-dotenv>=1.0.0
9
+ httpx>=0.27.0
2c211a42/tests/__init__.py ADDED
File without changes
2c211a42/tests/test_main.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Tests for Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API API.
3
+ """
4
+ import pytest
5
+ from fastapi.testclient import TestClient
6
+ from main import app
7
+
8
+ client = TestClient(app)
9
+
10
+
11
+ def test_health():
12
+ r = client.get("/health")
13
+ assert r.status_code == 200
14
+ data = r.json()
15
+ assert data["status"] == "healthy"
16
+ assert "version" in data
17
+
18
+
19
+ def test_unauthorized():
20
+ r = client.get("/items")
21
+ assert r.status_code == 403
22
+
23
+
24
+ def test_login():
25
+ r = client.post("/auth/token", params={"username": "admin", "password": "admin123"})
26
+ assert r.status_code == 200
27
+ assert "access_token" in r.json()
28
+
29
+
30
+ def _auth_headers():
31
+ r = client.post("/auth/token", params={"username": "admin", "password": "admin123"})
32
+ token = r.json()["access_token"]
33
+ return {"Authorization": f"Bearer {token}"}
34
+
35
+
36
+ def test_create_item():
37
+ headers = _auth_headers()
38
+ payload = {"name": "Test Item", "description": "A test", "tags": ["test"]}
39
+ r = client.post("/items", json=payload, headers=headers)
40
+ assert r.status_code == 201
41
+ data = r.json()
42
+ assert data["name"] == "Test Item"
43
+ assert "id" in data
44
+
45
+
46
+ def test_list_items():
47
+ headers = _auth_headers()
48
+ r = client.get("/items", headers=headers)
49
+ assert r.status_code == 200
50
+ assert isinstance(r.json(), list)
51
+
52
+
53
+ def test_get_item():
54
+ headers = _auth_headers()
55
+ # Create
56
+ payload = {"name": "Get Test", "tags": []}
57
+ r = client.post("/items", json=payload, headers=headers)
58
+ item_id = r.json()["id"]
59
+ # Get
60
+ r = client.get(f"/items/{item_id}", headers=headers)
61
+ assert r.status_code == 200
62
+ assert r.json()["id"] == item_id
63
+
64
+
65
+ def test_update_item():
66
+ headers = _auth_headers()
67
+ payload = {"name": "Update Test", "tags": []}
68
+ r = client.post("/items", json=payload, headers=headers)
69
+ item_id = r.json()["id"]
70
+ r = client.patch(f"/items/{item_id}", json={"name": "Updated"}, headers=headers)
71
+ assert r.status_code == 200
72
+ assert r.json()["name"] == "Updated"
73
+
74
+
75
+ def test_delete_item():
76
+ headers = _auth_headers()
77
+ payload = {"name": "Delete Test", "tags": []}
78
+ r = client.post("/items", json=payload, headers=headers)
79
+ item_id = r.json()["id"]
80
+ r = client.delete(f"/items/{item_id}", headers=headers)
81
+ assert r.status_code == 204
82
+ r = client.get(f"/items/{item_id}", headers=headers)
83
+ assert r.status_code == 404
84
+
85
+
86
+ def test_stats():
87
+ headers = _auth_headers()
88
+ r = client.get("/stats", headers=headers)
89
+ assert r.status_code == 200
90
+ assert "total_items" in r.json()
91
+
92
+
93
+ def test_openapi_docs():
94
+ r = client.get("/openapi.json")
95
+ assert r.status_code == 200
96
+ assert "openapi" in r.json()
README.md CHANGED
@@ -1,15 +1,21 @@
1
  ---
2
  title: "Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API"
3
- emoji: "🤖"
4
  colorFrom: blue
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: "4.44.0"
8
  app_file: app.py
9
  pinned: false
10
  license: mit
 
 
 
 
 
 
 
11
  ---
12
-
13
  # Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API
14
 
15
  Stop overpaying for idle cloud resources across AWS, GCP, and Azure—this API automates scheduling so you only pay for what you actually use.
@@ -61,11 +67,28 @@ Absolutely – no contracts. You keep the API key and access to the dashboard ev
61
 
62
  **Get your API key now and start saving hundreds on cloud costs with one simple integration.**
63
 
64
- ## Pricing
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
  - **Demo**: Free on Hugging Face Spaces
67
  - **Full Source Code**: $42.23
 
 
 
 
 
68
 
69
  ---
70
 
71
- *Built with [FORGE-X](https://github.com/WealthFromAI)*
 
1
  ---
2
  title: "Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API"
3
+ emoji: 🤖
4
  colorFrom: blue
5
+ colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ tags:
12
+ - cloud
13
+ - aws
14
+ - infrastructure
15
+ - devops
16
+ - gcp
17
+ - azure
18
  ---
 
19
  # Cloud Infrastructure (AWS/GCP/Azure) Scheduling and Availability API
20
 
21
  Stop overpaying for idle cloud resources across AWS, GCP, and Azure—this API automates scheduling so you only pay for what you actually use.
 
67
 
68
  **Get your API key now and start saving hundreds on cloud costs with one simple integration.**
69
 
70
+ ## 🚀 Usage
71
+
72
+ 1. Click **Use in Spaces** above to run the demo directly
73
+ 2. Or clone the repository and run locally:
74
+
75
+ ```bash
76
+ git clone https://huggingface.co/spaces/WealthFromAI/cloud-infrastructure-awsgcpazure-scheduling-and-availability-api-2c21
77
+ cd cloud-infrastructure-awsgcpazure-scheduling-and-availability-api-2c21
78
+ pip install -r requirements.txt
79
+ python app.py
80
+ ```
81
+
82
+ ## 💰 Pricing
83
 
84
  - **Demo**: Free on Hugging Face Spaces
85
  - **Full Source Code**: $42.23
86
+ - Available on [Gumroad](https://gumroad.com) and [Whop](https://whop.com)
87
+
88
+ ## 📄 License
89
+
90
+ MIT License — free to use, modify, and distribute.
91
 
92
  ---
93
 
94
+ *Built with [FORGE-X](https://github.com/WealthFromAI) — automated digital product engine*