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)