İlk commit: Stepstead backend (FastAPI)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
commit
ea049b8ccd
45 changed files with 6751 additions and 0 deletions
505
routers/market.py
Normal file
505
routers/market.py
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
"""
|
||||
Pazar — oyuncu arası ticaret (gold ile) + NPC vendor.
|
||||
|
||||
Kurallar:
|
||||
- İlan verme / satın alma / vendor'a satma SADECE 'pazar' lokasyonundayken.
|
||||
- İlanları görüntüleme her yerden serbest (vitrin).
|
||||
- Para birimi: gold (characters.gold). Vendor her şeyi items.base_value'dan alır.
|
||||
- İlan iptali her yerden yapılabilir (item envantere döner).
|
||||
|
||||
Endpoint'ler:
|
||||
- GET /market/listings → açık ilanlar (q= arama, mine= sadece benimkiler)
|
||||
- POST /market/list → {item_code, qty, unit_price} ilan ver
|
||||
- POST /market/buy → {listing_id, qty?} satın al (kısmi alım destekli)
|
||||
- POST /market/cancel → {listing_id} ilanı iptal et
|
||||
- GET /market/vendor → envanterim + vendor'un ödeyeceği birim fiyatlar
|
||||
- POST /market/vendor/sell → {item_code, qty} vendor'a sat → gold
|
||||
"""
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from sqlalchemy import select, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from deps import get_current_user
|
||||
from models import Character, User, get_db
|
||||
from routers.harvest import consume_items
|
||||
from routers.travel import _add_items
|
||||
|
||||
router = APIRouter(prefix="/market", tags=["market"])
|
||||
|
||||
MARKET_LOCATION = "pazar"
|
||||
MAX_OPEN_LISTINGS = 10
|
||||
|
||||
|
||||
async def _char(db: AsyncSession, user: User) -> Character:
|
||||
char = (await db.execute(select(Character).where(Character.user_id == user.id))).scalar_one_or_none()
|
||||
if not char:
|
||||
raise HTTPException(404, "Karakter yok")
|
||||
return char
|
||||
|
||||
|
||||
async def _require_at_market(db: AsyncSession, char: Character) -> None:
|
||||
row = (await db.execute(text(
|
||||
"SELECT code FROM locations WHERE id = :i"
|
||||
), {"i": char.location_id})).first()
|
||||
if not row or row[0] != MARKET_LOCATION:
|
||||
raise HTTPException(400, "Bunun için Pazar'da olmalısın")
|
||||
|
||||
|
||||
# ---------- İlanlar ----------
|
||||
|
||||
class ListingOut(BaseModel):
|
||||
id: int
|
||||
item_code: str
|
||||
item_name: str
|
||||
rarity: str
|
||||
icon: Optional[str] = None
|
||||
qty: int
|
||||
unit_price: int
|
||||
seller_name: str
|
||||
mine: bool
|
||||
|
||||
|
||||
@router.get("/listings", response_model=list[ListingOut])
|
||||
async def listings(q: str = "", mine: bool = False,
|
||||
user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
sql = """
|
||||
SELECT ml.id, i.code, i.name, i.rarity, i.icon, ml.qty, ml.unit_price, c.name, ml.seller_id
|
||||
FROM market_listings ml
|
||||
JOIN items i ON i.id = ml.item_id
|
||||
JOIN characters c ON c.id = ml.seller_id
|
||||
WHERE ml.status = 'open'
|
||||
"""
|
||||
params: dict = {}
|
||||
if q:
|
||||
sql += " AND i.name ILIKE :q"
|
||||
params["q"] = f"%{q}%"
|
||||
if mine:
|
||||
sql += " AND ml.seller_id = :me"
|
||||
params["me"] = char.id
|
||||
sql += " ORDER BY i.name, ml.unit_price LIMIT 100"
|
||||
rows = (await db.execute(text(sql), params)).all()
|
||||
return [ListingOut(id=r[0], item_code=r[1], item_name=r[2], rarity=r[3], icon=r[4],
|
||||
qty=r[5], unit_price=r[6], seller_name=r[7], mine=(r[8] == char.id))
|
||||
for r in rows]
|
||||
|
||||
|
||||
class ListIn(BaseModel):
|
||||
item_code: str
|
||||
qty: int = Field(ge=1, le=999)
|
||||
unit_price: int = Field(ge=1, le=1_000_000)
|
||||
|
||||
|
||||
class ListingActionOut(BaseModel):
|
||||
listing_id: int
|
||||
gold: int
|
||||
|
||||
|
||||
@router.post("/list", response_model=ListingActionOut)
|
||||
async def create_listing(l: ListIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
await _require_at_market(db, char)
|
||||
|
||||
open_count = (await db.execute(text(
|
||||
"SELECT count(*) FROM market_listings WHERE seller_id=:s AND status='open'"
|
||||
), {"s": char.id})).scalar_one()
|
||||
if open_count >= MAX_OPEN_LISTINGS:
|
||||
raise HTTPException(400, f"En fazla {MAX_OPEN_LISTINGS} açık ilanın olabilir")
|
||||
|
||||
item = (await db.execute(text(
|
||||
"SELECT id, name FROM items WHERE code = :c"), {"c": l.item_code})).first()
|
||||
if not item:
|
||||
raise HTTPException(404, "Item yok")
|
||||
|
||||
await consume_items(db, char.id, item[0], l.qty, item[1])
|
||||
lid = (await db.execute(text("""
|
||||
INSERT INTO market_listings (seller_id, item_id, qty, unit_price)
|
||||
VALUES (:s, :i, :q, :p) RETURNING id
|
||||
"""), {"s": char.id, "i": item[0], "q": l.qty, "p": l.unit_price})).scalar_one()
|
||||
await db.commit()
|
||||
return ListingActionOut(listing_id=lid, gold=char.gold)
|
||||
|
||||
|
||||
class BuyIn(BaseModel):
|
||||
listing_id: int
|
||||
qty: Optional[int] = Field(None, ge=1, le=999) # None = tamamı
|
||||
|
||||
|
||||
class BuyOut(BaseModel):
|
||||
item_code: str
|
||||
item_name: str
|
||||
qty: int
|
||||
total_price: int
|
||||
gold: int
|
||||
|
||||
|
||||
@router.post("/buy", response_model=BuyOut)
|
||||
async def buy(b: BuyIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
await _require_at_market(db, char)
|
||||
|
||||
row = (await db.execute(text("""
|
||||
SELECT ml.id, ml.seller_id, ml.item_id, ml.qty, ml.unit_price, i.code, i.name
|
||||
FROM market_listings ml JOIN items i ON i.id = ml.item_id
|
||||
WHERE ml.id = :id AND ml.status = 'open' FOR UPDATE OF ml
|
||||
"""), {"id": b.listing_id})).first()
|
||||
if not row:
|
||||
raise HTTPException(404, "İlan yok ya da kapanmış")
|
||||
if row[1] == char.id:
|
||||
raise HTTPException(400, "Kendi ilanını satın alamazsın")
|
||||
|
||||
qty = b.qty if b.qty is not None else int(row[3])
|
||||
if qty > int(row[3]):
|
||||
raise HTTPException(400, f"İlanda sadece {row[3]} adet var")
|
||||
total = qty * int(row[4])
|
||||
if char.gold < total:
|
||||
raise HTTPException(400, f"Yetersiz gold: {total} gerekli, {char.gold} var")
|
||||
|
||||
char.gold -= total
|
||||
await db.execute(text("UPDATE characters SET gold = gold + :g WHERE id = :s"),
|
||||
{"g": total, "s": row[1]})
|
||||
await _add_items(db, char.id, row[5], qty)
|
||||
|
||||
if qty == int(row[3]):
|
||||
await db.execute(text(
|
||||
"UPDATE market_listings SET status='sold', closed_at=:t WHERE id=:id"
|
||||
), {"t": datetime.now(timezone.utc), "id": row[0]})
|
||||
else:
|
||||
await db.execute(text("UPDATE market_listings SET qty = qty - :q WHERE id=:id"),
|
||||
{"q": qty, "id": row[0]})
|
||||
|
||||
await db.execute(text("""
|
||||
INSERT INTO trade_logs (seller_id, buyer_id, item_id, qty, unit_price)
|
||||
VALUES (:s, :b, :i, :q, :p)
|
||||
"""), {"s": row[1], "b": char.id, "i": row[2], "q": qty, "p": row[4]})
|
||||
await db.commit()
|
||||
return BuyOut(item_code=row[5], item_name=row[6], qty=qty, total_price=total, gold=char.gold)
|
||||
|
||||
|
||||
class CancelIn(BaseModel):
|
||||
listing_id: int
|
||||
|
||||
|
||||
@router.post("/cancel", response_model=ListingActionOut)
|
||||
async def cancel(cn: CancelIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
row = (await db.execute(text("""
|
||||
SELECT ml.id, ml.item_id, ml.qty, i.code
|
||||
FROM market_listings ml JOIN items i ON i.id = ml.item_id
|
||||
WHERE ml.id = :id AND ml.status = 'open' AND ml.seller_id = :s FOR UPDATE OF ml
|
||||
"""), {"id": cn.listing_id, "s": char.id})).first()
|
||||
if not row:
|
||||
raise HTTPException(404, "Açık ilanın yok")
|
||||
await db.execute(text(
|
||||
"UPDATE market_listings SET status='cancelled', closed_at=:t WHERE id=:id"
|
||||
), {"t": datetime.now(timezone.utc), "id": row[0]})
|
||||
await _add_items(db, char.id, row[3], int(row[2]))
|
||||
await db.commit()
|
||||
return ListingActionOut(listing_id=row[0], gold=char.gold)
|
||||
|
||||
|
||||
# ---------- NPC Vendor ----------
|
||||
|
||||
class VendorRow(BaseModel):
|
||||
item_code: str
|
||||
item_name: str
|
||||
rarity: str
|
||||
qty: int
|
||||
unit_price: int # vendor'un ödeyeceği gold
|
||||
|
||||
|
||||
@router.get("/vendor", response_model=list[VendorRow])
|
||||
async def vendor_prices(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
rows = (await db.execute(text("""
|
||||
SELECT i.code, i.name, i.rarity, SUM(inv.qty), i.base_value
|
||||
FROM inventory inv JOIN items i ON i.id = inv.item_id
|
||||
WHERE inv.character_id = :cid
|
||||
GROUP BY i.code, i.name, i.rarity, i.base_value
|
||||
ORDER BY i.name
|
||||
"""), {"cid": char.id})).all()
|
||||
return [VendorRow(item_code=r[0], item_name=r[1], rarity=r[2], qty=int(r[3]), unit_price=int(r[4]))
|
||||
for r in rows]
|
||||
|
||||
|
||||
class VendorSellIn(BaseModel):
|
||||
item_code: str
|
||||
qty: int = Field(ge=1, le=999)
|
||||
|
||||
|
||||
class VendorSellOut(BaseModel):
|
||||
item_code: str
|
||||
item_name: str
|
||||
qty: int
|
||||
gold_gained: int
|
||||
gold: int
|
||||
|
||||
|
||||
@router.post("/vendor/sell", response_model=VendorSellOut)
|
||||
async def vendor_sell(v: VendorSellIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
await _require_at_market(db, char)
|
||||
|
||||
item = (await db.execute(text(
|
||||
"SELECT id, name, base_value FROM items WHERE code = :c"), {"c": v.item_code})).first()
|
||||
if not item:
|
||||
raise HTTPException(404, "Item yok")
|
||||
|
||||
await consume_items(db, char.id, item[0], v.qty, item[1])
|
||||
gained = int(item[2]) * v.qty
|
||||
char.gold += gained
|
||||
await db.commit()
|
||||
return VendorSellOut(item_code=v.item_code, item_name=item[1], qty=v.qty,
|
||||
gold_gained=gained, gold=char.gold)
|
||||
|
||||
|
||||
# ---------- Vendor stok (satın alma — gold sink) ----------
|
||||
|
||||
# Vendor'un sattığı temel mallar; fiyat = base_value × çarpan
|
||||
VENDOR_STOCK_MARKUP = 4
|
||||
VENDOR_STOCK_CODES = ["axe_stone", "pickaxe_stone", "fishing_rod", "tea_mint", "stone_small", "stick", "vine"]
|
||||
|
||||
|
||||
class VendorStockRow(BaseModel):
|
||||
item_code: str
|
||||
item_name: str
|
||||
rarity: str
|
||||
unit_price: int # gold
|
||||
|
||||
|
||||
@router.get("/vendor/stock", response_model=list[VendorStockRow])
|
||||
async def vendor_stock(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
await _char(db, user)
|
||||
from sqlalchemy import bindparam
|
||||
stmt = text(
|
||||
"SELECT code, name, rarity, base_value FROM items WHERE code IN :codes ORDER BY base_value"
|
||||
).bindparams(bindparam("codes", expanding=True))
|
||||
rows = (await db.execute(stmt, {"codes": VENDOR_STOCK_CODES})).all()
|
||||
return [VendorStockRow(item_code=r[0], item_name=r[1], rarity=r[2],
|
||||
unit_price=int(r[3]) * VENDOR_STOCK_MARKUP) for r in rows]
|
||||
|
||||
|
||||
class VendorBuyIn(BaseModel):
|
||||
item_code: str
|
||||
qty: int = Field(ge=1, le=10)
|
||||
|
||||
|
||||
class VendorBuyOut(BaseModel):
|
||||
item_code: str
|
||||
item_name: str
|
||||
qty: int
|
||||
total_price: int
|
||||
gold: int
|
||||
|
||||
|
||||
@router.post("/vendor/buy", response_model=VendorBuyOut)
|
||||
async def vendor_buy(v: VendorBuyIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
await _require_at_market(db, char)
|
||||
if v.item_code not in VENDOR_STOCK_CODES:
|
||||
raise HTTPException(400, "Vendor bunu satmıyor")
|
||||
|
||||
item = (await db.execute(text(
|
||||
"SELECT id, name, base_value FROM items WHERE code = :c"), {"c": v.item_code})).first()
|
||||
if not item:
|
||||
raise HTTPException(404, "Item yok")
|
||||
|
||||
total = int(item[2]) * VENDOR_STOCK_MARKUP * v.qty
|
||||
if char.gold < total:
|
||||
raise HTTPException(400, f"Yetersiz gold: {total} gerekli, {char.gold} var")
|
||||
|
||||
char.gold -= total
|
||||
await _add_items(db, char.id, v.item_code, v.qty)
|
||||
await db.commit()
|
||||
return VendorBuyOut(item_code=v.item_code, item_name=item[1], qty=v.qty,
|
||||
total_price=total, gold=char.gold)
|
||||
|
||||
|
||||
# ---------- Şehir haritası: konum bazlı NPC vendor'lar ----------
|
||||
# Her şehir location'ının kendi vendor NPC'leri var (npcs.location_id).
|
||||
# Vendor stoğu = sabit stok (npc_items, sonsuz, base_value×markup)
|
||||
# + konsinye (vendor_stock: oyuncuların sattığı, 24s ömürlü, kendi fiyatı).
|
||||
# Oyuncu vendor'a sattığında: gold alır + ürün vendor'ın normal satmadığı bir şeyse
|
||||
# 24s ömürle konsinyeye eklenir (varsa süresi yenilenir). Normal sattığı üründe
|
||||
# sadece gold ödenir (sabit stok zaten sonsuz).
|
||||
|
||||
VENDOR_CONSIGN_HOURS = 24
|
||||
|
||||
|
||||
async def _clean_expired(db: AsyncSession) -> None:
|
||||
await db.execute(text("DELETE FROM vendor_stock WHERE expires_at < now()"))
|
||||
|
||||
|
||||
async def _vendor_at_here(db: AsyncSession, npc_id: int, char: Character) -> dict:
|
||||
"""npc_id gerçekten char'ın bulunduğu şehrin vendor'ı mı? değilse 400."""
|
||||
row = (await db.execute(text(
|
||||
"SELECT id, name, role, location_id FROM npcs WHERE id = :i"
|
||||
), {"i": npc_id})).first()
|
||||
if not row:
|
||||
raise HTTPException(404, "Satıcı yok")
|
||||
if row[3] != char.location_id:
|
||||
raise HTTPException(400, "Bu satıcı burada değil")
|
||||
return {"id": row[0], "name": row[1], "role": row[2]}
|
||||
|
||||
|
||||
class CityVendor(BaseModel):
|
||||
npc_id: int
|
||||
name: str
|
||||
role: Optional[str] = None
|
||||
|
||||
|
||||
class CityOut(BaseModel):
|
||||
location_code: str
|
||||
location_name: str
|
||||
vendors: list[CityVendor]
|
||||
|
||||
|
||||
@router.get("/city", response_model=CityOut)
|
||||
async def city_vendors(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
"""Bulunduğun şehrin vendor NPC'leri (şehir haritası pinleri)."""
|
||||
char = await _char(db, user)
|
||||
loc = (await db.execute(text(
|
||||
"SELECT code, name FROM locations WHERE id = :i"
|
||||
), {"i": char.location_id})).first()
|
||||
if not loc:
|
||||
raise HTTPException(404, "Konum yok")
|
||||
rows = (await db.execute(text(
|
||||
"SELECT id, name, role FROM npcs WHERE location_id = :l ORDER BY id"
|
||||
), {"l": char.location_id})).all()
|
||||
return CityOut(location_code=loc[0], location_name=loc[1],
|
||||
vendors=[CityVendor(npc_id=r[0], name=r[1], role=r[2]) for r in rows])
|
||||
|
||||
|
||||
class ShopRow(BaseModel):
|
||||
item_code: str
|
||||
item_name: str
|
||||
rarity: str
|
||||
icon: Optional[str] = None
|
||||
unit_price: int
|
||||
qty: Optional[int] = None # None = sabit stok (sonsuz); sayı = konsinye kalan
|
||||
source: str # "base" | "player"
|
||||
expires_in_h: Optional[int] = None
|
||||
|
||||
|
||||
class ShopOut(BaseModel):
|
||||
npc_id: int
|
||||
npc_name: str
|
||||
role: Optional[str] = None
|
||||
items: list[ShopRow]
|
||||
|
||||
|
||||
@router.get("/vendor/{npc_id}/shop", response_model=ShopOut)
|
||||
async def vendor_shop(npc_id: int, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
v = await _vendor_at_here(db, npc_id, char)
|
||||
await _clean_expired(db)
|
||||
# sabit stok — npc_items
|
||||
base = (await db.execute(text(
|
||||
"SELECT i.code, i.name, i.rarity, i.icon, i.base_value "
|
||||
"FROM npc_items ni JOIN items i ON i.id = ni.item_id "
|
||||
"WHERE ni.npc_id = :n ORDER BY i.base_value"
|
||||
), {"n": npc_id})).all()
|
||||
items = [ShopRow(item_code=r[0], item_name=r[1], rarity=r[2], icon=r[3],
|
||||
unit_price=int(r[4]) * VENDOR_STOCK_MARKUP, qty=None, source="base")
|
||||
for r in base]
|
||||
# konsinye — vendor_stock (aktif)
|
||||
cons = (await db.execute(text(
|
||||
"SELECT i.code, i.name, i.rarity, i.icon, vs.unit_price, vs.qty, "
|
||||
"CEIL(EXTRACT(EPOCH FROM (vs.expires_at - now()))/3600.0) "
|
||||
"FROM vendor_stock vs JOIN items i ON i.id = vs.item_id "
|
||||
"WHERE vs.npc_id = :n AND vs.expires_at > now() ORDER BY vs.expires_at"
|
||||
), {"n": npc_id})).all()
|
||||
items += [ShopRow(item_code=r[0], item_name=r[1], rarity=r[2], icon=r[3],
|
||||
unit_price=int(r[4]), qty=int(r[5]), source="player", expires_in_h=int(r[6]))
|
||||
for r in cons]
|
||||
await db.commit()
|
||||
return ShopOut(npc_id=v["id"], npc_name=v["name"], role=v["role"], items=items)
|
||||
|
||||
|
||||
class NpcBuyIn(BaseModel):
|
||||
item_code: str
|
||||
qty: int = Field(ge=1, le=99)
|
||||
|
||||
|
||||
@router.post("/vendor/{npc_id}/buy", response_model=VendorBuyOut)
|
||||
async def npc_buy(npc_id: int, b: NpcBuyIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
await _vendor_at_here(db, npc_id, char)
|
||||
await _clean_expired(db)
|
||||
item = (await db.execute(text(
|
||||
"SELECT id, name, base_value FROM items WHERE code = :c"), {"c": b.item_code})).first()
|
||||
if not item:
|
||||
raise HTTPException(404, "Item yok")
|
||||
# önce konsinye stoğu (sınırlı), yoksa sabit stok (npc_items, sonsuz)
|
||||
cons = (await db.execute(text(
|
||||
"SELECT qty, unit_price FROM vendor_stock "
|
||||
"WHERE npc_id = :n AND item_id = :it AND expires_at > now()"
|
||||
), {"n": npc_id, "it": item[0]})).first()
|
||||
if cons is not None:
|
||||
if int(cons[0]) < b.qty:
|
||||
raise HTTPException(400, f"Stokta {int(cons[0])} adet var")
|
||||
unit = int(cons[1])
|
||||
total = unit * b.qty
|
||||
if char.gold < total:
|
||||
raise HTTPException(400, f"Yetersiz gold: {total} gerekli, {char.gold} var")
|
||||
remaining = int(cons[0]) - b.qty
|
||||
if remaining > 0:
|
||||
await db.execute(text("UPDATE vendor_stock SET qty = :q WHERE npc_id = :n AND item_id = :it"),
|
||||
{"q": remaining, "n": npc_id, "it": item[0]})
|
||||
else:
|
||||
await db.execute(text("DELETE FROM vendor_stock WHERE npc_id = :n AND item_id = :it"),
|
||||
{"n": npc_id, "it": item[0]})
|
||||
else:
|
||||
# sabit stok mu?
|
||||
is_base = (await db.execute(text(
|
||||
"SELECT 1 FROM npc_items WHERE npc_id = :n AND item_id = :it"
|
||||
), {"n": npc_id, "it": item[0]})).first()
|
||||
if not is_base:
|
||||
raise HTTPException(400, "Bu satıcı bunu satmıyor")
|
||||
unit = int(item[2]) * VENDOR_STOCK_MARKUP
|
||||
total = unit * b.qty
|
||||
if char.gold < total:
|
||||
raise HTTPException(400, f"Yetersiz gold: {total} gerekli, {char.gold} var")
|
||||
char.gold -= total
|
||||
await _add_items(db, char.id, b.item_code, b.qty)
|
||||
await db.commit()
|
||||
return VendorBuyOut(item_code=b.item_code, item_name=item[1], qty=b.qty,
|
||||
total_price=total, gold=char.gold)
|
||||
|
||||
|
||||
class NpcSellIn(BaseModel):
|
||||
item_code: str
|
||||
qty: int = Field(ge=1, le=999)
|
||||
|
||||
|
||||
@router.post("/vendor/{npc_id}/sell", response_model=VendorSellOut)
|
||||
async def npc_sell(npc_id: int, s: NpcSellIn, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
||||
char = await _char(db, user)
|
||||
await _vendor_at_here(db, npc_id, char)
|
||||
item = (await db.execute(text(
|
||||
"SELECT id, name, base_value FROM items WHERE code = :c"), {"c": s.item_code})).first()
|
||||
if not item:
|
||||
raise HTTPException(404, "Item yok")
|
||||
await consume_items(db, char.id, item[0], s.qty, item[1])
|
||||
gained = int(item[2]) * s.qty
|
||||
char.gold += gained
|
||||
# Vendor'ın normal satmadığı ürünse konsinyeye ekle (24s, süresi yenilenir).
|
||||
is_base = (await db.execute(text(
|
||||
"SELECT 1 FROM npc_items WHERE npc_id = :n AND item_id = :it"
|
||||
), {"n": npc_id, "it": item[0]})).first()
|
||||
if not is_base:
|
||||
unit = int(item[2]) * VENDOR_STOCK_MARKUP
|
||||
await db.execute(text(
|
||||
"INSERT INTO vendor_stock(npc_id, item_id, qty, unit_price, expires_at) "
|
||||
"VALUES (:n, :it, :q, :u, now() + make_interval(hours => :h)) "
|
||||
"ON CONFLICT (npc_id, item_id) DO UPDATE SET "
|
||||
"qty = vendor_stock.qty + EXCLUDED.qty, unit_price = EXCLUDED.unit_price, "
|
||||
"expires_at = now() + make_interval(hours => :h)"
|
||||
), {"n": npc_id, "it": item[0], "q": s.qty, "u": unit, "h": VENDOR_CONSIGN_HOURS})
|
||||
await db.commit()
|
||||
return VendorSellOut(item_code=s.item_code, item_name=item[1], qty=s.qty,
|
||||
gold_gained=gained, gold=char.gold)
|
||||
Loading…
Add table
Add a link
Reference in a new issue