orders
This commit is contained in:
parent
721b26ce97
commit
1bf255d0fe
3
.gitignore
vendored
3
.gitignore
vendored
@ -173,4 +173,5 @@ cython_debug/
|
|||||||
temp/
|
temp/
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.db-journal
|
*.db-journal
|
||||||
cookies/
|
cookies/
|
||||||
|
alembic/versions/*
|
@ -328,6 +328,41 @@ class TCGPlayerGroups(Base):
|
|||||||
modified_on = Column(String)
|
modified_on = Column(String)
|
||||||
category_id = Column(Integer)
|
category_id = Column(Integer)
|
||||||
|
|
||||||
|
class Orders(Base):
|
||||||
|
__tablename__ = 'orders'
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True)
|
||||||
|
order_id = Column(String, unique=True)
|
||||||
|
buyer_name = Column(String)
|
||||||
|
recipient_name = Column(String)
|
||||||
|
recipient_address_one = Column(String)
|
||||||
|
recipient_address_two = Column(String)
|
||||||
|
recipient_city = Column(String)
|
||||||
|
recipient_state = Column(String)
|
||||||
|
recipient_zip = Column(String)
|
||||||
|
recipient_country = Column(String)
|
||||||
|
order_date = Column(String)
|
||||||
|
status = Column(String)
|
||||||
|
num_products = Column(Integer)
|
||||||
|
num_cards = Column(Integer)
|
||||||
|
product_amount = Column(Float)
|
||||||
|
shipping_amount = Column(Float)
|
||||||
|
gross_amount = Column(Float)
|
||||||
|
fee_amount = Column(Float)
|
||||||
|
net_amount = Column(Float)
|
||||||
|
direct_fee_amount = Column(Float)
|
||||||
|
date_created = Column(DateTime, default=datetime.now)
|
||||||
|
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
class OrderProducts(Base):
|
||||||
|
__tablename__ = 'order_products'
|
||||||
|
|
||||||
|
id = Column(String, primary_key=True)
|
||||||
|
order_id = Column(String, ForeignKey('orders.id'))
|
||||||
|
product_id = Column(String, ForeignKey('products.id'))
|
||||||
|
quantity = Column(Integer)
|
||||||
|
unit_price = Column(Float)
|
||||||
|
|
||||||
# enums
|
# enums
|
||||||
|
|
||||||
class RarityEnum(str, Enum):
|
class RarityEnum(str, Enum):
|
||||||
|
@ -10,6 +10,7 @@ from app.services.product import ProductService
|
|||||||
from app.services.inventory import InventoryService
|
from app.services.inventory import InventoryService
|
||||||
from app.services.task import TaskService
|
from app.services.task import TaskService
|
||||||
from app.services.storage import StorageService
|
from app.services.storage import StorageService
|
||||||
|
from app.services.tcgplayer_api import TCGPlayerAPIService
|
||||||
from app.db.database import get_db
|
from app.db.database import get_db
|
||||||
from app.schemas.file import CreateFileRequest
|
from app.schemas.file import CreateFileRequest
|
||||||
from app.schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxRequest
|
from app.schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxRequest
|
||||||
@ -18,6 +19,10 @@ from app.schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxReq
|
|||||||
DB = Annotated[Session, Depends(get_db)]
|
DB = Annotated[Session, Depends(get_db)]
|
||||||
|
|
||||||
# Base Services (no dependencies besides DB)
|
# Base Services (no dependencies besides DB)
|
||||||
|
def get_tcgplayer_api_service(db: DB) -> TCGPlayerAPIService:
|
||||||
|
"""TCGPlayerAPIService with only database dependency"""
|
||||||
|
return TCGPlayerAPIService(db)
|
||||||
|
|
||||||
def get_file_service(db: DB) -> FileService:
|
def get_file_service(db: DB) -> FileService:
|
||||||
"""FileService with only database dependency"""
|
"""FileService with only database dependency"""
|
||||||
return FileService(db)
|
return FileService(db)
|
||||||
|
@ -25,10 +25,12 @@ from app.schemas.box import (
|
|||||||
CreateOpenBoxResponse,
|
CreateOpenBoxResponse,
|
||||||
OpenBoxSchema
|
OpenBoxSchema
|
||||||
)
|
)
|
||||||
|
from app.schemas.orders import ProcessOrdersResponse
|
||||||
from app.services.file import FileService
|
from app.services.file import FileService
|
||||||
from app.services.box import BoxService
|
from app.services.box import BoxService
|
||||||
from app.services.task import TaskService
|
from app.services.task import TaskService
|
||||||
from app.services.pricing import PricingService
|
from app.services.pricing import PricingService
|
||||||
|
from app.services.tcgplayer_api import TCGPlayerAPIService
|
||||||
from app.dependencies import (
|
from app.dependencies import (
|
||||||
get_file_service,
|
get_file_service,
|
||||||
get_box_service,
|
get_box_service,
|
||||||
@ -37,7 +39,8 @@ from app.dependencies import (
|
|||||||
get_box_data,
|
get_box_data,
|
||||||
get_box_update_data,
|
get_box_update_data,
|
||||||
get_open_box_data,
|
get_open_box_data,
|
||||||
get_pricing_service
|
get_pricing_service,
|
||||||
|
get_tcgplayer_api_service
|
||||||
)
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -313,3 +316,23 @@ async def update_cookies(
|
|||||||
status_code=500,
|
status_code=500,
|
||||||
detail=f"Failed to update cookies: {str(e)}"
|
detail=f"Failed to update cookies: {str(e)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class TCGPlayerOrderRequest(BaseModel):
|
||||||
|
order_ids: List[str]
|
||||||
|
|
||||||
|
@router.post("/processOrders", response_model=ProcessOrdersResponse)
|
||||||
|
async def process_orders(
|
||||||
|
body: TCGPlayerOrderRequest,
|
||||||
|
tcgplayer_api_service: TCGPlayerAPIService = Depends(get_tcgplayer_api_service),
|
||||||
|
) -> ProcessOrdersResponse:
|
||||||
|
"""Process TCGPlayer orders."""
|
||||||
|
try:
|
||||||
|
orders = tcgplayer_api_service.process_orders(body.order_ids)
|
||||||
|
return ProcessOrdersResponse(
|
||||||
|
status_code=200,
|
||||||
|
success=True,
|
||||||
|
orders=orders
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Process orders failed: {str(e)}")
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
@ -1,19 +0,0 @@
|
|||||||
from pydantic import BaseModel, Field, ConfigDict
|
|
||||||
from typing import Optional
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
# FILE
|
|
||||||
class OrderSchema(BaseModel):
|
|
||||||
id: str = Field(..., title="id")
|
|
||||||
filename: str = Field(..., title="filename")
|
|
||||||
type: str = Field(..., title="type")
|
|
||||||
filesize_kb: float = Field(..., title="filesize_kb")
|
|
||||||
source: str = Field(..., title="source")
|
|
||||||
status: str = Field(..., title="status")
|
|
||||||
service: Optional[str] = Field(None, title="service")
|
|
||||||
date_created: datetime = Field(..., title="date_created")
|
|
||||||
date_modified: datetime = Field(..., title="date_modified")
|
|
||||||
|
|
||||||
# This enables ORM mode
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
9
app/schemas/orders.py
Normal file
9
app/schemas/orders.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class OrderSchema(BaseModel):
|
||||||
|
order_id: str
|
||||||
|
|
||||||
|
class ProcessOrdersResponse(BaseModel):
|
||||||
|
status_code: int
|
||||||
|
success: bool
|
||||||
|
orders: list[str]
|
@ -10,7 +10,7 @@ import io
|
|||||||
|
|
||||||
# Printer settings
|
# Printer settings
|
||||||
printer_model = "QL-1100"
|
printer_model = "QL-1100"
|
||||||
backend = 'pyusb' # Changed from network to USB
|
backend = 'pyusb'
|
||||||
printer = 'usb://0x04f9:0x20a7'
|
printer = 'usb://0x04f9:0x20a7'
|
||||||
|
|
||||||
def convert_pdf_to_image(pdf_path):
|
def convert_pdf_to_image(pdf_path):
|
||||||
@ -45,6 +45,8 @@ def create_address_label(input_data, font_size=30, is_pdf=False):
|
|||||||
font = ImageFont.truetype("C:\\Windows\\Fonts\\arial.ttf", size=font_size)
|
font = ImageFont.truetype("C:\\Windows\\Fonts\\arial.ttf", size=font_size)
|
||||||
elif platform.system() == 'Darwin':
|
elif platform.system() == 'Darwin':
|
||||||
font = ImageFont.truetype("/Library/Fonts/Arial.ttf", size=font_size)
|
font = ImageFont.truetype("/Library/Fonts/Arial.ttf", size=font_size)
|
||||||
|
elif platform.system() == 'Linux':
|
||||||
|
font = ImageFont.truetype("/usr/share/fonts/truetype/msttcorefonts/arial.ttf", size=font_size)
|
||||||
|
|
||||||
margin = 20
|
margin = 20
|
||||||
lines = input_data.split('\n')
|
lines = input_data.split('\n')
|
||||||
@ -71,10 +73,8 @@ def print_address_label(input_data, font_size=30, is_pdf=False, label_size='29x9
|
|||||||
if not image:
|
if not image:
|
||||||
raise Exception("Failed to create label image")
|
raise Exception("Failed to create label image")
|
||||||
|
|
||||||
# For 4x6 shipping labels from Pirate Ship
|
|
||||||
if label_size == '4x6':
|
if label_size == '4x6':
|
||||||
# Resize image to fit 4x6 format if needed
|
target_width = 1164
|
||||||
target_width = 1164 # Adjusted for 4x6 format
|
|
||||||
target_height = 1660
|
target_height = 1660
|
||||||
image = image.resize((target_width, target_height), Image.LANCZOS)
|
image = image.resize((target_width, target_height), Image.LANCZOS)
|
||||||
|
|
||||||
@ -92,7 +92,6 @@ def print_address_label(input_data, font_size=30, is_pdf=False, label_size='29x9
|
|||||||
red=False,
|
red=False,
|
||||||
dpi_600=False,
|
dpi_600=False,
|
||||||
hq=True,
|
hq=True,
|
||||||
#cut=True
|
|
||||||
cut=False
|
cut=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -135,34 +134,24 @@ def process_tcg_shipping_export(file_path, require_input=False, font_size=60, pr
|
|||||||
else:
|
else:
|
||||||
sleep(1)
|
sleep(1)
|
||||||
|
|
||||||
# Example usage
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Example for regular address label
|
|
||||||
address = """John Doe
|
|
||||||
123 Main Street
|
|
||||||
Apt 4B
|
|
||||||
City, State 12345"""
|
|
||||||
|
|
||||||
# Example for TCG Player export
|
|
||||||
shipping_export_file = "_TCGplayer_ShippingExport_20250201_115949.csv"
|
|
||||||
|
|
||||||
# Example for Pirate Ship PDF
|
|
||||||
pirate_ship_pdf = "C:\\Users\\joshu\\Downloads\\2025-02-10---greg-creek---9400136208070411592215.pdf"
|
|
||||||
|
|
||||||
# Choose which type to process
|
# Choose which type to process
|
||||||
label_type = input("Enter label type (1 for regular, 2 for TCG, 3 for Pirate Ship): ")
|
label_type = input("Enter label type (1 for regular, 2 for TCG, 3 for Pirate Ship): ")
|
||||||
|
|
||||||
if label_type == "1":
|
if label_type == "1":
|
||||||
|
address = input("Enter the address to print: ")
|
||||||
preview_label(address, font_size=60)
|
preview_label(address, font_size=60)
|
||||||
user_input = input("Press 'p' to print the label or any other key to cancel: ")
|
user_input = input("Press 'p' to print the label or any other key to cancel: ")
|
||||||
if user_input.lower() == 'p':
|
if user_input.lower() == 'p':
|
||||||
print_address_label(address, font_size=60)
|
print_address_label(address, font_size=60)
|
||||||
|
|
||||||
elif label_type == "2":
|
elif label_type == "2":
|
||||||
|
shipping_export_file = input("Enter the path to the TCG Player shipping export CSV file: ")
|
||||||
process_tcg_shipping_export(shipping_export_file, font_size=60, preview=False)
|
process_tcg_shipping_export(shipping_export_file, font_size=60, preview=False)
|
||||||
|
|
||||||
elif label_type == "3":
|
elif label_type == "3":
|
||||||
|
pirate_ship_pdf = input("Enter the path to the Pirate Ship PDF file: ")
|
||||||
process_pirate_ship_pdf(pirate_ship_pdf, preview=True)
|
process_pirate_ship_pdf(pirate_ship_pdf, preview=True)
|
||||||
user_input = input("Press 'p' to print the label or any other key to cancel: ")
|
user_input = input("Press 'p' to print the label or any other key to cancel: ")
|
||||||
if user_input.lower() == 'p':
|
if user_input.lower() == 'p':
|
||||||
process_pirate_ship_pdf(pirate_ship_pdf, preview=False)
|
process_pirate_ship_pdf(pirate_ship_pdf, preview=False)
|
@ -21,6 +21,7 @@ import pandas as pd
|
|||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from app.schemas.file import CreateFileRequest
|
from app.schemas.file import CreateFileRequest
|
||||||
import os
|
import os
|
||||||
|
from app.services.util._docker import DockerUtil
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -53,6 +54,7 @@ class TCGPlayerService:
|
|||||||
self.previous_request_time = None
|
self.previous_request_time = None
|
||||||
self.df_util = DataframeUtil()
|
self.df_util = DataframeUtil()
|
||||||
self.file_service = file_service
|
self.file_service = file_service
|
||||||
|
self.docker_util = DockerUtil()
|
||||||
|
|
||||||
def _insert_groups(self, groups):
|
def _insert_groups(self, groups):
|
||||||
for group in groups:
|
for group in groups:
|
||||||
@ -118,47 +120,6 @@ class TCGPlayerService:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to get browser cookies: {str(e)}")
|
logger.error(f"Failed to get browser cookies: {str(e)}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def is_in_docker(self) -> bool:
|
|
||||||
"""Check if we're running inside a Docker container using multiple methods"""
|
|
||||||
# Method 1: Check cgroup
|
|
||||||
try:
|
|
||||||
with open('/proc/1/cgroup', 'r') as f:
|
|
||||||
content = f.read().lower()
|
|
||||||
if any(container_id in content for container_id in ['docker', 'containerd', 'kubepods']):
|
|
||||||
logger.debug("Docker detected via cgroup")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Could not read cgroup file: {e}")
|
|
||||||
|
|
||||||
# Method 2: Check /.dockerenv file
|
|
||||||
if os.path.exists('/.dockerenv'):
|
|
||||||
logger.debug("Docker detected via /.dockerenv file")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Method 3: Check environment variables
|
|
||||||
docker_env = any(os.environ.get(var, False) for var in [
|
|
||||||
'DOCKER_CONTAINER',
|
|
||||||
'IN_DOCKER',
|
|
||||||
'KUBERNETES_SERVICE_HOST', # For k8s
|
|
||||||
'DOCKER_HOST'
|
|
||||||
])
|
|
||||||
if docker_env:
|
|
||||||
logger.debug("Docker detected via environment variables")
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Method 4: Check container runtime
|
|
||||||
try:
|
|
||||||
with open('/proc/self/mountinfo', 'r') as f:
|
|
||||||
content = f.read().lower()
|
|
||||||
if any(rt in content for rt in ['docker', 'containerd', 'kubernetes']):
|
|
||||||
logger.debug("Docker detected via mountinfo")
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.debug(f"Could not read mountinfo: {e}")
|
|
||||||
|
|
||||||
logger.debug("No Docker environment detected")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _send_request(self, url: str, method: str, data=None, except_302=False) -> requests.Response:
|
def _send_request(self, url: str, method: str, data=None, except_302=False) -> requests.Response:
|
||||||
"""Send a request with the specified cookies"""
|
"""Send a request with the specified cookies"""
|
||||||
@ -173,7 +134,7 @@ class TCGPlayerService:
|
|||||||
|
|
||||||
# Move cookie initialization outside and make it more explicit
|
# Move cookie initialization outside and make it more explicit
|
||||||
if not self.cookies:
|
if not self.cookies:
|
||||||
if self.is_in_docker():
|
if self.docker_util.is_in_docker():
|
||||||
logger.debug("Running in Docker - using cookies from file")
|
logger.debug("Running in Docker - using cookies from file")
|
||||||
self.cookies = self.get_cookies_from_file()
|
self.cookies = self.get_cookies_from_file()
|
||||||
else:
|
else:
|
||||||
|
105
app/services/tcgplayer_api.py
Normal file
105
app/services/tcgplayer_api.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
import logging
|
||||||
|
from app.db.models import Orders, OrderProducts, CardTCGPlayer
|
||||||
|
from app.services.util._requests import RequestsUtil
|
||||||
|
from app.services.util._docker import DockerUtil
|
||||||
|
from app.db.utils import db_transaction
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from uuid import uuid4 as uuid
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TCGPlayerAPIConfig:
|
||||||
|
"""Configuration for TCGPlayer API"""
|
||||||
|
ORDER_BASE_URL: str = "https://order-management-api.tcgplayer.com/orders"
|
||||||
|
API_VERSION: str = "?api-version=2.0"
|
||||||
|
|
||||||
|
class TCGPlayerAPIService:
|
||||||
|
def __init__(self, db: Session):
|
||||||
|
self.db = db
|
||||||
|
self.docker_util = DockerUtil()
|
||||||
|
self.requests_util = RequestsUtil()
|
||||||
|
self.is_in_docker = self.docker_util.is_in_docker()
|
||||||
|
self.config = TCGPlayerAPIConfig()
|
||||||
|
self.cookies = self.get_cookies()
|
||||||
|
|
||||||
|
def get_cookies(self) -> dict:
|
||||||
|
if self.is_in_docker:
|
||||||
|
return self.requests_util.get_tcgplayer_cookies_from_file()
|
||||||
|
else:
|
||||||
|
return self.requests_util.get_tcgplayer_browser_cookies()
|
||||||
|
|
||||||
|
def get_order(self, order_id: str) -> dict:
|
||||||
|
url = f"{self.config.ORDER_BASE_URL}/{order_id}{self.config.API_VERSION}"
|
||||||
|
response = self.requests_util.send_request(url, method='GET', cookies=self.cookies)
|
||||||
|
if response:
|
||||||
|
return response.json()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_product_ids_from_sku(self, sku_ids: list[str]) -> dict:
|
||||||
|
"""Get product IDs from TCGPlayer SKU IDs"""
|
||||||
|
tcg_cards = self.db.query(CardTCGPlayer).filter(CardTCGPlayer.tcgplayer_id.in_(sku_ids)).all()
|
||||||
|
return {card.tcgplayer_id: card.product_id for card in tcg_cards}
|
||||||
|
|
||||||
|
def save_order(self, order: dict):
|
||||||
|
# check if order exists by order number
|
||||||
|
order_number = order['orderNumber']
|
||||||
|
existing_order = self.db.query(Orders).filter(Orders.order_id == order_number).first()
|
||||||
|
if existing_order:
|
||||||
|
logger.info(f"Order {order_number} already exists in database")
|
||||||
|
return existing_order
|
||||||
|
transaction = order['transaction']
|
||||||
|
shipping = order['shippingAddress']
|
||||||
|
products = order['products']
|
||||||
|
with db_transaction(self.db):
|
||||||
|
db_order = Orders(
|
||||||
|
id = str(uuid()),
|
||||||
|
order_id=order_number,
|
||||||
|
buyer_name=order['buyerName'],
|
||||||
|
recipient_name=shipping['recipientName'],
|
||||||
|
recipient_address_one=shipping['addressOne'],
|
||||||
|
recipient_address_two=shipping['addressTwo'] if 'addressTwo' in shipping else '',
|
||||||
|
recipient_city=shipping['city'],
|
||||||
|
recipient_state=shipping['territory'],
|
||||||
|
recipient_zip=shipping['postalCode'],
|
||||||
|
recipient_country=shipping['country'],
|
||||||
|
order_date=order['createdAt'],
|
||||||
|
status=order['status'],
|
||||||
|
num_products=len(products),
|
||||||
|
num_cards=sum([product['quantity'] for product in products]),
|
||||||
|
product_amount=transaction['productAmount'],
|
||||||
|
shipping_amount=transaction['shippingAmount'],
|
||||||
|
gross_amount=transaction['grossAmount'],
|
||||||
|
fee_amount=transaction['feeAmount'],
|
||||||
|
net_amount=transaction['netAmount'],
|
||||||
|
direct_fee_amount=transaction['directFeeAmount']
|
||||||
|
)
|
||||||
|
self.db.add(db_order)
|
||||||
|
|
||||||
|
product_ids = [product['skuId'] for product in products]
|
||||||
|
sku_to_product_id_mapping = self.get_product_ids_from_sku(product_ids)
|
||||||
|
order_products = []
|
||||||
|
for product in products:
|
||||||
|
product_id = sku_to_product_id_mapping.get(product['skuId'])
|
||||||
|
if product_id:
|
||||||
|
order_products.append(
|
||||||
|
OrderProducts(
|
||||||
|
id=str(uuid()),
|
||||||
|
order_id=db_order.id,
|
||||||
|
product_id=product_id,
|
||||||
|
quantity=product['quantity'],
|
||||||
|
unit_price=product['unitPrice']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.db.add_all(order_products)
|
||||||
|
return db_order
|
||||||
|
|
||||||
|
def process_orders(self, orders: list[str]):
|
||||||
|
processed_orders = []
|
||||||
|
for order_id in orders:
|
||||||
|
order = self.get_order(order_id)
|
||||||
|
if order:
|
||||||
|
self.save_order(order)
|
||||||
|
processed_orders.append(order_id)
|
||||||
|
return processed_orders
|
49
app/services/util/_docker.py
Normal file
49
app/services/util/_docker.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class DockerUtil:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def is_in_docker(self) -> bool:
|
||||||
|
"""Check if we're running inside a Docker container using multiple methods"""
|
||||||
|
# Method 1: Check cgroup
|
||||||
|
try:
|
||||||
|
with open('/proc/1/cgroup', 'r') as f:
|
||||||
|
content = f.read().lower()
|
||||||
|
if any(container_id in content for container_id in ['docker', 'containerd', 'kubepods']):
|
||||||
|
logger.debug("Docker detected via cgroup")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Could not read cgroup file: {e}")
|
||||||
|
|
||||||
|
# Method 2: Check /.dockerenv file
|
||||||
|
if os.path.exists('/.dockerenv'):
|
||||||
|
logger.debug("Docker detected via /.dockerenv file")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Method 3: Check environment variables
|
||||||
|
docker_env = any(os.environ.get(var, False) for var in [
|
||||||
|
'DOCKER_CONTAINER',
|
||||||
|
'IN_DOCKER',
|
||||||
|
'KUBERNETES_SERVICE_HOST', # For k8s
|
||||||
|
'DOCKER_HOST'
|
||||||
|
])
|
||||||
|
if docker_env:
|
||||||
|
logger.debug("Docker detected via environment variables")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Method 4: Check container runtime
|
||||||
|
try:
|
||||||
|
with open('/proc/self/mountinfo', 'r') as f:
|
||||||
|
content = f.read().lower()
|
||||||
|
if any(rt in content for rt in ['docker', 'containerd', 'kubernetes']):
|
||||||
|
logger.debug("Docker detected via mountinfo")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"Could not read mountinfo: {e}")
|
||||||
|
|
||||||
|
logger.debug("No Docker environment detected")
|
||||||
|
return False
|
172
app/services/util/_requests.py
Normal file
172
app/services/util/_requests.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
from typing import Dict, Optional
|
||||||
|
from app.services.util._docker import DockerUtil
|
||||||
|
from enum import Enum
|
||||||
|
import browser_cookie3
|
||||||
|
import os
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
from datetime import datetime
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Browser(Enum):
|
||||||
|
"""Supported browser types for cookie extraction"""
|
||||||
|
BRAVE = "brave"
|
||||||
|
CHROME = "chrome"
|
||||||
|
FIREFOX = "firefox"
|
||||||
|
|
||||||
|
class Method(Enum):
|
||||||
|
"""Supported HTTP methods"""
|
||||||
|
GET = "GET"
|
||||||
|
POST = "POST"
|
||||||
|
|
||||||
|
class TCGPlayerEndpoints(Enum):
|
||||||
|
"""Supported TCGPlayer API endpoints"""
|
||||||
|
ORDERS = "https://order-management-api.tcgplayer.com/orders"
|
||||||
|
|
||||||
|
class Headers:
|
||||||
|
ACCEPT = 'application/json, text/plain, */*'
|
||||||
|
ACCEPT_ENCODING = 'gzip, deflate, br, zstd'
|
||||||
|
ACCEPT_LANGUAGE = 'en-US,en;q=0.8'
|
||||||
|
PRIORITY = 'u=1, i'
|
||||||
|
SECCHUA = '"Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133"'
|
||||||
|
SECCHUA_MOBILE = '?0'
|
||||||
|
SECCHUA_PLATFORM = '"macOS"'
|
||||||
|
SEC_FETCH_DEST = 'empty'
|
||||||
|
SEC_FETCH_MODE = 'cors'
|
||||||
|
SEC_FETCH_SITE = 'same-site'
|
||||||
|
SEC_GPC = '1'
|
||||||
|
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36'
|
||||||
|
SELLER_ORIGIN = 'https://sellerportal.tcgplayer.com'
|
||||||
|
SELLER_REFERER = 'https://sellerportal.tcgplayer.com/'
|
||||||
|
|
||||||
|
class RequestHeaders:
|
||||||
|
BASE_HEADERS = {
|
||||||
|
'accept': Headers.ACCEPT,
|
||||||
|
'accept-encoding': Headers.ACCEPT_ENCODING,
|
||||||
|
'accept-language': Headers.ACCEPT_LANGUAGE,
|
||||||
|
'priority': Headers.PRIORITY,
|
||||||
|
'sec-ch-ua': Headers.SECCHUA,
|
||||||
|
'sec-ch-ua-mobile': Headers.SECCHUA_MOBILE,
|
||||||
|
'sec-ch-ua-platform': Headers.SECCHUA_PLATFORM,
|
||||||
|
'sec-fetch-dest': Headers.SEC_FETCH_DEST,
|
||||||
|
'sec-fetch-mode': Headers.SEC_FETCH_MODE,
|
||||||
|
'sec-fetch-site': Headers.SEC_FETCH_SITE,
|
||||||
|
'sec-gpc': Headers.SEC_GPC,
|
||||||
|
'user-agent': Headers.USER_AGENT
|
||||||
|
}
|
||||||
|
SELLER_HEADERS = {
|
||||||
|
'origin': Headers.SELLER_ORIGIN,
|
||||||
|
'referer': Headers.SELLER_REFERER
|
||||||
|
}
|
||||||
|
|
||||||
|
class URLHeaders:
|
||||||
|
# combine base and seller headers
|
||||||
|
ORDER_HEADERS = {**RequestHeaders.BASE_HEADERS, **RequestHeaders.SELLER_HEADERS}
|
||||||
|
|
||||||
|
class RequestsUtil:
|
||||||
|
def __init__(self, browser_type: Browser = Browser.BRAVE):
|
||||||
|
self.browser_type = browser_type
|
||||||
|
self.docker_util = DockerUtil()
|
||||||
|
|
||||||
|
def get_tcgplayer_cookies_from_file(self) -> Dict:
|
||||||
|
# check if cookies file exists
|
||||||
|
if not os.path.exists('cookies/tcg_cookies.json'):
|
||||||
|
raise ValueError("Cookies file not found")
|
||||||
|
with open('cookies/tcg_cookies.json', 'r') as f:
|
||||||
|
logger.debug("Loading cookies from file")
|
||||||
|
cookies = json.load(f)
|
||||||
|
return cookies
|
||||||
|
|
||||||
|
def get_tcgplayer_browser_cookies(self) -> Optional[Dict]:
|
||||||
|
"""Retrieve cookies from the specified browser"""
|
||||||
|
try:
|
||||||
|
cookie_getter = getattr(browser_cookie3, self.browser_type.value, None)
|
||||||
|
if not cookie_getter:
|
||||||
|
raise ValueError(f"Unsupported browser type: {self.browser_type.value}")
|
||||||
|
return cookie_getter()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to get browser cookies: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def rate_limit(self, time_between_requests: int = 10):
|
||||||
|
"""Rate limit requests by waiting for a specified time between requests"""
|
||||||
|
time_diff = (datetime.now() - self.previous_request_time).total_seconds()
|
||||||
|
if time_diff < time_between_requests:
|
||||||
|
# logger.info(f"Waiting {time_between_requests - time_diff} seconds before next request...")
|
||||||
|
time.sleep(time_between_requests - time_diff)
|
||||||
|
|
||||||
|
def send_request(self, url: str, method: str, cookies: dict, data=None) -> requests.Response:
|
||||||
|
"""Send a request with the specified cookies"""
|
||||||
|
|
||||||
|
headers = self.set_headers(url)
|
||||||
|
if not headers:
|
||||||
|
raise ValueError("Headers not set")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.request(method, url, headers=headers, cookies=cookies, data=data)
|
||||||
|
response.raise_for_status()
|
||||||
|
self.previous_request_time = datetime.now()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"Request failed: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_headers(self, url: str):
|
||||||
|
# use tcgplayerendpoints enum to set headers where url partially matches enum value
|
||||||
|
for endpoint in TCGPlayerEndpoints:
|
||||||
|
if endpoint.value in url:
|
||||||
|
return URLHeaders.ORDER_HEADERS
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Endpoint not found in TCGPlayerEndpoints: {url}")
|
||||||
|
|
||||||
|
def old_set_headers(self, method: str) -> Dict:
|
||||||
|
base_headers = {
|
||||||
|
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
|
||||||
|
'accept-language': 'en-US,en;q=0.8',
|
||||||
|
'priority': 'u=0, i',
|
||||||
|
'referer': 'https://store.tcgplayer.com/admin/pricing',
|
||||||
|
'sec-ch-ua': '"Not A(Brand";v="8", "Chromium";v="132", "Brave";v="132"',
|
||||||
|
'sec-ch-ua-mobile': '?0',
|
||||||
|
'sec-ch-ua-platform': '"macOS"',
|
||||||
|
'sec-fetch-dest': 'document',
|
||||||
|
'sec-fetch-mode': 'navigate',
|
||||||
|
'sec-fetch-site': 'same-origin',
|
||||||
|
'sec-fetch-user': '?1',
|
||||||
|
'sec-gpc': '1',
|
||||||
|
'upgrade-insecure-requests': '1',
|
||||||
|
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == 'POST':
|
||||||
|
post_headers = {
|
||||||
|
'cache-control': 'max-age=0',
|
||||||
|
'content-type': 'application/x-www-form-urlencoded',
|
||||||
|
'origin': 'https://store.tcgplayer.com'
|
||||||
|
}
|
||||||
|
base_headers.update(post_headers)
|
||||||
|
|
||||||
|
return base_headers
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
curl 'https://order-management-api.tcgplayer.com/orders/E576ED4C-38871F-B0277?api-version=2.0' \
|
||||||
|
-H 'accept: application/json, text/plain, */*' \
|
||||||
|
-H 'accept-language: en-US,en;q=0.8' \
|
||||||
|
-b 'tcgpartner=PK=TRADECARDS&M=1; valid=set=true; product-display-settings=sort=price+shipping&size=10; TCG_Data=M=1&SearchGameNameID=magic; tcg-uuid=ab16b5f8-dd66-446d-b217-d394328a5cf1; setting=CD=US&M=1; tracking-preferences={%22version%22:1%2C%22destinations%22:{%22Actions%20Amplitude%22:false%2C%22AdWords%22:false%2C%22Google%20AdWords%20New%22:false%2C%22Google%20Enhanced%20Conversions%22:false%2C%22Google%20Tag%20Manager%22:false%2C%22Impact%20Partnership%20Cloud%22:false%2C%22Optimizely%22:false}%2C%22custom%22:{%22advertising%22:false%2C%22functional%22:false%2C%22marketingAndAnalytics%22:false}}; OAuthLoginSessionId=63b1a89d-1ac2-43f7-8e79-55a9ca5e761d; TCGAuthTicket_Production=F453BFF2FA3FAA3D1ACA23F319314F8273713DECEB06C69BB7C208A77C81559B46E1AA22A7E70FABC8D7F681A423C86870FAE318B76048CDE7BF6D73D220631B899BEBA86C422E1EBBF2ACD1921E0846F708AFE203C844031364E13B047465E7B41CB6460E4F4AAB278B614445B93E722E976688; BuyerRevalidationKey=; ASP.NET_SessionId=oouwzrh3jkhdrmaioooqhr4k; TCG_VisitorKey=431efcca-2d5b-404d-a04f-3ae979696051; __RequestVerificationToken_L2FkbWlu0=Lw1sfWh823UeJ7zRux0b1ZTI4Vg4i_dFt97a55aQpf-qBURVuwWDCJyuCxSwgLNLe9nPlfDSc1AMV5nyqhY4Q4jurxs1; fileDownloadToken=1740145585435; spDisabledUIFeatures=' \
|
||||||
|
-H 'origin: https://sellerportal.tcgplayer.com' \
|
||||||
|
-H 'priority: u=1, i' \
|
||||||
|
-H 'referer: https://sellerportal.tcgplayer.com/' \
|
||||||
|
-H 'sec-ch-ua: "Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133"' \
|
||||||
|
-H 'sec-ch-ua-mobile: ?0' \
|
||||||
|
-H 'sec-ch-ua-platform: "macOS"' \
|
||||||
|
-H 'sec-fetch-dest: empty' \
|
||||||
|
-H 'sec-fetch-mode: cors' \
|
||||||
|
-H 'sec-fetch-site: same-site' \
|
||||||
|
-H 'sec-gpc: 1' \
|
||||||
|
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36'
|
||||||
|
"""
|
22
requests.md
22
requests.md
@ -2,16 +2,20 @@ curl -J http://192.168.1.41:8000/api/tcgplayer/inventory/update --remote-name
|
|||||||
|
|
||||||
curl -J -X POST http://192.168.1.41:8000/api/tcgplayer/inventory/add \
|
curl -J -X POST http://192.168.1.41:8000/api/tcgplayer/inventory/add \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"open_box_ids": ["fb629d9d-13d2-405e-9a69-6c44294d55de"]}' \
|
-d '{"open_box_ids": ["02d14109-ad0c-41f9-b49f-24134f962c1a"]}' \
|
||||||
--remote-name
|
--remote-name
|
||||||
|
|
||||||
curl -X POST http://192.168.1.41:8000/api/boxes \
|
curl -X POST http://192.168.1.41:8000/api/boxes \
|
||||||
-F "type=draft" \
|
-F "type=play" \
|
||||||
-F "set_code=CLB" \
|
-F "set_code=DFT" \
|
||||||
-F "sku=195166181127" \
|
-F "sku=195166278636" \
|
||||||
-F "num_cards_expected=480"
|
-F "num_cards_expected=422"
|
||||||
|
|
||||||
curl -X POST "http://192.168.1.41:8000/api/boxes/0d31b9c3-3093-438a-9e8c-b6b70a2d437e/open" \
|
curl -X POST "http://192.168.1.41:8000/api/boxes/0d605f46-25b5-4784-8d45-38dec144ec8e/open" \
|
||||||
-F "product_id=0d31b9c3-3093-438a-9e8c-b6b70a2d437e" \
|
-F "product_id=0d605f46-25b5-4784-8d45-38dec144ec8e" \
|
||||||
-F "file_ids=bb4a022c-5427-49b5-b57c-0147b5e9c4a9" \
|
-F "file_ids=61124a9d-8db3-49aa-bf56-ec0d784ca817" \
|
||||||
-F "date_opened=2025-02-15"
|
-F "date_opened=2025-02-17"
|
||||||
|
|
||||||
|
curl -X POST "http://127.0.0.1:8000/api/processOrders" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"order_ids": ["E576ED4C-A1117E-6904E"]}'
|
Loading…
x
Reference in New Issue
Block a user