Compare commits
	
		
			18 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9e656ef329 | |||
| 18ceef8351 | |||
| 87c84fd0a8 | |||
| 0c78276b12 | |||
| 47a1b1d3ac | |||
| c889a84c34 | |||
| 1ef706afe5 | |||
| 3454f24451 | |||
| 7786655db0 | |||
| ed52e7da04 | |||
| bf3f4ddb38 | |||
| 3f53513c36 | |||
| d3bd696d67 | |||
| 113a920da7 | |||
| 9d11adaf6c | |||
| 8de5bec523 | |||
| 0414018099 | |||
| d4579a9db0 | 
							
								
								
									
										32
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.gitea/workflows/deploy.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| # .gitea/workflows/deploy.yml | ||||
| name: Deploy App to Docker | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: | ||||
|       - main | ||||
|  | ||||
| jobs: | ||||
|   deploy: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|     - name: Checkout code | ||||
|       uses: actions/checkout@v2 | ||||
|  | ||||
|     - name: Set up Docker | ||||
|       uses: docker/setup-buildx-action@v1 | ||||
|       with: | ||||
|         version: 'latest' | ||||
|  | ||||
|     - name: Build Docker Image | ||||
|       run: | | ||||
|         docker build -t giga_tcg . | ||||
|  | ||||
|     - name: Remove existing Docker container | ||||
|       run: | | ||||
|         docker rm -f giga_tcg_container || true | ||||
|  | ||||
|     - name: Run Docker container | ||||
|       run: | | ||||
|         docker run -d -p 8000:8000 --name giga_tcg_container giga_tcg | ||||
| @@ -317,6 +317,7 @@ async def update_cookies( | ||||
|             detail=f"Failed to update cookies: {str(e)}" | ||||
|         ) | ||||
|  | ||||
| ### DEPRECATED ### | ||||
| class TCGPlayerOrderRequest(BaseModel): | ||||
|     order_ids: List[str] | ||||
|  | ||||
| @@ -336,3 +337,5 @@ async def process_orders( | ||||
|     except Exception as e: | ||||
|         logger.error(f"Process orders failed: {str(e)}") | ||||
|         raise HTTPException(status_code=400, detail=str(e)) | ||||
|  | ||||
| ### END DEPRECATED ### | ||||
| @@ -198,8 +198,13 @@ class PricingService: | ||||
|                 .all() | ||||
|             } | ||||
|  | ||||
|             # Map the ids using the dictionary | ||||
|             df['product_id'] = df['tcgplayer_id'].map(product_id_mapping) | ||||
|             # Map tcgplayer_id to product_id, ensure strings, keep None if not found | ||||
|             df['product_id'] = df['tcgplayer_id'].map(product_id_mapping).apply(lambda x: str(x) if pd.notnull(x) else None) | ||||
|  | ||||
|             # Log any tcgplayer_ids that didn't map to a product_id | ||||
|             null_product_ids = df[df['product_id'].isnull()]['tcgplayer_id'].tolist() | ||||
|             if null_product_ids: | ||||
|                 logger.warning(f"The following tcgplayer_ids could not be mapped to a product_id: {null_product_ids}") | ||||
|          | ||||
|         price_lookup = self.get_all_prices_for_products(df['product_id'].unique()) | ||||
|          | ||||
|   | ||||
| @@ -26,6 +26,7 @@ class TaskService: | ||||
|      | ||||
|     def register_scheduled_tasks(self): | ||||
|         self.scheduler.add_job(self.hourly_pricing, 'cron', minute='36') | ||||
|         self.scheduler.add_job(self.hourly_orders, 'cron', hour='*', minute='03') | ||||
|         # every 5 hours on the 24th minute | ||||
|         #self.scheduler.add_job(self.inventory_pricing, 'cron', hour='*', minute='44') | ||||
|         self.logger.info("Scheduled tasks registered.") | ||||
| @@ -35,6 +36,11 @@ class TaskService: | ||||
|         self.pricing_service.cron_load_prices() | ||||
|         self.logger.info("Finished hourly pricing task") | ||||
|      | ||||
|     def hourly_orders(self): | ||||
|         self.logger.info("Running hourly orders task") | ||||
|         self.tcgplayer_api_service.process_orders_task() | ||||
|         self.logger.info("Finished hourly orders task") | ||||
|      | ||||
|     def inventory_pricing(self): | ||||
|         self.tcgplayer_api_service.cron_tcgplayer_api_pricing() | ||||
|  | ||||
|   | ||||
| @@ -16,6 +16,7 @@ class TCGPlayerAPIConfig: | ||||
|     """Configuration for TCGPlayer API""" | ||||
|     ORDER_BASE_URL: str = "https://order-management-api.tcgplayer.com/orders" | ||||
|     API_VERSION: str = "?api-version=2.0" | ||||
|     SELLER_KEY: str = "e576ed4c" | ||||
|  | ||||
| class TCGPlayerAPIService: | ||||
|     def __init__(self, db: Session): | ||||
| @@ -40,6 +41,25 @@ class TCGPlayerAPIService: | ||||
|             return response.json() | ||||
|         return None | ||||
|      | ||||
|     def get_orders(self, size: int = 25) -> dict: | ||||
|         url = f"{self.config.ORDER_BASE_URL}/search{self.config.API_VERSION}" | ||||
|         payload = { | ||||
|             "searchRange": "LastThreeMonths", | ||||
|             "filters": { | ||||
|                 "sellerKey": self.config.SELLER_KEY | ||||
|             }, | ||||
|             "sortBy": [ | ||||
|                 {"sortingType": "orderStatus", "direction": "ascending"}, | ||||
|                 {"sortingType": "orderDate", "direction": "descending"} | ||||
|             ], | ||||
|             "from": 0, | ||||
|             "size": size | ||||
|         } | ||||
|         response = self.requests_util.send_request(url, method='POST', cookies=self.cookies, json=payload) | ||||
|         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""" | ||||
|         # convert SKU IDs to integers | ||||
| @@ -101,14 +121,30 @@ class TCGPlayerAPIService: | ||||
|             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 | ||||
|     def process_orders_task(self): | ||||
|         # get last 25 orders from tcgplayer | ||||
|         orders = self.get_orders(size=100) | ||||
|         if orders: | ||||
|             # get list of order ids | ||||
|             order_ids = [order['orderNumber'] for order in orders['orders']] | ||||
|             # get a list of order ids that are not in the database | ||||
|             existing_orders = self.db.query(Orders).filter(Orders.order_id.in_(order_ids)).all() | ||||
|             existing_order_ids = [order.order_id for order in existing_orders] | ||||
|             # get a list of order ids that are not in the database | ||||
|             new_order_ids = [order_id for order_id in order_ids if order_id not in existing_order_ids] | ||||
|             # process new orders | ||||
|             processed_orders = [] | ||||
|             if new_order_ids: | ||||
|                 logger.info(f"Processing {len(new_order_ids)} new orders") | ||||
|                 new_orders = [order for order in orders['orders'] if order['orderNumber'] in new_order_ids] | ||||
|                 for new_order in new_orders: | ||||
|                     order = self.get_order(new_order['orderNumber']) | ||||
|                     self.save_order(order) | ||||
|                     processed_orders.append(order['orderNumber']) | ||||
|                 logger.info(f"Processed {len(processed_orders)} new orders") | ||||
|                 return processed_orders | ||||
|             else: | ||||
|                 logger.info("No new orders to process") | ||||
|  | ||||
|     def get_scryfall_data(self, scryfall_id: str): | ||||
|         url = f"https://api.scryfall.com/cards/{scryfall_id}?format=json" | ||||
| @@ -133,7 +169,6 @@ class TCGPlayerAPIService: | ||||
|             'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0 Safari/537.36' | ||||
|         } | ||||
|         url = f"https://mp-search-api.tcgplayer.com/v2/product/{tcgplayer_id}/details?mpfev=3279" | ||||
|         #self.requests_util.rate_limit() | ||||
|         response = self.session.get(url, headers=headers) | ||||
|         self.requests_util.previous_request_time = datetime.now() | ||||
|         return response | ||||
|   | ||||
| @@ -61,10 +61,14 @@ class RequestHeaders: | ||||
|         'origin': Headers.SELLER_ORIGIN, | ||||
|         'referer': Headers.SELLER_REFERER | ||||
|     } | ||||
|     POST_HEADERS = { | ||||
|         'content-type': 'application/json' | ||||
|     } | ||||
|  | ||||
| class URLHeaders: | ||||
|     # combine base and seller headers | ||||
|     ORDER_HEADERS = {**RequestHeaders.BASE_HEADERS, **RequestHeaders.SELLER_HEADERS} | ||||
|     POST_HEADERS = {**RequestHeaders.BASE_HEADERS, **RequestHeaders.SELLER_HEADERS, **RequestHeaders.POST_HEADERS} | ||||
|  | ||||
| class RequestsUtil: | ||||
|     def __init__(self, browser_type: Browser = Browser.BRAVE): | ||||
| @@ -113,19 +117,18 @@ class RequestsUtil: | ||||
|         """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: | ||||
|     def send_request(self, url: str, method: str, cookies: dict,  data=None, json=None) -> requests.Response: | ||||
|         """Send a request with the specified cookies""" | ||||
|                  | ||||
|         headers = self.set_headers(url) | ||||
|         headers = self.set_headers(url, method) | ||||
|         if not headers: | ||||
|             raise ValueError("Headers not set") | ||||
|  | ||||
|         try: | ||||
|             self.rate_limit() | ||||
|             response = requests.request(method, url, headers=headers, cookies=cookies, data=data) | ||||
|             response = requests.request(method, url, headers=headers, cookies=cookies, data=data, json=json) | ||||
|             response.raise_for_status() | ||||
|             self.previous_request_time = datetime.now() | ||||
|              | ||||
| @@ -135,57 +138,12 @@ class RequestsUtil: | ||||
|             logger.error(f"Request failed: {str(e)}") | ||||
|             return None | ||||
|          | ||||
|     def set_headers(self, url: str): | ||||
|     def set_headers(self, url: str, method: str) -> Dict: | ||||
|         # use tcgplayerendpoints enum to set headers where url partially matches enum value | ||||
|         for endpoint in TCGPlayerEndpoints: | ||||
|             if endpoint.value in url: | ||||
|             if endpoint.value in url and str.upper(method) == "POST": | ||||
|                 return URLHeaders.POST_HEADERS | ||||
|             elif 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' | ||||
| """ | ||||
		Reference in New Issue
	
	Block a user