Job 1788121522940

Command: python -c "from pathlib import Path; specs=[(Path(r'C:\Users\ash\Documents\ShopeeApi\app\main.py'),[(270,315),(760,850),(1265,1305)]),(Path(r'C:\Users\ash\Documents\ShopeeApi\app\db.py'),[(1765,1810),(4990,5060)]),(Path(r'C:\Users\ash\Documents\ShopeeApi\app\shop_context.py'),[(1,80)])]; [(print('FILE='+str(p)),[(print('RANGE '+str(a)+'-'+str(b)),[print(f'{i}: {lines[i-1]}') for i in range(a,min(b,len(lines))+1)]) for a,b in ranges]) for p,ranges in specs if p.exists() for lines in [p.read_text(encoding='utf-8',errors='replace').splitlines()]]"
Directory: projects
Status: SUCCESS
Exit code: 0

Cancel Job Rerun Command Refresh

FILE=C:\Users\ash\Documents\ShopeeApi\app\main.py
RANGE 270-315
270:     cost_35ml: float = Field(ge=0)
271:     effective_from: date
272:     currency: str = Field(default="MYR", min_length=3, max_length=3)
273: 
274: 
275: class FinanceSyncRequest(BaseModel):
276:     lookback_days: int = Field(default=45, ge=1, le=3650)
277: 
278: 
279: class LlmContextRequest(BaseModel):
280:     query: str = Field(min_length=1, max_length=2000)
281:     product_hint: str = Field(default="", max_length=500)
282:     order_sn: str = Field(default="", max_length=80)
283:     limit: int = Field(default=5, ge=1, le=5)
284: 
285: class ShopUpsertRequest(BaseModel):
286:     shop_id: int
287:     name: str = ""
288: 
289: 
290: class PerfumeRegistryRequest(BaseModel):
291:     id: int | None = None
292:     shop_id: int | None = None
293:     canonical_name: str = Field(default="", max_length=255)
294:     listing_name: str = Field(default="", max_length=512)
295:     variant_name: str = Field(default="", max_length=512)
296:     source_mode: str = Field(default="product", max_length=32)
297:     platform: str = Field(default="SHOPEE", max_length=32)
298:     source: str = Field(default="manual", max_length=128)
299: 
300: 
301: class PerfumeRegistryDeleteRequest(BaseModel):
302:     confirmed: bool = False
303:     shop_id: int | None = None
304: 
305: 
306: class CanonicalScentRequest(BaseModel):
307:     canonical_name: str = Field(min_length=1, max_length=255)
308:     aliases: list[str] = Field(default_factory=list, max_length=100)
309:     gender: Literal["", "M", "W", "U"] = ""
310:     status: Literal["ACTIVE", "INACTIVE"] = "ACTIVE"
311:     source: str = Field(default="manual", max_length=128)
312: 
313: 
314: class ProductMappingRequest(BaseModel):
315:     scent_id: str | None = Field(default=None, max_length=128)
RANGE 760-850
760:     try:
761:         sid = int(value or 0)
762:     except (TypeError, ValueError):
763:         sid = 0
764:     if sid <= 0:
765:         return 0
766:     if current and sid != current:
767:         raise PermissionError("Registry entry ini bukan untuk shop aktif")
768:     return sid
769: 
770: 
771: def _shop_records() -> list[dict[str, Any]]:
772:     with db.connect() as conn:
773:         rows = conn.execute("SELECT shop_id, name, created_at, updated_at FROM shops ORDER BY shop_id ASC").fetchall()
774:     items: list[dict[str, Any]] = []
775:     for row in rows:
776:         item = dict(row)
777:         shop_id = int(item.get("shop_id") or 0)
778:         item["token_status"] = _safe_token_status(shop_id)
779:         item["onboarding"] = db.get_shop_onboarding(shop_id)
780:         item["sync_policy"] = db.get_shop_sync_policy(shop_id)
781:         items.append(item)
782:     if not items and settings.shop_id:
783:         items.append({
784:             "shop_id": int(settings.shop_id),
785:             "name": settings.shop_name,
786:             "created_at": "",
787:             "updated_at": "",
788:             "token_status": _safe_token_status(int(settings.shop_id)),
789:             "onboarding": db.get_shop_onboarding(int(settings.shop_id)),
790:             "sync_policy": db.get_shop_sync_policy(int(settings.shop_id)),
791:         })
792:     return items
793: 
794: 
795: def _safe_token_status(shop_id: int) -> dict[str, Any]:
796:     try:
797:         return token_status_for_shop(int(shop_id or 0))
798:     except Exception as exc:
799:         return {
800:             "shop_id": int(shop_id or 0),
801:             "authorized": False,
802:             "has_access_token": False,
803:             "has_refresh_token": False,
804:             "expires_at": 0,
805:             "expired": True,
806:             "storage_path": "",
807:             "storage_exists": False,
808:             "storage_encrypted": False,
809:             "storage_mode": "error",
810:             "encryption_algorithm": "error",
811:             "error": redact_text(str(exc)),
812:         }
813: 
814: 
815: def _upsert_shop_record(shop_id: int, name: str) -> dict[str, Any]:
816:     now = db.utc_now_iso()
817:     with db.connect() as conn:
818:         conn.execute(
819:             """
820:             INSERT INTO shops(shop_id, name, created_at, updated_at)
821:             VALUES (?, ?, ?, ?)
822:             ON CONFLICT(shop_id) DO UPDATE SET name=excluded.name, updated_at=excluded.updated_at
823:             """,
824:             (int(shop_id), name or f"Shop {shop_id}", now, now),
825:         )
826:     return {"shop_id": int(shop_id), "name": name or f"Shop {shop_id}", "created_at": now, "updated_at": now}
827: 
828: 
829: def _register_authorized_shop(client: ShopeeClient, saved: dict[str, Any]) -> tuple[dict[str, Any], str]:
830:     """Always register a successfully authorized shop; profile lookup is best-effort."""
831:     shop_id = int(saved.get("shop_id") or client.shop_id or 0)
832:     if shop_id <= 0:
833:         raise ValueError("Shopee authorization tidak memulangkan shop_id yang sah")
834:     shop_name = ""
835:     warning = ""
836:     try:
837:         profile = client.get_shop_info()
838:         if isinstance(profile, dict):
839:             shop_name = str(profile.get("shop_name") or "").strip()
840:             nested = profile.get("response")
841:             if not shop_name and isinstance(nested, dict):
842:                 shop_name = str(nested.get("shop_name") or "").strip()
843:     except Exception as exc:
844:         warning = redact_text(str(exc))
845:     if not shop_name:
846:         with db.connect() as conn:
847:             existing = conn.execute("SELECT name FROM shops WHERE shop_id=?", (shop_id,)).fetchone()
848:         shop_name = str(existing["name"] or "").strip() if existing else ""
849:     shop = _upsert_shop_record(shop_id, shop_name or f"Shop {shop_id}")
850:     # This is durable and idempotent: a newly authorized tenant begins with
RANGE 1265-1305
1265:         "missing": settings.validate_runtime(),
1266:         "initial_backfill_start_date": settings.auto_sync_initial_start_date,
1267:         "initial_backfill_start_ts": settings.auto_sync_initial_start_ts(),
1268:     }
1269: 
1270: 
1271: @app.get("/api/shops", dependencies=[Depends(require_admin)])
1272: def api_list_shops():
1273:     return {"active_shop_id": current_shop_id(), "items": _shop_records()}
1274: 
1275: 
1276: @app.post("/api/shops", dependencies=[Depends(require_admin)])
1277: def api_upsert_shop(req: ShopUpsertRequest):
1278:     if req.shop_id <= 0:
1279:         raise HTTPException(422, "shop_id mesti lebih besar daripada 0")
1280:     return {"ok": True, "shop": _upsert_shop_record(req.shop_id, req.name)}
1281: 
1282: 
1283: @app.patch("/api/shops/{shop_id}", dependencies=[Depends(require_admin)])
1284: def api_patch_shop(shop_id: int, req: ShopUpsertRequest):
1285:     if shop_id != req.shop_id and req.shop_id > 0:
1286:         raise HTTPException(422, "shop_id dalam path dan body mesti sama")
1287:     if shop_id <= 0:
1288:         raise HTTPException(422, "shop_id mesti lebih besar daripada 0")
1289:     return {"ok": True, "shop": _upsert_shop_record(shop_id, req.name)}
1290: 
1291: 
1292: @app.get("/api/shops/{shop_id}/auth-url", dependencies=[Depends(require_admin)])
1293: def api_shop_auth_url(shop_id: int):
1294:     if shop_id <= 0:
1295:         raise HTTPException(422, "shop_id mesti lebih besar daripada 0")
1296:     client = ShopeeClient(shop_id=shop_id)
1297:     try:
1298:         return {"shop_id": shop_id, "auth_url": client.build_auth_url(), "redirect_url": settings.shopee_redirect_url}
1299:     finally:
1300:         client.close()
1301: 
1302: 
1303: @app.get("/api/summary", dependencies=[Depends(require_admin)])
1304: def summary(): return db.get_summary()
1305: 
FILE=C:\Users\ash\Documents\ShopeeApi\app\db.py
RANGE 1765-1810
1765:             "forecast_updated_at": None,
1766:             "forecast_source": "",
1767:         }
1768:     return dict(row)
1769: 
1770: 
1771: def init_db() -> None:
1772:     schema = """
1773:     CREATE TABLE IF NOT EXISTS shops (
1774:         shop_id INTEGER PRIMARY KEY,
1775:         name TEXT NOT NULL,
1776:         created_at TEXT NOT NULL,
1777:         updated_at TEXT NOT NULL
1778:     );
1779: 
1780:     CREATE TABLE IF NOT EXISTS orders (
1781:         shop_id INTEGER NOT NULL,
1782:         order_sn TEXT NOT NULL,
1783:         order_status TEXT,
1784:         create_time INTEGER,
1785:         update_time INTEGER,
1786:         pay_time INTEGER,
1787:         ship_by_date INTEGER,
1788:         total_amount REAL,
1789:         currency TEXT,
1790:         buyer_user_id INTEGER,
1791:         buyer_username TEXT,
1792:         payment_method TEXT,
1793:         checkout_shipping_carrier TEXT,
1794:         shipping_carrier TEXT,
1795:         recipient_name TEXT,
1796:         recipient_phone TEXT,
1797:         recipient_city TEXT,
1798:         recipient_state TEXT,
1799:         recipient_zipcode TEXT,
1800:         item_count INTEGER,
1801:         package_count INTEGER,
1802:         has_detail INTEGER NOT NULL DEFAULT 0,
1803:         escrow_synced INTEGER NOT NULL DEFAULT 0,
1804:         logistics_synced INTEGER NOT NULL DEFAULT 0,
1805:         payload_json TEXT NOT NULL,
1806:         first_seen_at TEXT NOT NULL,
1807:         synced_at TEXT NOT NULL,
1808:         PRIMARY KEY (shop_id, order_sn)
1809:     );
1810:     CREATE INDEX IF NOT EXISTS idx_orders_update_time ON orders(shop_id, update_time DESC);
RANGE 4990-5060
4990: def get_awb_shop_branding(shop_id: int | None = None) -> dict[str, Any] | None:
4991:     sid = _shop_id(shop_id)
4992:     with connect() as conn:
4993:         row = conn.execute("SELECT * FROM awb_shop_branding WHERE shop_id=?", (sid,)).fetchone()
4994:     return dict(row) if row else None
4995: 
4996: 
4997: def touch_awb_shop_branding(shop_id: int | None = None) -> dict[str, Any] | None:
4998:     sid = _shop_id(shop_id)
4999:     with _DB_LOCK, connect() as conn:
5000:         conn.execute(
5001:             "UPDATE awb_shop_branding SET updated_at=? WHERE shop_id=?",
5002:             (utc_now_iso(), sid),
5003:         )
5004:     return get_awb_shop_branding(sid)
5005: 
5006: 
5007: def list_registered_shop_ids() -> list[int]:
5008:     with connect() as conn:
5009:         rows = conn.execute("SELECT shop_id FROM shops ORDER BY shop_id ASC").fetchall()
5010:     return [int(row["shop_id"]) for row in rows if int(row["shop_id"] or 0) > 0]
5011: 
5012: 
5013: def is_registered_shop_id(shop_id: int | str) -> bool:
5014:     try:
5015:         sid = int(shop_id)
5016:     except (TypeError, ValueError):
5017:         return False
5018:     if sid <= 0:
5019:         return False
5020:     with connect() as conn:
5021:         return conn.execute("SELECT 1 FROM shops WHERE shop_id=?", (sid,)).fetchone() is not None
5022: 
5023: 
5024: def upsert_shop_identity(shop_id: int, shop_name: str) -> None:
5025:     sid = _shop_id(shop_id)
5026:     name = str(shop_name or "").strip()
5027:     if not name:
5028:         return
5029:     now = utc_now_iso()
5030:     with _DB_LOCK, connect() as conn:
5031:         conn.execute(
5032:             """
5033:             INSERT INTO shops(shop_id, name, created_at, updated_at)
5034:             VALUES (?, ?, ?, ?)
5035:             ON CONFLICT(shop_id) DO UPDATE SET name=excluded.name, updated_at=excluded.updated_at
5036:             """,
5037:             (sid, name, now, now),
5038:         )
5039: 
5040: 
5041: def get_shop_name(shop_id: int) -> str:
5042:     sid = _shop_id(shop_id)
5043:     with connect() as conn:
5044:         row = conn.execute("SELECT name FROM shops WHERE shop_id=?", (sid,)).fetchone()
5045:     return str(row["name"] or "").strip() if row else ""
5046: 
5047: 
5048: def upsert_awb_shop_branding(
5049:     shop_id: int,
5050:     *,
5051:     logo_relpath: str,
5052:     logo_sha256: str,
5053:     original_filename: str | None = None,
5054:     source_mime_type: str | None = None,
5055:     enabled: bool = True,
5056: ) -> dict[str, Any]:
5057:     now = utc_now_iso()
5058:     sid = _shop_id(shop_id)
5059:     with _DB_LOCK, connect() as conn:
5060:         conn.execute(
FILE=C:\Users\ash\Documents\ShopeeApi\app\shop_context.py
RANGE 1-80
1: from __future__ import annotations
2: 
3: from contextlib import contextmanager
4: from contextvars import ContextVar, Token
5: from typing import Iterator
6: 
7: from app.config import settings
8: 
9: _ACTIVE_SHOP_ID: ContextVar[int] = ContextVar("active_shopee_shop_id", default=0)
10: 
11: 
12: def current_shop_id(default: int | None = None) -> int:
13:     value = int(_ACTIVE_SHOP_ID.get() or 0)
14:     if value > 0:
15:         return value
16:     if default is not None:
17:         try:
18:             fallback = int(default or 0)
19:         except (TypeError, ValueError):
20:             fallback = 0
21:         if fallback > 0:
22:             return fallback
23:     return int(settings.shop_id or 0)
24: 
25: 
26: def set_active_shop_id(shop_id: int | str | None) -> Token:
27:     try:
28:         value = int(shop_id or 0)
29:     except (TypeError, ValueError):
30:         value = 0
31:     return _ACTIVE_SHOP_ID.set(value if value > 0 else 0)
32: 
33: 
34: def reset_active_shop_id(token: Token) -> None:
35:     _ACTIVE_SHOP_ID.reset(token)
36: 
37: 
38: @contextmanager
39: def use_shop(shop_id: int | str | None) -> Iterator[int]:
40:     token = set_active_shop_id(shop_id)
41:     try:
42:         yield current_shop_id()
43:     finally:
44:         reset_active_shop_id(token)