19 Commits

Author SHA1 Message Date
893b229cc6 j 2025-02-07 22:14:08 -05:00
06f539aea2 fk 2025-02-07 21:55:30 -05:00
d0c2960ec9 frick 2025-02-07 21:50:53 -05:00
6b1362c166 database 2025-02-07 21:49:40 -05:00
8cadc6df4c asdf 2025-02-07 21:38:09 -05:00
1ca6f98684 fffff 2025-02-07 21:32:50 -05:00
8bb337a9c3 ffff 2025-02-07 21:31:13 -05:00
65aba280c5 aa 2025-02-07 21:26:16 -05:00
59ef03a59e asdf 2025-02-07 21:24:21 -05:00
f44d5740fc aaa 2025-02-07 21:23:32 -05:00
13c96b1643 sdf 2025-02-07 21:18:54 -05:00
949c795fd1 asdf 2025-02-07 21:17:53 -05:00
8c3cd423fe app2 2025-02-07 20:56:01 -05:00
78eafc739e app 2025-02-07 20:54:55 -05:00
dc47eced14 asdfasdfasdf 2025-02-07 20:43:15 -05:00
e24bcae88c a 2025-02-07 20:39:44 -05:00
c894451bfe req 2025-02-07 20:38:20 -05:00
3d09869562 wrong number = code dont work lol i love computers 2025-02-07 20:33:27 -05:00
4c93a1271b q 2025-02-07 20:29:39 -05:00
39 changed files with 502 additions and 87 deletions

1
.gitignore vendored
View File

@ -173,3 +173,4 @@ cython_debug/
temp/
.DS_Store
*.db-journal
cookies/

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
FROM python:3.13-slim
WORKDIR /app
ENV DATABASE_URL=postgresql://poggers:giga!@192.168.1.41:5432/omegatcgdb
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

119
alembic.ini Normal file
View File

@ -0,0 +1,119 @@
# A generic, single database configuration.
[alembic]
# path to migration scripts
# Use forward slashes (/) also on windows to provide an os agnostic path
script_location = alembic
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
# for all available tokens
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
# sys.path path, will be prepended to sys.path if present.
# defaults to the current working directory.
prepend_sys_path = .
# timezone to use when rendering the date within the migration file
# as well as the filename.
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
# string value is passed to ZoneInfo()
# leave blank for localtime
# timezone =
# max length of characters to apply to the "slug" field
# truncate_slug_length = 40
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false
# version location specification; This defaults
# to alembic/versions. When using multiple version
# directories, initial revisions must be specified with --version-path.
# The path separator used here should be the separator specified by "version_path_separator" below.
# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions
# version path separator; As mentioned above, this is the character used to split
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
# Valid values for version_path_separator are:
#
# version_path_separator = :
# version_path_separator = ;
# version_path_separator = space
# version_path_separator = newline
#
# Use os.pathsep. Default configuration used for new projects.
version_path_separator = os
# set to 'true' to search source files recursively
# in each "version_locations" directory
# new in Alembic version 1.10
# recursive_version_locations = false
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = sqlite:///omegacard.db
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples
# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks = black
# black.type = console_scripts
# black.entrypoint = black
# black.options = -l 79 REVISION_SCRIPT_FILENAME
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
# hooks = ruff
# ruff.type = exec
# ruff.executable = %(here)s/.venv/bin/ruff
# ruff.options = --fix REVISION_SCRIPT_FILENAME
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARNING
handlers = console
qualname =
[logger_sqlalchemy]
level = WARNING
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

1
alembic/README Normal file
View File

@ -0,0 +1 @@
Generic single-database configuration.

90
alembic/env.py Normal file
View File

@ -0,0 +1,90 @@
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
import sys
import os
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from app.db.models import Base
from app.db.database import DATABASE_URL
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
config.set_main_option('sqlalchemy.url', DATABASE_URL)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None:
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

26
alembic/script.py.mako Normal file
View File

