"""Dropshipping API — public read-only inventory endpoints. Authentication: X-Dropshipping-Key header (per-tenant). Optional: X-Tenant-Subdomain for faster resolution. """ from flask import Blueprint, request, jsonify, g from tenant_db import get_tenant_conn, get_master_conn from services import dropshipping_service as ds_svc from services.webhook_service import dispatch_webhooks_bulk dropship_bp = Blueprint("dropship", __name__, url_prefix="/pos/api/dropship") def _resolve_tenant_by_key(api_key: str, subdomain_hint: str = None): """Return (tenant_conn, tenant_id) for a valid dropshipping API key. If subdomain_hint is provided, validate only that tenant. Otherwise scan active tenants (acceptable for small tenant count). """ master = get_master_conn() try: cur = master.cursor() if subdomain_hint: cur.execute( "SELECT id, db_name FROM tenants WHERE subdomain = %s AND is_active = true", (subdomain_hint,), ) rows = cur.fetchall() else: cur.execute("SELECT id, db_name FROM tenants WHERE is_active = true") rows = cur.fetchall() cur.close() for tid, db_name in rows: try: tconn = get_tenant_conn(tid) if ds_svc.validate_api_key(tconn, api_key): return tconn, tid tconn.close() except Exception: continue return None, None finally: master.close() def _require_dropship_auth(): key = request.headers.get("X-Dropshipping-Key") subdomain = request.headers.get("X-Tenant-Subdomain") if not key: return jsonify({"error": "Missing X-Dropshipping-Key header"}), 401 tconn, tid = _resolve_tenant_by_key(key, subdomain_hint=subdomain) if not tconn: return jsonify({"error": "Invalid API key or tenant inactive"}), 401 g.tenant_id = tid g.tenant_conn = tconn return None def _release_tenant(): if hasattr(g, "tenant_conn") and g.tenant_conn: g.tenant_conn.close() @dropship_bp.route("/inventory", methods=["GET"]) def list_inventory(): err = _require_dropship_auth() if err: return err try: page = int(request.args.get("page", 1)) per_page = min(int(request.args.get("per_page", 50)), 200) search = request.args.get("q") result = ds_svc.get_inventory_list(g.tenant_conn, search=search, page=page, per_page=per_page) return jsonify(result) finally: _release_tenant() @dropship_bp.route("/inventory/", methods=["GET"]) def get_inventory_item(sku): err = _require_dropship_auth() if err: return err try: item = ds_svc.get_inventory_by_sku(g.tenant_conn, sku) if not item: return jsonify({"error": "SKU not found"}), 404 return jsonify(item) finally: _release_tenant() @dropship_bp.route("/stock", methods=["GET"]) def get_stock(): err = _require_dropship_auth() if err: return err try: skus = request.args.get("skus", "") sku_list = [s.strip() for s in skus.split(",") if s.strip()] if not sku_list: return jsonify({"error": "Provide ?skus=SKU1,SKU2,SKU3"}), 400 result = ds_svc.get_stock_by_skus(g.tenant_conn, sku_list) return jsonify({"stock": result}) finally: _release_tenant() @dropship_bp.route("/webhooks/test", methods=["POST"]) def test_webhook(): """Test endpoint to trigger a sample webhook to all configured targets.""" err = _require_dropship_auth() if err: return err try: urls = ds_svc.get_webhook_targets(g.tenant_conn, "stock_updated") if not urls: return jsonify({"error": "No webhook targets configured"}), 400 results = dispatch_webhooks_bulk( urls, "test", {"message": "Webhook test from Nexus POS", "tenant_id": g.tenant_id}, ) return jsonify({"dispatched": len(results), "results": results}) finally: _release_tenant()