{"record":{"id":"5559591ab021bdb3","repo":"ZhuLinsen/daily_stock_analysis","slug":"desktop-backend-origin-is-invalid","errorCode":null,"errorMessage":"Desktop backend origin is invalid","messagePattern":"Desktop backend origin is invalid","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/dsa-desktop/main.js","lineNumber":1362,"sourceCode":"\nfunction buildDesktopShareImageUrl(pageUrl, recordId, expectedBackendOrigin = '') {\n  if (!Number.isSafeInteger(recordId) || recordId <= 0) {\n    throw new Error('Invalid share image record ID');\n  }\n\n  let page;\n  try {\n    page = new URL(pageUrl);\n  } catch (_error) {\n    throw new Error('Desktop backend URL is unavailable');\n  }\n\n  let expectedOrigin = page.origin;\n  if (expectedBackendOrigin) {\n    try {\n      expectedOrigin = new URL(expectedBackendOrigin).origin;\n    } catch (_error) {\n      throw new Error('Desktop backend origin is invalid');\n    }\n  }\n  if (page.protocol !== 'http:' || !page.port || page.origin !== expectedOrigin) {\n    throw new Error('Desktop share images require the configured backend origin');\n  }\n\n  return new URL(\n    `/api/v1/history/${recordId}/share-image-html`,\n    page.origin\n  ).toString();\n}\n\nasync function renderDesktopShareImage(\n  recordId,\n  {\n    sourceWindow = mainWindow,\n    BrowserWindowClass = BrowserWindow,\n    backendOrigin = '',","sourceCodeStart":1344,"sourceCodeEnd":1380,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/apps/dsa-desktop/main.js#L1344-L1380","documentation":"Raised by PortfolioService.query_trades when the side filter, after strip+lowercase, is not in VALID_SIDES = {'buy','sell'} (portfolio_service.py:36). The check runs before query_trades hits the repository, so an invalid side never reaches SQL.","triggerScenarios":"GET /portfolio/trades?side=BUY%2FSELL, ?side=purchase, ?side=trade, or query_trades(side=\"long\"). 'BUY' and 'Buy' are fine because the value is lowercased; anything else raises. Empty/whitespace side is treated as no filter, not an error.","commonSituations":"Clients sending synonymous terms ('b', 's', 'purchase', 'bought') instead of the enum; enum drift between an older client and the service; form selects with a stale option value after a schema change.","solutions":["Use exactly 'buy' or 'sell' (case-insensitive) as the side parameter","If no side filter is wanted, omit the parameter or pass an empty string","Constrain the UI dropdown / API client enum to the two valid literals","If migrating from another system, map its side vocabulary ('B'/'S') to 'buy'/'sell' before calling"],"exampleFix":"# before\nsvc.query_trades(side=\"B\")\n# after\nsvc.query_trades(side=\"buy\")","handlingStrategy":"validation","validationCode":"VALID_SIDES = {\"buy\", \"sell\"}\n\ndef side_ok(side):\n    return side is None or not side.strip() or side.strip().lower() in VALID_SIDES","typeGuard":"from typing import Literal\n\nTradeSide = Literal[\"buy\", \"sell\"]\n\ndef is_trade_side(value: str) -> TypeGuard[TradeSide]:\n    return value.strip().lower() in {\"buy\", \"sell\"}","tryCatchPattern":"try:\n    svc.query_trades(side=side)\nexcept ValueError as exc:\n    if \"side must be buy or sell\" in str(exc):\n        raise HTTPBadRequest(detail=str(exc)) from exc\n    raise","preventionTips":["Restrict UI dropdowns and API schemas to 'buy'/'sell'","Map broker B/S codes to the enum in an adapter","Case does not matter, but the literal words do"],"tags":["portfolio","validation","enum","query"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}