Migrate web app from Python/FastAPI to Node.js/TypeScript
Replace the entire Python/FastAPI backend with a Node.js/TypeScript stack: - Framework: Hono + @hono/node-server - Templates: Nunjucks (.njk) replacing Jinja2 (.html) - ORM: Drizzle ORM with mysql2 (same MariaDB schema, no migrations needed) - Sessions: hono-sessions with CookieStore - CSS: Pico CSS v2 replacing Bootstrap 5 (Bootstrap Icons CDN kept) - Dev: tsx watch; Prod: tsc + node dist/index.js Original Python app preserved in web-python/ as backup. Updated Dockerfile.web and docker-compose.yml for Node.js deployment. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
119
web-python/evotor_api.py
Normal file
119
web-python/evotor_api.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from datetime import datetime
|
||||
|
||||
import httpx
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
EVOTOR_API_BASE = "https://api.evotor.ru"
|
||||
|
||||
|
||||
async def fetch_stores(access_token: str) -> list[dict]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(
|
||||
f"{EVOTOR_API_BASE}/stores",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
timeout=15,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
items = data.get("items", data) if isinstance(data, dict) else data
|
||||
return [
|
||||
{
|
||||
"id": s.get("uuid") or s.get("id"),
|
||||
"name": s.get("name"),
|
||||
"address": s.get("address"),
|
||||
}
|
||||
for s in items
|
||||
]
|
||||
|
||||
|
||||
async def fetch_groups(access_token: str, store_id: str) -> list[dict]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(
|
||||
f"{EVOTOR_API_BASE}/stores/{store_id}/product-groups",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
timeout=15,
|
||||
)
|
||||
if resp.status_code == 402:
|
||||
return []
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
items = data.get("items", data) if isinstance(data, dict) else data
|
||||
return [{"id": g.get("uuid") or g.get("id"), "name": g.get("name")} for g in items]
|
||||
|
||||
|
||||
async def fetch_products(access_token: str, store_id: str) -> list[dict]:
|
||||
async with httpx.AsyncClient() as client:
|
||||
resp = await client.get(
|
||||
f"{EVOTOR_API_BASE}/stores/{store_id}/products",
|
||||
headers={"Authorization": f"Bearer {access_token}"},
|
||||
timeout=15,
|
||||
)
|
||||
if resp.status_code == 402:
|
||||
return []
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
items = data.get("items", data) if isinstance(data, dict) else data
|
||||
return [
|
||||
{
|
||||
"id": p.get("uuid") or p.get("id"),
|
||||
"name": p.get("name"),
|
||||
"parent_id": p.get("parentUuid") or p.get("parent_id"),
|
||||
"price": p.get("price"),
|
||||
"quantity": p.get("quantity"),
|
||||
"measure_name": p.get("measureName") or p.get("measure_name"),
|
||||
"article_number": p.get("code") or p.get("article_number"),
|
||||
"allow_to_sell": p.get("allowToSell") if p.get("allowToSell") is not None else p.get("allow_to_sell"),
|
||||
}
|
||||
for p in items
|
||||
]
|
||||
|
||||
|
||||
async def refresh_catalog_cache(user_id: int, access_token: str, db: Session) -> None:
|
||||
from web.models import CachedStore, CachedGroup, CachedProduct
|
||||
|
||||
now = datetime.utcnow()
|
||||
|
||||
# Delete old cache for user
|
||||
db.query(CachedProduct).filter(CachedProduct.user_id == user_id).delete()
|
||||
db.query(CachedGroup).filter(CachedGroup.user_id == user_id).delete()
|
||||
db.query(CachedStore).filter(CachedStore.user_id == user_id).delete()
|
||||
db.commit()
|
||||
|
||||
stores = await fetch_stores(access_token)
|
||||
for store in stores:
|
||||
db.add(CachedStore(
|
||||
user_id=user_id,
|
||||
evotor_id=store["id"],
|
||||
name=store["name"] or "",
|
||||
address=store.get("address"),
|
||||
fetched_at=now,
|
||||
))
|
||||
db.commit()
|
||||
|
||||
for store in stores:
|
||||
groups = await fetch_groups(access_token, store["id"])
|
||||
for group in groups:
|
||||
db.add(CachedGroup(
|
||||
user_id=user_id,
|
||||
evotor_id=group["id"],
|
||||
store_evotor_id=store["id"],
|
||||
name=group["name"] or "",
|
||||
fetched_at=now,
|
||||
))
|
||||
|
||||
products = await fetch_products(access_token, store["id"])
|
||||
for product in products:
|
||||
db.add(CachedProduct(
|
||||
user_id=user_id,
|
||||
evotor_id=product["id"],
|
||||
store_evotor_id=store["id"],
|
||||
group_evotor_id=product.get("parent_id"),
|
||||
name=product["name"] or "",
|
||||
price=product.get("price"),
|
||||
quantity=product.get("quantity"),
|
||||
measure_name=product.get("measure_name"),
|
||||
article_number=product.get("article_number"),
|
||||
allow_to_sell=product.get("allow_to_sell"),
|
||||
fetched_at=now,
|
||||
))
|
||||
db.commit()
|
||||
Reference in New Issue
Block a user