@ -0,0 +1,26 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision: str = ${repr(up_revision)}
down_revision: Union[str, None] = ${repr(down_revision)}
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
def upgrade() -> None:
${upgrades if upgrades else "pass"}
def downgrade() -> None:
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,30 @@
"""empty message
Revision ID: f629adc7e597
Revises:
Create Date: 2025-02-07 20:13:32.559672
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'f629adc7e597'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@ -4,10 +4,10 @@ from contextlib import contextmanager
from typing import Generator
import os
from sqlalchemy import inspect
from services.tcgplayer import TCGPlayerService
from services.pricing import PricingService
from services.file import FileService
from db.models import Price
from app.services.tcgplayer import TCGPlayerService
from app.services.pricing import PricingService
from app.services.file import FileService
from app.db.models import Price
from datetime import datetime

View File

@ -132,7 +132,7 @@ class Card(Base):
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
class CardManabox(Base):
__tablename__ = "card_manabox"
__tablename__ = "manabox_cards"
product_id = Column(String, ForeignKey("cards.product_id"), primary_key=True)
name = Column(String)
@ -147,10 +147,10 @@ class CardManabox(Base):
language = Column(String)
class CardTCGPlayer(Base):
__tablename__ = "card_tcgplayer"
__tablename__ = "tcgplayer_cards"
product_id = Column(String, ForeignKey("cards.product_id"), primary_key=True)
group_id = Column(Integer, ForeignKey("tcgplayer_groups.group_id"))
group_id = Column(Integer)
tcgplayer_id = Column(Integer)
product_line = Column(String)
set_name = Column(String)
@ -164,7 +164,7 @@ class Warehouse(Base):
"""
container that is associated with a user and contains inventory and stock
"""
__tablename__ = "warehouse"
__tablename__ = "warehouses"
id = Column(String, primary_key=True)
user_id = Column(String, ForeignKey("users.id"))
@ -175,10 +175,10 @@ class Stock(Base):
"""
contains products that are listed for sale
"""
__tablename__ = "stock"
__tablename__ = "stocks"
product_id = Column(String, ForeignKey("products.id"), primary_key=True)
warehouse_id = Column(String, ForeignKey("warehouse.id"), default="default")
warehouse_id = Column(String, ForeignKey("warehouses.id"), default="default")
marketplace_id = Column(String, ForeignKey("marketplaces.id"))
quantity = Column(Integer)
date_created = Column(DateTime, default=datetime.now)
@ -190,10 +190,10 @@ class Inventory(Base):
sealed product in breakdown queue, held sealed product, speculatively held singles, etc.
inventory can contain products across multiple marketplaces
"""
__tablename__ = "inventory"
__tablename__ = "inventories"
product_id = Column(String, ForeignKey("products.id"), primary_key=True)
warehouse_id = Column(String, ForeignKey("warehouse.id"), default="default")
warehouse_id = Column(String, ForeignKey("warehouses.id"), default="default")
quantity = Column(Integer)
date_created = Column(DateTime, default=datetime.now)
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
@ -248,7 +248,7 @@ class File(Base):
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
class Price(Base):
__tablename__ = "price"
__tablename__ = "prices"
id = Column(String, primary_key=True)
product_id = Column(String, ForeignKey("products.id"))
@ -271,7 +271,7 @@ class StorageBlock(Base):
return type
id = Column(String, primary_key=True)
warehouse_id = Column(String, ForeignKey("warehouse.id"))
warehouse_id = Column(String, ForeignKey("warehouses.id"))
name = Column(String)
type = Column(String) # rare or common
date_created = Column(DateTime, default=datetime.now)
@ -282,7 +282,7 @@ class ProductBlock(Base):
ProductBlock represents the relationship between a product and a storage block
which products are in a block and at what index
"""
__tablename__ = "product_block"
__tablename__ = "product_blocks"
id = Column(String, primary_key=True)
product_id = Column(String, ForeignKey("products.id"))
@ -295,7 +295,7 @@ class OpenBoxCard(Base):
"""
OpenedBoxCard represents the relationship between an opened box and the cards it contains
"""
__tablename__ = "open_box_card"
__tablename__ = "open_box_cards"
id = Column(String, primary_key=True)
open_box_id = Column(String, ForeignKey("open_boxes.id"))
@ -308,7 +308,7 @@ class ProductSale(Base):
"""
ProductSale represents the relationship between products and sales
"""
__tablename__ = "product_sale"
__tablename__ = "product_sales"
id = Column(String, primary_key=True)
product_id = Column(String, ForeignKey("products.id"))

View File

@ -1,6 +1,6 @@
from contextlib import contextmanager
from sqlalchemy.orm import Session
from exceptions import FailedUploadException
from app.exceptions import FailedUploadException
import logging
logger = logging.getLogger(__name__)

View File

@ -2,17 +2,17 @@ from typing import Annotated
from sqlalchemy.orm import Session
from fastapi import Depends, Form
from services.box import BoxService
from services.tcgplayer import TCGPlayerService
from services.pricing import PricingService
from services.file import FileService
from services.product import ProductService
from services.inventory import InventoryService
from services.task import TaskService
from services.storage import StorageService
from db.database import get_db
from schemas.file import CreateFileRequest
from schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxRequest
from app.services.box import BoxService
from app.services.tcgplayer import TCGPlayerService
from app.services.pricing import PricingService
from app.services.file import FileService
from app.services.product import ProductService
from app.services.inventory import InventoryService
from app.services.task import TaskService
from app.services.storage import StorageService
from app.db.database import get_db
from app.schemas.file import CreateFileRequest
from app.schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxRequest
# Common type annotation for database dependency
DB = Annotated[Session, Depends(get_db)]

View File

@ -1,13 +1,13 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import uvicorn
from routes.routes import router
from db.database import init_db, check_db_connection, get_db
from app.routes.routes import router
from app.db.database import init_db, check_db_connection, get_db
import logging
import sys
# Import your dependency functions
from dependencies import (
from app.dependencies import (
get_task_service,
get_tcgplayer_service,
get_pricing_service,

View File

@ -4,8 +4,11 @@ from typing import Optional, List
from io import BytesIO
import logging
from datetime import datetime
import os
import json
from pydantic import BaseModel
from schemas.file import (
from app.schemas.file import (
FileSchema,
CreateFileRequest,
CreateFileResponse,
@ -13,7 +16,7 @@ from schemas.file import (
DeleteFileResponse,
GetFileQueryParams
)
from schemas.box import (
from app.schemas.box import (
CreateBoxResponse,
CreateBoxRequest,
BoxSchema,
@ -22,11 +25,11 @@ from schemas.box import (
CreateOpenBoxResponse,
OpenBoxSchema
)
from services.file import FileService
from services.box import BoxService
from services.task import TaskService
from services.pricing import PricingService
from dependencies import (
from app.services.file import FileService
from app.services.box import BoxService
from app.services.task import TaskService
from app.services.pricing import PricingService
from app.dependencies import (
get_file_service,
get_box_service,
get_task_service,
@ -276,3 +279,36 @@ async def create_inventory_update_file(
except Exception as e:
logger.error(f"Create inventory update file failed: {str(e)}")
raise HTTPException(status_code=400, detail=str(e))
class CookieUpdate(BaseModel):
cookies: dict
# cookies
@router.post("/cookies", response_model=dict)
async def update_cookies(
cookie_data: CookieUpdate
):
try:
# Create cookies directory if it doesn't exist
os.makedirs('cookies', exist_ok=True)
# Save cookies with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
cookie_path = f'cookies/tcg_cookies.json'
# Save new cookies
with open(cookie_path, 'w') as f:
json.dump(cookie_data.cookies, f, indent=2)
# Update the "latest" cookies file
with open('cookies/tcg_cookies_latest.json', 'w') as f:
json.dump(cookie_data.cookies, f, indent=2)
return {"message": "Cookies updated successfully"}
except Exception as e:
raise HTTPException(
status_code=500,
detail=f"Failed to update cookies: {str(e)}"
)

View File

@ -1,5 +1,5 @@
from pydantic import BaseModel, Field, ConfigDict
from schemas.base import BaseSchema
from app.schemas.base import BaseSchema
from typing import Optional
from datetime import datetime

View File

@ -5,7 +5,7 @@ from sqlalchemy import or_
from sqlalchemy.orm import Session
import logging
from db.models import (
from app.db.models import (
Box,
File,
StagedFileProduct,
@ -15,9 +15,9 @@ from db.models import (
TCGPlayerGroups,
Inventory
)
from db.utils import db_transaction
from schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxRequest
from services.inventory import InventoryService
from app.db.utils import db_transaction
from app.schemas.box import CreateBoxRequest, UpdateBoxRequest, CreateOpenBoxRequest
from app.services.inventory import InventoryService
logger = logging.getLogger(__name__)

View File

@ -6,15 +6,15 @@ import logging
import os
from io import StringIO
from db.utils import db_transaction
from db.models import File, StagedFileProduct
from schemas.file import CreateFileRequest
from app.db.utils import db_transaction
from app.db.models import File, StagedFileProduct
from app.schemas.file import CreateFileRequest
logger = logging.getLogger(__name__)
class FileConfig:
"""Configuration constants for file processing"""
TEMP_DIR = os.path.join(os.getcwd(), 'temp')
TEMP_DIR = os.path.join(os.getcwd(), 'app/' + 'temp')
MANABOX_HEADERS = [
'Name', 'Set code', 'Set name', 'Collector number', 'Foil',

View File

@ -3,9 +3,9 @@ from typing import Dict
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from db.models import Product, Inventory
from schemas.inventory import UpdateInventoryResponse
from db.utils import db_transaction
from app.db.models import Product, Inventory
from app.schemas.inventory import UpdateInventoryResponse
from app.db.utils import db_transaction
class InventoryService:

View File

@ -1,10 +1,10 @@
from sqlalchemy.orm import Session
from db.models import File, CardTCGPlayer, Price
from services.util._dataframe import TCGPlayerPricingRow, DataframeUtil
from services.file import FileService
from services.tcgplayer import TCGPlayerService
from app.db.models import File, CardTCGPlayer, Price
from app.services.util._dataframe import TCGPlayerPricingRow, DataframeUtil
from app.services.file import FileService
from app.services.tcgplayer import TCGPlayerService
from uuid import uuid4
from db.utils import db_transaction
from app.db.utils import db_transaction
from typing import List, Dict
import pandas as pd
import logging

View File

@ -3,12 +3,12 @@ from uuid import uuid4
from pandas import DataFrame
from sqlalchemy.orm import Session
from db.utils import db_transaction
from db.models import CardManabox, CardTCGPlayer, StagedFileProduct, TCGPlayerGroups
from services.util._dataframe import ManaboxRow, DataframeUtil
from services.file import FileService
from services.tcgplayer import TCGPlayerService
from services.storage import StorageService
from app.db.utils import db_transaction
from app.db.models import CardManabox, CardTCGPlayer, StagedFileProduct, TCGPlayerGroups
from app.services.util._dataframe import ManaboxRow, DataframeUtil
from app.services.file import FileService
from app.services.tcgplayer import TCGPlayerService
from app.services.storage import StorageService
logger = getLogger(__name__)

View File

@ -2,8 +2,8 @@ from uuid import uuid4
from typing import List, TypedDict
from sqlalchemy.orm import Session
from db.utils import db_transaction
from db.models import (
from app.db.utils import db_transaction
from app.db.models import (
Warehouse,
User,
StagedFileProduct,

View File

@ -2,9 +2,9 @@ from apscheduler.schedulers.background import BackgroundScheduler
import logging
from typing import Dict, Callable
from sqlalchemy.orm import Session
from services.product import ProductService
from db.models import File
from services.pricing import PricingService
from app.services.product import ProductService
from app.db.models import File
from app.services.pricing import PricingService
class TaskService:
@ -23,7 +23,7 @@ class TaskService:
# self.pricing_service.generate_tcgplayer_inventory_update_file_with_pricing(['e20cc342-23cb-4593-89cb-56a0cb3ed3f3'])
def register_scheduled_tasks(self):
self.scheduler.add_job(self.hourly_pricing, 'cron', minute='0')
self.scheduler.add_job(self.hourly_pricing, 'cron', minute='45')
self.logger.info("Scheduled tasks registered.")
def hourly_pricing(self):

View File

@ -1,10 +1,10 @@
from db.models import TCGPlayerGroups, CardTCGPlayer, Product, Card, File, Inventory, OpenBox, OpenBoxCard
from app.db.models import TCGPlayerGroups, CardTCGPlayer, Product, Card, File, Inventory, OpenBox, OpenBoxCard
import requests
from services.util._dataframe import TCGPlayerPricingRow, DataframeUtil, ManaboxRow
from services.file import FileService
from services.inventory import InventoryService
from app.services.util._dataframe import TCGPlayerPricingRow, DataframeUtil, ManaboxRow
from app.services.file import FileService
from app.services.inventory import InventoryService
from sqlalchemy.orm import Session
from db.utils import db_transaction
from app.db.utils import db_transaction
from uuid import uuid4 as uuid
import browser_cookie3
import webbrowser
@ -19,7 +19,8 @@ import time
from typing import List, Dict, Optional
import pandas as pd
from sqlalchemy.exc import SQLAlchemyError
from schemas.file import CreateFileRequest
from app.schemas.file import CreateFileRequest
import os
logger = logging.getLogger(__name__)
@ -98,6 +99,15 @@ class TCGPlayerService:
with db_transaction(self.db):
self._insert_groups(groups)
def get_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_browser_cookies(self) -> Optional[Dict]:
"""Retrieve cookies from the specified browser"""
try:
@ -109,20 +119,69 @@ class TCGPlayerService:
logger.error(f"Failed to get browser cookies: {str(e)}")
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:
"""Send a request with the specified cookies"""
# if previous request was made less than 10 seconds ago, wait until current time is 10 seconds after previous request
# Rate limiting logic
if self.previous_request_time:
time_diff = (datetime.now() - self.previous_request_time).total_seconds()
if time_diff < 10:
logger.info(f"Waiting 10 seconds before next request...")
time.sleep(10 - time_diff)
headers = self._set_headers(method)
# Move cookie initialization outside and make it more explicit
if not self.cookies:
if self.is_in_docker():
logger.debug("Running in Docker - using cookies from file")
self.cookies = self.get_cookies_from_file()
else:
logger.debug("Not in Docker - using browser cookies")
self.cookies = self._get_browser_cookies()
if not self.cookies:
raise ValueError("Failed to retrieve browser cookies")
raise ValueError("Failed to retrieve cookies")
try:
#logger.info(f"debug: request url {url}, method {method}, data {data}")
@ -459,7 +518,7 @@ class TCGPlayerService:
def get_pricing_export_for_all_products(self) -> File:
"""
"""
DEBUG = True
DEBUG = False
if DEBUG:
logger.debug("DEBUG: Using existing pricing export file")
file = self.db.query(File).filter(File.type == 'tcgplayer_pricing_export').first()

View File

@ -1,6 +1,6 @@
import pandas as pd
from io import StringIO
from db.models import File
from app.db.models import File
class ManaboxRow:

View File

@ -2,7 +2,7 @@ from fastapi.testclient import TestClient
from fastapi import BackgroundTasks
import pytest
import os
from main import app
from app.main import app

View File

@ -4,9 +4,9 @@ import pytest
from unittest.mock import Mock, patch
import asyncio
import os
from main import app
from services.file import FileService
from services.task import TaskService
from app.main import app
from app.services.file import FileService
from app.services.task import TaskService
client = TestClient(app)

23
requests.md Normal file
View File

@ -0,0 +1,23 @@
curl -J http://192.168.1.41:8000/api/tcgplayer/inventory/update --remote-name
curl -J -X POST \ -H "Content-Type: application/json" \
-d '{"open_box_ids": ["e20cc342-23cb-4593-89cb-56a0cb3ed3f3"]}' \
http://192.168.1.41:8000/api/tcgplayer/inventory/add --remote-name
curl -X POST http://192.168.1.41:8000/api/boxes \
-H "Content-Type: application/json" \
-d '{
"type": "collector",
"set_code": "MOM",
"sku": "ABC123",
"num_cards_expected": 15
}'
curl -X POST http://192.168.1.41:8000/api/boxes/box123/open \
-H "Content-Type: application/json" \
-d '{
"product_id": "box123",
"file_ids": ["file1", "file2"],
"num_cards_actual": 15,
"date_opened": "2025-02-07T12:00:00Z"
}'

View File

@ -1,18 +1,32 @@
alembic==1.14.1
annotated-types==0.7.0
anyio==4.8.0
APScheduler==3.11.0
browser-cookie3==0.20.1
certifi==2025.1.31
charset-normalizer==3.4.1
click==8.1.8
coverage==7.6.10
fastapi==0.115.8
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
idna==3.10
iniconfig==2.0.0
lz4==4.4.3
Mako==1.3.9
MarkupSafe==3.0.2
numpy==2.2.2
packaging==24.2
pandas==2.2.3
pluggy==1.5.0
psycopg2-binary==2.9.10
pycryptodomex==3.21.0
pydantic==2.10.6
pydantic_core==2.27.2
pytest==8.3.4
pytest-asyncio==0.25.3
pytest-cov==6.0.0
python-dateutil==2.9.0.post0
python-multipart==0.0.20
pytz==2025.1
@ -23,5 +37,6 @@ SQLAlchemy==2.0.37
starlette==0.45.3
typing_extensions==4.12.2
tzdata==2025.1
tzlocal==5.2
urllib3==2.3.0
uvicorn==0.34.0