commit 893b229cc6b35c09181a84050f34fb79024e41c2 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 22:14:08 2025 -0500 j commit 06f539aea2f4fff9da7038d43d0de553c4423796 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:55:30 2025 -0500 fk commit d0c2960ec9f334448d2eb3573b9d7817482abf46 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:50:53 2025 -0500 frick commit 6b1362c166fc5f51c3bcf316a99116f0d11074a5 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:49:40 2025 -0500 database commit 8cadc6df4c817d9d05503807e56287fd00e5e939 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:38:09 2025 -0500 asdf commit 1ca6f9868452e34143b8df4a412be35e6902a31e Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:32:50 2025 -0500 fffff commit 8bb337a9c35e830ef9ce3dac0a0f2df3fe9bc5a0 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:31:13 2025 -0500 ffff commit 65aba280c55fa09c6a37f688f485efab1f70792b Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:26:16 2025 -0500 aa commit 59ef03a59ee4a15c30e080a1aef7c31c0214a2e3 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:24:21 2025 -0500 asdf commit f44d5740fc9315ccb0792ecac3e8ec9f28f171be Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:23:32 2025 -0500 aaa commit 13c96b164316b4908d9d01e454cbdc9103157558 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:18:54 2025 -0500 sdf commit 949c795fd13d93c9618613740fb093f6bb7b7710 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 21:17:53 2025 -0500 asdf commit 8c3cd423fe228e8aff112a050170246a5fc9f8bd Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 20:56:01 2025 -0500 app2 commit 78eafc739ebb7f100f657964b3ad8f4937a4046b Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 20:54:55 2025 -0500 app commit dc47eced143e77ebec415bdfbe209d9466b7bcf1 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 20:43:15 2025 -0500 asdfasdfasdf commit e24bcae88cf8c14ea543f49b639b2976c627d201 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 20:39:44 2025 -0500 a commit c894451bfe790c97ac0e01085615d7c7288a39da Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 20:38:20 2025 -0500 req commit 3d09869562a96b5adc7c4be279bc8c003bbb37b2 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 20:33:27 2025 -0500 wrong number = code dont work lol i love computers commit 4c93a1271b8aea159cf53f8d7879b00513886d6f Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 20:29:39 2025 -0500 q commit 1f5361da88fe3903a1e92a345fa56bb390f69d92 Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 18:27:20 2025 -0500 same as original code now -5 days of my life commit 511b070cbbcd29b4e784e9a09d58481e50e6e82f Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 13:52:28 2025 -0500 pricey worky commit 964fdd641b63530c59e038ebc7d1e01e9570d75c Author: zman <joshua.k.rzemien@gmail.com> Date: Fri Feb 7 11:37:29 2025 -0500 prep for pricing service work commit a78c3bcba303c2605b6277c1db33b155abe4db1b Author: zman <joshua.k.rzemien@gmail.com> Date: Wed Feb 5 21:51:22 2025 -0500 more stuff yay commit bd9cfca7a95c89b2140eec57bf52bc84432b9a4e Author: zman <joshua.k.rzemien@gmail.com> Date: Tue Feb 4 22:30:33 2025 -0500 GIGA FIXED EVERYTHING OMG commit 85510a46713e0ac660e70c7befb4e94ccf11912e Author: zman <joshua.k.rzemien@gmail.com> Date: Tue Feb 4 00:01:34 2025 -0500 data model change and some new services
315 lines
10 KiB
Python
315 lines
10 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, BackgroundTasks
|
|
from fastapi.responses import StreamingResponse
|
|
from typing import Optional, List
|
|
from io import BytesIO
|
|
import logging
|
|
from datetime import datetime
|
|
import os
|
|
import json
|
|
from pydantic import BaseModel
|
|
|
|
from app.schemas.file import (
|
|
FileSchema,
|
|
CreateFileRequest,
|
|
CreateFileResponse,
|
|
GetFileResponse,
|
|
DeleteFileResponse,
|
|
GetFileQueryParams
|
|
)
|
|
from app.schemas.box import (
|
|
CreateBoxResponse,
|
|
CreateBoxRequest,
|
|
BoxSchema,
|
|
UpdateBoxRequest,
|
|
CreateOpenBoxRequest,
|
|
CreateOpenBoxResponse,
|
|
OpenBoxSchema
|
|
)
|
|
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,
|
|
get_create_file_metadata,
|
|
get_box_data,
|
|
get_box_update_data,
|
|
get_open_box_data,
|
|
get_pricing_service
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
router = APIRouter(prefix="/api", tags=["cards"])
|
|
MAX_FILE_SIZE = 100 * 1024 * 1024 # 100 MB
|
|
|
|
async def validate_file_upload(file: UploadFile) -> bytes:
|
|
"""Validate uploaded file and return its contents."""
|
|
if not file.filename:
|
|
raise HTTPException(status_code=400, detail="No filename provided")
|
|
|
|
content = await file.read()
|
|
if len(content) > MAX_FILE_SIZE:
|
|
raise HTTPException(status_code=413, detail="File too large")
|
|
|
|
return content
|
|
|
|
@router.post("/files", response_model=CreateFileResponse, status_code=201)
|
|
async def create_file(
|
|
background_tasks: BackgroundTasks,
|
|
file: UploadFile = File(...),
|
|
metadata: CreateFileRequest = Depends(get_create_file_metadata),
|
|
file_service: FileService = Depends(get_file_service),
|
|
task_service: TaskService = Depends(get_task_service)
|
|
) -> CreateFileResponse:
|
|
"""Create a new file entry with the uploaded file."""
|
|
try:
|
|
content = await validate_file_upload(file)
|
|
logger.debug(f"File received: {file.filename}")
|
|
logger.debug(f"Metadata: {metadata}")
|
|
|
|
metadata.filename = metadata.filename or file.filename
|
|
|
|
if not file_service.validate_file(content, metadata):
|
|
raise HTTPException(status_code=400, detail="Invalid file content")
|
|
|
|
created_file = file_service.create_file(content, metadata)
|
|
|
|
if metadata.source == 'manabox':
|
|
background_tasks.add_task(task_service.process_manabox_file, created_file)
|
|
|
|
return CreateFileResponse(
|
|
status_code=201,
|
|
success=True,
|
|
files=[FileSchema.from_orm(created_file)]
|
|
)
|
|
|
|
except HTTPException as http_ex:
|
|
raise http_ex
|
|
except Exception as e:
|
|
logger.error(f"File upload failed: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail="Internal server error occurred during file upload"
|
|
)
|
|
finally:
|
|
await file.close()
|
|
|
|
@router.get("/files/{file_id:path}", response_model=GetFileResponse)
|
|
@router.get("/files", response_model=GetFileResponse)
|
|
async def get_file(
|
|
file_id: Optional[str] = None,
|
|
query: GetFileQueryParams = Depends(),
|
|
file_service: FileService = Depends(get_file_service)
|
|
) -> GetFileResponse:
|
|
"""
|
|
Get file(s) by optional ID and/or status.
|
|
If file_id is provided, returns that specific file.
|
|
If status is provided, returns all files with that status.
|
|
If neither is provided, returns all files.
|
|
"""
|
|
try:
|
|
if file_id:
|
|
file = file_service.get_file(file_id)
|
|
files = [file]
|
|
else:
|
|
files = file_service.get_files(status=query.status)
|
|
|
|
return GetFileResponse(
|
|
status_code=200,
|
|
success=True,
|
|
files=[FileSchema.from_orm(f) for f in files]
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Get file(s) failed: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
@router.delete("/files/{file_id}", response_model=DeleteFileResponse)
|
|
async def delete_file(
|
|
file_id: str,
|
|
file_service: FileService = Depends(get_file_service)
|
|
) -> DeleteFileResponse:
|
|
"""Delete a file by ID."""
|
|
try:
|
|
file = file_service.delete_file(file_id)
|
|
return DeleteFileResponse(
|
|
status_code=200,
|
|
success=True,
|
|
files=[FileSchema.from_orm(file)]
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Delete file failed: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
@router.post("/boxes", response_model=CreateBoxResponse, status_code=201)
|
|
async def create_box(
|
|
box_data: CreateBoxRequest = Depends(get_box_data),
|
|
box_service: BoxService = Depends(get_box_service)
|
|
) -> CreateBoxResponse:
|
|
"""Create a new box."""
|
|
try:
|
|
result, success = box_service.create_box(box_data)
|
|
if not success:
|
|
raise HTTPException(status_code=400, detail="Box creation failed, box already exists")
|
|
return CreateBoxResponse(
|
|
status_code=201,
|
|
success=True,
|
|
box=[BoxSchema.from_orm(result)]
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Create box failed: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
@router.put("/boxes/{box_id}", response_model=CreateBoxResponse)
|
|
async def update_box(
|
|
box_id: str,
|
|
box_data: UpdateBoxRequest = Depends(get_box_update_data),
|
|
box_service: BoxService = Depends(get_box_service)
|
|
) -> CreateBoxResponse:
|
|
"""Update an existing box."""
|
|
try:
|
|
result = box_service.update_box(box_id, box_data)
|
|
return CreateBoxResponse(
|
|
status_code=200,
|
|
success=True,
|
|
box=[BoxSchema.from_orm(result)]
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Update box failed: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
@router.delete("/boxes/{box_id}", response_model=CreateBoxResponse)
|
|
async def delete_box(
|
|
box_id: str,
|
|
box_service: BoxService = Depends(get_box_service)
|
|
) -> CreateBoxResponse:
|
|
"""Delete a box by ID."""
|
|
try:
|
|
result = box_service.delete_box(box_id)
|
|
return CreateBoxResponse(
|
|
status_code=200,
|
|
success=True,
|
|
box=[BoxSchema.from_orm(result)]
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Delete box failed: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
@router.post("/boxes/{box_id}/open", response_model=CreateOpenBoxResponse, status_code=201)
|
|
async def open_box(
|
|
box_id: str,
|
|
box_data: CreateOpenBoxRequest = Depends(get_open_box_data),
|
|
box_service: BoxService = Depends(get_box_service)
|
|
) -> CreateOpenBoxResponse:
|
|
"""Open a box by ID."""
|
|
try:
|
|
result = box_service.open_box(box_id, box_data)
|
|
return CreateOpenBoxResponse(
|
|
status_code=201,
|
|
success=True,
|
|
open_box=[OpenBoxSchema.from_orm(result)]
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Open box failed: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
@router.delete("/boxes/{box_id}/open", response_model=CreateOpenBoxResponse, status_code=200)
|
|
async def delete_open_box(
|
|
box_id: str,
|
|
box_service: BoxService = Depends(get_box_service)
|
|
) -> CreateOpenBoxResponse:
|
|
"""Delete an open box by ID."""
|
|
try:
|
|
result = box_service.delete_open_box(box_id)
|
|
return CreateOpenBoxResponse(
|
|
status_code=201,
|
|
success=True,
|
|
open_box=[OpenBoxSchema.from_orm(result)]
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Delete open box failed: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=str(e)
|
|
)
|
|
|
|
@router.post("/tcgplayer/inventory/add", response_class=StreamingResponse)
|
|
async def create_inventory_add_file(
|
|
request: dict, # Just use a dict instead
|
|
pricing_service: PricingService = Depends(get_pricing_service),
|
|
):
|
|
"""Create a new inventory add file for download."""
|
|
try:
|
|
# Get IDs directly from the dict
|
|
open_box_ids = request.get('open_box_ids', [])
|
|
content = pricing_service.generate_tcgplayer_inventory_update_file_with_pricing(open_box_ids)
|
|
|
|
stream = BytesIO(content)
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
return StreamingResponse(
|
|
iter([stream.getvalue()]),
|
|
media_type="text/csv",
|
|
headers={
|
|
'Content-Disposition': f'attachment; filename="inventory_add_{timestamp}.csv"'
|
|
}
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Create inventory add file failed: {str(e)}")
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
@router.get("/tcgplayer/inventory/update", response_class=StreamingResponse)
|
|
async def create_inventory_update_file(
|
|
pricing_service: PricingService = Depends(get_pricing_service),
|
|
):
|
|
"""Create a new inventory update file for download."""
|
|
try:
|
|
content = pricing_service.generate_tcgplayer_inventory_update_file_with_pricing()
|
|
|
|
stream = BytesIO(content)
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
return StreamingResponse(
|
|
iter([stream.getvalue()]),
|
|
media_type="text/csv",
|
|
headers={
|
|
'Content-Disposition': f'attachment; filename="inventory_update_{timestamp}.csv"'
|
|
}
|
|
)
|
|
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)}"
|
|
)
|