Compare commits
25 Commits
721b26ce97
...
docker
Author | SHA1 | Date | |
---|---|---|---|
893b229cc6 | |||
06f539aea2 | |||
d0c2960ec9 | |||
6b1362c166 | |||
8cadc6df4c | |||
1ca6f98684 | |||
8bb337a9c3 | |||
65aba280c5 | |||
59ef03a59e | |||
f44d5740fc | |||
13c96b1643 | |||
949c795fd1 | |||
8c3cd423fe | |||
78eafc739e | |||
dc47eced14 | |||
e24bcae88c | |||
c894451bfe | |||
3d09869562 | |||
4c93a1271b | |||
1f5361da88 | |||
511b070cbb | |||
964fdd641b | |||
a78c3bcba3 | |||
bd9cfca7a9 | |||
85510a4671 |
@@ -243,7 +243,7 @@ class File(Base):
|
|||||||
filepath = Column(String) # backup location
|
filepath = Column(String) # backup location
|
||||||
filesize_kb = Column(Float)
|
filesize_kb = Column(Float)
|
||||||
status = Column(String)
|
status = Column(String)
|
||||||
box_id = Column(String, nullable=True)
|
box_id = Column(String, ForeignKey("boxes.product_id"), nullable=True)
|
||||||
date_created = Column(DateTime, default=datetime.now)
|
date_created = Column(DateTime, default=datetime.now)
|
||||||
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
date_modified = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, BackgroundTasks, Request
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, BackgroundTasks
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from typing import Optional, List
|
from typing import Optional, List
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@@ -233,18 +233,16 @@ async def delete_open_box(
|
|||||||
raise HTTPException(status_code=400, detail=str(e)
|
raise HTTPException(status_code=400, detail=str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
class InventoryAddRequest(BaseModel):
|
|
||||||
open_box_ids: List[str]
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/tcgplayer/inventory/add", response_class=StreamingResponse)
|
@router.post("/tcgplayer/inventory/add", response_class=StreamingResponse)
|
||||||
async def create_inventory_add_file(
|
async def create_inventory_add_file(
|
||||||
body: InventoryAddRequest,
|
request: dict, # Just use a dict instead
|
||||||
pricing_service: PricingService = Depends(get_pricing_service),
|
pricing_service: PricingService = Depends(get_pricing_service),
|
||||||
):
|
):
|
||||||
"""Create a new inventory add file for download."""
|
"""Create a new inventory add file for download."""
|
||||||
try:
|
try:
|
||||||
content = pricing_service.generate_tcgplayer_inventory_update_file_with_pricing(body.open_box_ids)
|
# 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)
|
stream = BytesIO(content)
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
@@ -292,20 +290,21 @@ async def update_cookies(
|
|||||||
cookie_data: CookieUpdate
|
cookie_data: CookieUpdate
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
# see if cookie file exists
|
|
||||||
if not os.path.exists('cookies') or os.path.exists('cookies/tcg_cookies.json'):
|
|
||||||
logger.info("Cannot find cookies")
|
|
||||||
# Create cookies directory if it doesn't exist
|
# Create cookies directory if it doesn't exist
|
||||||
os.makedirs('cookies', exist_ok=True)
|
os.makedirs('cookies', exist_ok=True)
|
||||||
|
|
||||||
# Save cookies with timestamp
|
# Save cookies with timestamp
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
cookie_path = 'cookies/tcg_cookies.json'
|
cookie_path = f'cookies/tcg_cookies.json'
|
||||||
|
|
||||||
# Save new cookies
|
# Save new cookies
|
||||||
with open(cookie_path, 'w') as f:
|
with open(cookie_path, 'w') as f:
|
||||||
json.dump(cookie_data.cookies, f, indent=2)
|
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"}
|
return {"message": "Cookies updated successfully"}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@@ -57,7 +57,7 @@ class CreateOpenBoxRequest(BaseModel):
|
|||||||
product_id: str = Field(..., title="Product ID")
|
product_id: str = Field(..., title="Product ID")
|
||||||
file_ids: list[str] = Field(None, title="File IDs")
|
file_ids: list[str] = Field(None, title="File IDs")
|
||||||
num_cards_actual: Optional[int] = Field(None, title="Number of cards actual")
|
num_cards_actual: Optional[int] = Field(None, title="Number of cards actual")
|
||||||
date_opened: Optional[str] = Field(None, title="Date Opened")
|
date_opened: Optional [str] = Field(None, title="Date Opened")
|
||||||
|
|
||||||
# RESPONSE
|
# RESPONSE
|
||||||
class CreateOpenBoxResponse(BaseModel):
|
class CreateOpenBoxResponse(BaseModel):
|
||||||
|
@@ -45,7 +45,7 @@ class BoxService:
|
|||||||
|
|
||||||
def add_products_to_open_box(self, open_box: OpenBox, product_data: Dict[Product, int]) -> None:
|
def add_products_to_open_box(self, open_box: OpenBox, product_data: Dict[Product, int]) -> None:
|
||||||
"""Add products to an open box."""
|
"""Add products to an open box."""
|
||||||
for product, quantity in product_data.items(): # TODO BATCH THIS
|
for product, quantity in product_data.items():
|
||||||
open_box_card = OpenBoxCard(
|
open_box_card = OpenBoxCard(
|
||||||
id=str(uuid4()),
|
id=str(uuid4()),
|
||||||
open_box_id=open_box.id,
|
open_box_id=open_box.id,
|
||||||
@@ -86,8 +86,6 @@ class BoxService:
|
|||||||
type='box',
|
type='box',
|
||||||
product_line='mtg'
|
product_line='mtg'
|
||||||
)
|
)
|
||||||
self.db.add(product)
|
|
||||||
self.db.flush()
|
|
||||||
box = Box(
|
box = Box(
|
||||||
product_id=product.id,
|
product_id=product.id,
|
||||||
type=create_box_data.type,
|
type=create_box_data.type,
|
||||||
@@ -95,6 +93,7 @@ class BoxService:
|
|||||||
sku=create_box_data.sku,
|
sku=create_box_data.sku,
|
||||||
num_cards_expected=create_box_data.num_cards_expected
|
num_cards_expected=create_box_data.num_cards_expected
|
||||||
)
|
)
|
||||||
|
self.db.add(product)
|
||||||
self.db.add(box)
|
self.db.add(box)
|
||||||
|
|
||||||
return box, True
|
return box, True
|
||||||
|
@@ -38,8 +38,7 @@ class InventoryService:
|
|||||||
if inventory is None:
|
if inventory is None:
|
||||||
inventory = Inventory(
|
inventory = Inventory(
|
||||||
product_id=product.id,
|
product_id=product.id,
|
||||||
quantity=quantity,
|
quantity=quantity
|
||||||
warehouse_id="0f0d01b1-97ba-4ab2-9082-22062bca9b06" # TODO FIX
|
|
||||||
)
|
)
|
||||||
self.db.add(inventory)
|
self.db.add(inventory)
|
||||||
else:
|
else:
|
||||||
@@ -62,7 +61,7 @@ class InventoryService:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
with db_transaction(self.db):
|
with db_transaction(self.db):
|
||||||
for product, quantity in product_data.items(): # TODO BATCH THIS
|
for product, quantity in product_data.items():
|
||||||
self.add_inventory(product, quantity)
|
self.add_inventory(product, quantity)
|
||||||
return UpdateInventoryResponse(success=True)
|
return UpdateInventoryResponse(success=True)
|
||||||
except SQLAlchemyError:
|
except SQLAlchemyError:
|
||||||
|
@@ -117,7 +117,6 @@ class PricingService:
|
|||||||
"""Default pricing algorithm with complex pricing rules"""
|
"""Default pricing algorithm with complex pricing rules"""
|
||||||
tcg_low = row.get('tcg_low_price')
|
tcg_low = row.get('tcg_low_price')
|
||||||
tcg_low_shipping = row.get('tcg_low_price_with_shipping')
|
tcg_low_shipping = row.get('tcg_low_price_with_shipping')
|
||||||
tcg_market_price = row.get('tcg_market_price')
|
|
||||||
|
|
||||||
if pd.isna(tcg_low) or pd.isna(tcg_low_shipping):
|
if pd.isna(tcg_low) or pd.isna(tcg_low_shipping):
|
||||||
logger.warning(f"Missing pricing data for row: {row}")
|
logger.warning(f"Missing pricing data for row: {row}")
|
||||||
@@ -125,22 +124,14 @@ class PricingService:
|
|||||||
return row
|
return row
|
||||||
|
|
||||||
# Apply pricing rules
|
# Apply pricing rules
|
||||||
if tcg_market_price < 1 and tcg_market_price > 0.25:
|
if tcg_low < 0.35:
|
||||||
new_price = tcg_market_price * 1.05
|
new_price = 0.35
|
||||||
elif tcg_market_price < 0.25:
|
|
||||||
new_price = 0.25
|
|
||||||
elif tcg_low < 5 or tcg_low_shipping < 5:
|
elif tcg_low < 5 or tcg_low_shipping < 5:
|
||||||
new_price = round(tcg_low+((abs(tcg_market_price-tcg_low))*.75), 2)
|
new_price = round(tcg_low * 1.25, 2)
|
||||||
elif tcg_low_shipping > 20:
|
elif tcg_low_shipping > 25:
|
||||||
new_price = round(tcg_low_shipping * 1.0125, 2)
|
new_price = round(tcg_low_shipping * 1.025, 2)
|
||||||
else:
|
else:
|
||||||
# new_price = round(tcg_low_shipping * 1.08, 2)
|
new_price = round(tcg_low_shipping * 1.10, 2)
|
||||||
new_price = round(tcg_market_price * 1.03)
|
|
||||||
# if new price is less than half of market price, set to 90% market
|
|
||||||
if new_price < (tcg_market_price / 2):
|
|
||||||
new_price = round(tcg_market_price * 0.85, 2)
|
|
||||||
if new_price < 0.25:
|
|
||||||
new_price = 0.25
|
|
||||||
|
|
||||||
row['new_price'] = new_price
|
row['new_price'] = new_price
|
||||||
return row
|
return row
|
||||||
|
@@ -1,168 +0,0 @@
|
|||||||
from brother_ql.conversion import convert
|
|
||||||
from brother_ql.backends.helpers import send
|
|
||||||
from brother_ql.raster import BrotherQLRaster
|
|
||||||
from PIL import Image, ImageDraw, ImageFont
|
|
||||||
import platform
|
|
||||||
import pandas as pd
|
|
||||||
from time import sleep
|
|
||||||
import pdf2image
|
|
||||||
import io
|
|
||||||
|
|
||||||
# Printer settings
|
|
||||||
printer_model = "QL-1100"
|
|
||||||
backend = 'pyusb' # Changed from network to USB
|
|
||||||
printer = 'usb://0x04f9:0x20a7'
|
|
||||||
|
|
||||||
def convert_pdf_to_image(pdf_path):
|
|
||||||
"""Converts a PDF to PIL Image"""
|
|
||||||
try:
|
|
||||||
# Convert PDF to image
|
|
||||||
images = pdf2image.convert_from_path(pdf_path)
|
|
||||||
if images:
|
|
||||||
return images[0] # Return first page
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error converting PDF: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def create_address_label(input_data, font_size=30, is_pdf=False):
|
|
||||||
"""Creates and returns the label image without printing"""
|
|
||||||
if is_pdf:
|
|
||||||
if isinstance(input_data, str): # If path is provided
|
|
||||||
return convert_pdf_to_image(input_data)
|
|
||||||
else: # If PIL Image is provided
|
|
||||||
return input_data
|
|
||||||
|
|
||||||
# Regular text-based label creation
|
|
||||||
label_width = 991
|
|
||||||
label_height = 306
|
|
||||||
|
|
||||||
image = Image.new('L', (label_width, label_height), 'white')
|
|
||||||
draw = ImageDraw.Draw(image)
|
|
||||||
|
|
||||||
# Font selection based on OS
|
|
||||||
if platform.system() == 'Windows':
|
|
||||||
font = ImageFont.truetype("C:\\Windows\\Fonts\\arial.ttf", size=font_size)
|
|
||||||
elif platform.system() == 'Darwin':
|
|
||||||
font = ImageFont.truetype("/Library/Fonts/Arial.ttf", size=font_size)
|
|
||||||
|
|
||||||
margin = 20
|
|
||||||
lines = input_data.split('\n')
|
|
||||||
line_height = font_size + 5
|
|
||||||
total_height = line_height * len(lines)
|
|
||||||
start_y = (label_height - total_height) // 2
|
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
y = start_y + (i * line_height)
|
|
||||||
draw.text((margin, y), line, font=font, fill='black')
|
|
||||||
|
|
||||||
return image
|
|
||||||
|
|
||||||
def preview_label(input_data, font_size=30, is_pdf=False):
|
|
||||||
"""Creates and displays the label preview"""
|
|
||||||
image = create_address_label(input_data, font_size, is_pdf)
|
|
||||||
if image:
|
|
||||||
image.show()
|
|
||||||
|
|
||||||
def print_address_label(input_data, font_size=30, is_pdf=False, label_size='29x90'):
|
|
||||||
"""Prints the label with support for both text and PDF inputs"""
|
|
||||||
try:
|
|
||||||
image = create_address_label(input_data, font_size, is_pdf)
|
|
||||||
if not image:
|
|
||||||
raise Exception("Failed to create label image")
|
|
||||||
|
|
||||||
# For 4x6 shipping labels from Pirate Ship
|
|
||||||
if label_size == '4x6':
|
|
||||||
# Resize image to fit 4x6 format if needed
|
|
||||||
target_width = 1164 # Adjusted for 4x6 format
|
|
||||||
target_height = 1660
|
|
||||||
image = image.resize((target_width, target_height), Image.LANCZOS)
|
|
||||||
|
|
||||||
qlr = BrotherQLRaster(printer_model)
|
|
||||||
qlr.exception_on_warning = True
|
|
||||||
|
|
||||||
print("Converting image to printer instructions...")
|
|
||||||
instructions = convert(
|
|
||||||
qlr=qlr,
|
|
||||||
images=[image],
|
|
||||||
label='29x90' if label_size == '29x90' else '102x152',
|
|
||||||
threshold=70.0,
|
|
||||||
dither=False,
|
|
||||||
compress=False,
|
|
||||||
red=False,
|
|
||||||
dpi_600=False,
|
|
||||||
hq=True,
|
|
||||||
#cut=True
|
|
||||||
cut=False
|
|
||||||
)
|
|
||||||
|
|
||||||
print("Sending to printer...")
|
|
||||||
send(
|
|
||||||
instructions=instructions,
|
|
||||||
printer_identifier=printer,
|
|
||||||
backend_identifier=backend,
|
|
||||||
blocking=True
|
|
||||||
)
|
|
||||||
print("Print job sent successfully")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error during printing: {str(e)}")
|
|
||||||
|
|
||||||
def process_pirate_ship_pdf(pdf_path, preview=False):
|
|
||||||
"""Process and print a Pirate Ship PDF shipping label"""
|
|
||||||
if preview:
|
|
||||||
preview_label(pdf_path, is_pdf=True)
|
|
||||||
else:
|
|
||||||
print_address_label(pdf_path, is_pdf=True, label_size='4x6')
|
|
||||||
|
|
||||||
def process_tcg_shipping_export(file_path, require_input=False, font_size=60, preview=False):
|
|
||||||
# Load the CSV file, all columns are strings
|
|
||||||
df = pd.read_csv(file_path, dtype=str)
|
|
||||||
print(df.dtypes)
|
|
||||||
for i, row in df.iterrows():
|
|
||||||
line1 = str(row['FirstName']) + ' ' + str(row['LastName'])
|
|
||||||
line2 = str(row['Address1'])
|
|
||||||
if not pd.isna(row['Address2']):
|
|
||||||
line2 += ' ' + str(row['Address2'])
|
|
||||||
line3 = str(row['City']) + ', ' + str(row['State']) + ' ' + str(row['PostalCode'])
|
|
||||||
address = f"{line1}\n{line2}\n{line3}"
|
|
||||||
if preview:
|
|
||||||
preview_label(address, font_size=font_size)
|
|
||||||
else:
|
|
||||||
print_address_label(address, font_size=font_size)
|
|
||||||
if require_input:
|
|
||||||
input("Press Enter to continue...")
|
|
||||||
else:
|
|
||||||
sleep(1)
|
|
||||||
|
|
||||||
# Example usage
|
|
||||||
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
|
|
||||||
label_type = input("Enter label type (1 for regular, 2 for TCG, 3 for Pirate Ship): ")
|
|
||||||
|
|
||||||
if label_type == "1":
|
|
||||||
preview_label(address, font_size=60)
|
|
||||||
user_input = input("Press 'p' to print the label or any other key to cancel: ")
|
|
||||||
if user_input.lower() == 'p':
|
|
||||||
print_address_label(address, font_size=60)
|
|
||||||
|
|
||||||
elif label_type == "2":
|
|
||||||
process_tcg_shipping_export(shipping_export_file, font_size=60, preview=False)
|
|
||||||
|
|
||||||
elif label_type == "3":
|
|
||||||
process_pirate_ship_pdf(pirate_ship_pdf, preview=True)
|
|
||||||
user_input = input("Press 'p' to print the label or any other key to cancel: ")
|
|
||||||
if user_input.lower() == 'p':
|
|
||||||
process_pirate_ship_pdf(pirate_ship_pdf, preview=False)
|
|
6
dns.txt
6
dns.txt
@@ -1,6 +0,0 @@
|
|||||||
@ IN MX 1 aspmx.l.google.com.
|
|
||||||
@ IN MX 5 alt1.aspmx.l.google.com.
|
|
||||||
@ IN MX 5 alt2.aspmx.l.google.com.
|
|
||||||
@ IN MX 10 alt3.aspmx.l.google.com.
|
|
||||||
@ IN MX 10 alt4.aspmx.l.google.com.
|
|
||||||
@ IN TXT "v=spf1 include:_spf.google.com ~all"
|
|
30
requests.md
30
requests.md
@@ -1,17 +1,23 @@
|
|||||||
curl -J http://192.168.1.41:8000/api/tcgplayer/inventory/update --remote-name
|
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 \ -H "Content-Type: application/json" \
|
||||||
-H "Content-Type: application/json" \
|
-d '{"open_box_ids": ["e20cc342-23cb-4593-89cb-56a0cb3ed3f3"]}' \
|
||||||
-d '{"open_box_ids": ["fb629d9d-13d2-405e-9a69-6c44294d55de"]}' \
|
http://192.168.1.41:8000/api/tcgplayer/inventory/add --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" \
|
-H "Content-Type: application/json" \
|
||||||
-F "set_code=CLB" \
|
-d '{
|
||||||
-F "sku=195166181127" \
|
"type": "collector",
|
||||||
-F "num_cards_expected=480"
|
"set_code": "MOM",
|
||||||
|
"sku": "ABC123",
|
||||||
|
"num_cards_expected": 15
|
||||||
|
}'
|
||||||
|
|
||||||
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/box123/open \
|
||||||
-F "product_id=0d31b9c3-3093-438a-9e8c-b6b70a2d437e" \
|
-H "Content-Type: application/json" \
|
||||||
-F "file_ids=bb4a022c-5427-49b5-b57c-0147b5e9c4a9" \
|
-d '{
|
||||||
-F "date_opened=2025-02-15"
|
"product_id": "box123",
|
||||||
|
"file_ids": ["file1", "file2"],
|
||||||
|
"num_cards_actual": 15,
|
||||||
|
"date_opened": "2025-02-07T12:00:00Z"
|
||||||
|
}'
|
@@ -1,38 +0,0 @@
|
|||||||
import browser_cookie3
|
|
||||||
import requests
|
|
||||||
import json
|
|
||||||
|
|
||||||
def send_tcg_cookies(api_url: str, browser_type='brave'):
|
|
||||||
"""Get TCGPlayer cookies and send them to the API"""
|
|
||||||
try:
|
|
||||||
# Get cookies from browser
|
|
||||||
cookie_getter = getattr(browser_cookie3, browser_type)
|
|
||||||
cookie_jar = cookie_getter(domain_name='tcgplayer.com')
|
|
||||||
|
|
||||||
# Filter essential cookies
|
|
||||||
cookies = {}
|
|
||||||
for cookie in cookie_jar:
|
|
||||||
if any(key in cookie.name.lower() for key in ['.aspnet', 'tcg', 'session']):
|
|
||||||
cookies[cookie.name] = cookie.value
|
|
||||||
|
|
||||||
# Send to API
|
|
||||||
headers = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
response = requests.post(
|
|
||||||
f"{api_url}",
|
|
||||||
headers=headers,
|
|
||||||
json={'cookies': cookies}
|
|
||||||
)
|
|
||||||
|
|
||||||
response.raise_for_status()
|
|
||||||
print("Cookies updated successfully!")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error updating cookies: {e}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
API_URL = "http://192.168.1.41:8000/api/cookies" # Update with your API URL
|
|
||||||
|
|
||||||
send_tcg_cookies(API_URL)
|
|
Reference in New Issue
Block a user