syntaxwebsite/app/pages/trades/trades.py

551 lines
28 KiB
Python

from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify, abort
from app.util import auth, redislock, websiteFeatures
from app.extensions import limiter, db, csrf, user_limiter
from app.models.user_trade_items import UserTradeItem
from app.models.user_trades import UserTrade
from app.models.user import User
from app.models.asset import Asset
from app.models.userassets import UserAsset
from app.models.usereconomy import UserEconomy
from app.models.asset_rap import AssetRap
from app.models.limited_item_transfers import LimitedItemTransfer
from app.util.membership import GetUserMembership
from app.enums.MembershipType import MembershipType
from app.enums.TradeStatus import TradeStatus
from app.enums.LimitedItemTransferMethod import LimitedItemTransferMethod
from datetime import datetime, timedelta
import math
TradesPageRoute = Blueprint('trades', __name__, template_folder="pages")
@TradesPageRoute.route("/trade", methods=['GET'])
@auth.authenticated_required
def trades():
AuthenticatedUser : User = auth.GetCurrentUser()
TradeInfo = []
PageCategory = request.args.get("category", default="inbound", type=str)
PageCategory = PageCategory.lower()
PageNumber = request.args.get("page", default=1, type=int)
if PageNumber < 1:
PageNumber = 1
if PageCategory == "inbound":
TradeListObj : list[UserTrade] = UserTrade.query.filter_by(recipient_userid=AuthenticatedUser.id, status=TradeStatus.Pending).order_by(UserTrade.updated_at.desc()).paginate( per_page=15, page=PageNumber, error_out=False )
for TradeObj in TradeListObj:
OppositeUser : User = User.query.filter_by(id=TradeObj.sender_userid).first()
if OppositeUser is None:
continue
TradeInfo.append({
"TradeID": TradeObj.id,
"TradeStatus": TradeObj.status,
"Created": datetime.strftime(TradeObj.created_at, "%d/%m/%Y %H:%M:%S UTC"),
"Expiration": datetime.strftime(TradeObj.expires_at, "%d/%m/%Y %H:%M:%S UTC"),
"OppositeUser": OppositeUser
})
elif PageCategory == "outbound":
TradeListObj : list[UserTrade] = UserTrade.query.filter_by(sender_userid=AuthenticatedUser.id, status=TradeStatus.Pending).order_by(UserTrade.updated_at.desc()).paginate( per_page=15, page=PageNumber, error_out=False )
for TradeObj in TradeListObj:
OppositeUser : User = User.query.filter_by(id=TradeObj.recipient_userid).first()
if OppositeUser is None:
continue
TradeInfo.append({
"TradeID": TradeObj.id,
"TradeStatus": TradeObj.status,
"Created": datetime.strftime(TradeObj.created_at, "%d/%m/%Y %H:%M:%S UTC"),
"Expiration": datetime.strftime(TradeObj.expires_at, "%d/%m/%Y %H:%M:%S UTC"),
"OppositeUser": OppositeUser
})
elif PageCategory == "completed":
TradeListObj : list[UserTrade] = UserTrade.query.filter((UserTrade.recipient_userid == AuthenticatedUser.id) | (UserTrade.sender_userid == AuthenticatedUser.id)).filter_by(status=TradeStatus.Accepted).order_by(UserTrade.updated_at.desc()).paginate( per_page=15, page=PageNumber, error_out=False )
for TradeObj in TradeListObj:
if TradeObj.recipient_userid == AuthenticatedUser.id:
OppositeUser : User = User.query.filter_by(id=TradeObj.sender_userid).first()
else:
OppositeUser : User = User.query.filter_by(id=TradeObj.recipient_userid).first()
if OppositeUser is None:
continue
TradeInfo.append({
"TradeID": TradeObj.id,
"TradeStatus": TradeObj.status,
"Created": datetime.strftime(TradeObj.created_at, "%d/%m/%Y %H:%M:%S UTC"),
"Expiration": datetime.strftime(TradeObj.expires_at, "%d/%m/%Y %H:%M:%S UTC"),
"OppositeUser": OppositeUser
})
elif PageCategory == "inactive": # inactive trades
TradeListObj : list[UserTrade] = UserTrade.query.filter((UserTrade.recipient_userid == AuthenticatedUser.id) | (UserTrade.sender_userid == AuthenticatedUser.id)).filter( (UserTrade.status == TradeStatus.Declined) | (UserTrade.status == TradeStatus.Expired) | (UserTrade.status == TradeStatus.Cancelled)).order_by(UserTrade.updated_at.desc()).paginate( per_page=15, page=PageNumber, error_out=False )
for TradeObj in TradeListObj:
if TradeObj.recipient_userid == AuthenticatedUser.id:
OppositeUser : User = User.query.filter_by(id=TradeObj.sender_userid).first()
else:
OppositeUser : User = User.query.filter_by(id=TradeObj.recipient_userid).first()
if OppositeUser is None:
continue
TradeInfo.append({
"TradeID": TradeObj.id,
"TradeStatus": TradeObj.status,
"Created": datetime.strftime(TradeObj.created_at, "%d/%m/%Y %H:%M:%S UTC"),
"Expiration": datetime.strftime(TradeObj.expires_at, "%d/%m/%Y %H:%M:%S UTC"),
"OppositeUser": OppositeUser
})
else:
return abort(404)
return render_template("trades/index.html", TradeInfo=TradeInfo, PageCategory=PageCategory, TradeListObj=TradeListObj)
@TradesPageRoute.route("/trade/<int:userid>/create", methods=['GET'])
@auth.authenticated_required
def createTrade(userid : int):
AuthenticatedUser : User = auth.GetCurrentUser()
if AuthenticatedUser.id == userid:
return abort(400)
UserCurrentMembership : MembershipType = GetUserMembership(AuthenticatedUser)
if UserCurrentMembership == MembershipType.NonBuildersClub:
return redirect("/membership")
TargetUser : User = User.query.filter_by(id=userid).first()
if TargetUser is None:
return abort(404)
if TargetUser.accountstatus == 4 or TargetUser.accountstatus == 3:
return abort(404)
return render_template("trades/create.html", TargetUser=TargetUser, AuthenticatedUser=AuthenticatedUser)
@TradesPageRoute.route("/trade/<int:userid>/create", methods=['POST'])
@auth.authenticated_required
@limiter.limit("5/second")
@user_limiter.limit("5/second")
def createTradePost(userid : int):
AuthenticatedUser : User = auth.GetCurrentUser()
if AuthenticatedUser.id == userid:
return jsonify({"success": False, "message": "You cannot trade with yourself."}),400
if not websiteFeatures.GetWebsiteFeature("ItemTrading"):
return jsonify({"success": False, "message": "Item trading is temporarily disabled."}),403
TargetUser : User = User.query.filter_by(id=userid).first()
if TargetUser is None:
return jsonify({"success": False, "message": "User not found."}),404
if TargetUser.accountstatus == 4 or TargetUser.accountstatus == 3:
return jsonify({"success": False, "message": "User not found."}),404
TradeRequestData : dict = request.json
if TradeRequestData is None:
return jsonify({"success": False, "message": "Invalid request data."}),400
UserCurrentMembership : MembershipType = GetUserMembership(AuthenticatedUser)
if UserCurrentMembership == MembershipType.NonBuildersClub:
return jsonify({"success": False, "message": "You must be a Builders Club member to send trades."}),403
OppositeUserCurrentMembership : MembershipType = GetUserMembership(TargetUser)
if OppositeUserCurrentMembership == MembershipType.NonBuildersClub:
return jsonify({"success": False, "message": "The opposite user must be a Builders Club member to accept trades."}),403
"""
Expected JSON:
"RequesterOfferRobux": RequesterOfferRobux, # int must be >= 0 <= 100000
"TargetOfferRobux": TargetOfferRobux, # int must be >= 0 and <= 100000
"RequesterOfferUAIDs": RequesterOfferUAIDs, # list of int
"TargetOfferUAIDs": TargetOfferUAIDs, # list of int
"TOTPCode": isTOTPEnabled ? TOTPInputElement.value : null # int or null
"""
# RequesterOfferRobux : int = TradeRequestData.get("RequesterOfferRobux", 0)
# TargetOfferRobux : int = TradeRequestData.get("TargetOfferRobux", 0)
# RequesterOfferUAIDs : list = TradeRequestData.get("RequesterOfferUAIDs", [])
# TargetOfferUAIDs : list = TradeRequestData.get("TargetOfferUAIDs", [])
# TOTPCode : int | None = TradeRequestData.get("TOTPCode", None, type=int)
# if not all(key in TradeRequestData for key in ("RequesterOfferRobux", "TargetOfferRobux", "RequesterOfferUAIDs", "TargetOfferUAIDs", "TOTPCode")):
# return jsonify({"success": False, "message": "Invalid request data."}),400
# if not isinstance(TradeRequestData["RequesterOfferRobux"], int) or not isinstance(TradeRequestData["TargetOfferRobux"], int):
# return jsonify({"success": False, "message": "Invalid request data, Robux Offer must be an integer."}),400
# if not isinstance(TradeRequestData["RequesterOfferUAIDs"], list) or not isinstance(TradeRequestData["TargetOfferUAIDs"], list):
# return jsonify({"success": False, "message": "Invalid request data, Offered UAIDs must be a list."}),400
# if not isinstance(TradeRequestData["TOTPCode"], int) and TradeRequestData["TOTPCode"] is not None:
# return jsonify({"success": False, "message": "Invalid request data, TOTP code must be an integer."}),400
try:
assert "RequesterOfferRobux" in TradeRequestData, "Missing parameter RequesterOfferRobux"
assert "TargetOfferRobux" in TradeRequestData, "Missing parameter TargetOfferRobux"
assert "RequesterOfferUAIDs" in TradeRequestData, "Missing parameter RequesterOfferUAIDs"
assert "TargetOfferUAIDs" in TradeRequestData, "Missing parameter TargetOfferUAIDs"
assert "TOTPCode" in TradeRequestData, "Missing parameter TOTPCode"
assert isinstance(TradeRequestData["RequesterOfferRobux"], int), "Invalid request data, Robux Offer must be an integer."
assert isinstance(TradeRequestData["TargetOfferRobux"], int), "Invalid request data, Target Robux Offer must be an integer."
assert isinstance(TradeRequestData["RequesterOfferUAIDs"], list), "Invalid request data, Offered UAIDs must be a list."
assert isinstance(TradeRequestData["TargetOfferUAIDs"], list), "Invalid request data, Offered UAIDs must be a list."
assert isinstance(TradeRequestData["TOTPCode"], int) or TradeRequestData["TOTPCode"] is None, "Invalid request data, TOTP code must be an integer or null."
except Exception as e:
return jsonify({"success": False, "message": f"Payload Validation failed, {str(e)}"}), 400
TotalTradesActive : int = UserTrade.query.filter_by(sender_userid=AuthenticatedUser.id, status=TradeStatus.Pending).count()
if TotalTradesActive >= 25:
return jsonify({"success": False, "message": "You cannot have more than 25 active trades at once."}),400
ActiveTradesWithTarget : int = UserTrade.query.filter_by(sender_userid=AuthenticatedUser.id, recipient_userid=TargetUser.id, status=TradeStatus.Pending).count()
if ActiveTradesWithTarget >= 2:
return jsonify({"success": False, "message": "You cannot have more than 2 active trades with the same user at once."}),400
RequesterOfferRobux : int = TradeRequestData["RequesterOfferRobux"]
TargetOfferRobux : int = TradeRequestData["TargetOfferRobux"]
RequesterOfferUAIDs : list = TradeRequestData["RequesterOfferUAIDs"]
TargetOfferUAIDs : list = TradeRequestData["TargetOfferUAIDs"]
TOTPCode : int | None = TradeRequestData["TOTPCode"]
if (RequesterOfferRobux < 0 or TargetOfferRobux < 0) or (RequesterOfferRobux > 100000 or TargetOfferRobux > 100000):
return jsonify({"success": False, "message": "Invalid request data, Robux Offer must be between 0 - 100000"}),400
if len(RequesterOfferUAIDs) > 4 or len(TargetOfferUAIDs) > 4:
return jsonify({"success": False, "message": "Invalid request data, cannot trade more than 4 items at once."}),400
if len(RequesterOfferUAIDs) <= 0 or len(TargetOfferUAIDs) <= 0:
return jsonify({"success": False, "message": "Invalid request data, both request and offer must contain at least one item"}),400
if AuthenticatedUser.TOTPEnabled:
if TOTPCode is None:
return jsonify({"success": False, "message": "Invalid request data, TOTP code is required."}),400
if not auth.Validate2FACode(AuthenticatedUser.id, int(TOTPCode)):
return jsonify({"success": False, "message": "Invalid request data, TOTP code is invalid."}),400
for UAID in RequesterOfferUAIDs:
UserAssetObj : UserAsset = UserAsset.query.filter_by(id=UAID).first()
if UserAssetObj is None:
return jsonify({"success": False, "message": "Invalid request data, one of the items is invalid."}),400
if UserAssetObj.userid != AuthenticatedUser.id:
return jsonify({"success": False, "message": f"Invalid request data, you do not own UAID {str(UAID)}"}),400
AssetObj : Asset = Asset.query.filter_by(id=UserAssetObj.assetid).first()
if not AssetObj.is_limited or AssetObj.is_for_sale:
return jsonify({"success": False, "message": f"Invalid request data, you cannot trade UAID {str(UAID)}"}),400
for UAID in TargetOfferUAIDs:
UserAssetObj : UserAsset = UserAsset.query.filter_by(id=UAID).first()
if UserAssetObj is None:
return jsonify({"success": False, "message": "Invalid request data, one of the items is invalid."}),400
if UserAssetObj.userid != TargetUser.id:
return jsonify({"success": False, "message": f"Invalid request data, user does not own UAID {str(UAID)}"}),400
AssetObj : Asset = Asset.query.filter_by(id=UserAssetObj.assetid).first()
if not AssetObj.is_limited or AssetObj.is_for_sale:
return jsonify({"success": False, "message": f"Invalid request data, you cannot trade UAID {str(UAID)}"}),400
NewUserTrade : UserTrade = UserTrade(
sender_userid = AuthenticatedUser.id,
recipient_userid = TargetUser.id,
sender_userid_robux = RequesterOfferRobux,
recipient_userid_robux = TargetOfferRobux,
status = TradeStatus.Pending,
expires_at = datetime.utcnow() + timedelta(days=7)
)
try:
db.session.add(NewUserTrade)
db.session.commit()
for UAID in RequesterOfferUAIDs:
NewTradeAsset : UserTradeItem = UserTradeItem(
tradeid = NewUserTrade.id,
userid = AuthenticatedUser.id,
user_asset_id = UAID
)
db.session.add(NewTradeAsset)
for UAID in TargetOfferUAIDs:
NewTradeAsset : UserTradeItem = UserTradeItem(
tradeid = NewUserTrade.id,
userid = TargetUser.id,
user_asset_id = UAID
)
db.session.add(NewTradeAsset)
db.session.commit()
except:
db.session.delete(NewUserTrade)
return jsonify({"success": False, "message": "Internal server error"}),500
return jsonify({"success": True, "message": "Trade request sent.", "tradeId": NewUserTrade.id}),200
@TradesPageRoute.route("/trade/view/<int:tradeid>", methods=['GET'])
@auth.authenticated_required_api
def viewTrade(tradeid : int):
AuthenticatedUser : User = auth.GetCurrentUser()
TradeObj : UserTrade = UserTrade.query.filter_by(id=tradeid).first()
if TradeObj is None:
return abort(404)
if TradeObj.sender_userid != AuthenticatedUser.id and TradeObj.recipient_userid != AuthenticatedUser.id:
return abort(403)
if TradeObj.expires_at < datetime.utcnow() and TradeObj.status == TradeStatus.Pending:
TradeObj.status = TradeStatus.Expired
TradeObj.updated_at = datetime.utcnow()
db.session.commit()
TradeItems : list[UserTradeItem] = UserTradeItem.query.filter_by(tradeid=TradeObj.id).all()
SenderUser : User = User.query.filter_by(id=TradeObj.sender_userid).first()
RecipientUser : User = User.query.filter_by(id=TradeObj.recipient_userid).first()
SenderItems = []
RecipientItems = []
AssetRAPCache = {}
def GetAssetRAP( AssetID : int ):
if AssetID in AssetRAPCache:
return AssetRAPCache[AssetID]
AssetRAPObj : AssetRap | None = AssetRap.query.filter_by(assetid=AssetID).first()
if AssetRAPObj is None:
return 0
AssetRAPCache[AssetID] = AssetRAPObj.rap
return AssetRAPCache[AssetID]
SenderOfferValue = 0
RecipientOfferValue = 0
for TradeItem in TradeItems:
UserAssetObj : UserAsset = UserAsset.query.filter_by(id=TradeItem.user_asset_id).first()
AssetObj : Asset = Asset.query.filter_by(id=UserAssetObj.assetid).first()
if TradeItem.userid == SenderUser.id:
SenderItems.append({
"UAID": UserAssetObj.id,
"Name": AssetObj.name,
"AssetId": AssetObj.id,
"RAP": GetAssetRAP(AssetObj.id),
"serial": UserAssetObj.serial
})
SenderOfferValue += GetAssetRAP(AssetObj.id)
else:
RecipientItems.append({
"UAID": UserAssetObj.id,
"Name": AssetObj.name,
"AssetId": AssetObj.id,
"RAP": GetAssetRAP(AssetObj.id),
"serial": UserAssetObj.serial
})
RecipientOfferValue += GetAssetRAP(AssetObj.id)
SenderOfferValue += TradeObj.sender_userid_robux
RecipientOfferValue += TradeObj.recipient_userid_robux
OppositeUser = SenderUser if AuthenticatedUser.id == RecipientUser.id else RecipientUser
return render_template("trades/view.html",
TradeObj = TradeObj,
SenderUser = SenderUser,
RecipientUser = RecipientUser,
AuthenticatedUser = AuthenticatedUser,
SenderItems = SenderItems,
RecipientItems = RecipientItems,
SenderOfferValue = SenderOfferValue,
RecipientOfferValue = RecipientOfferValue,
OppositeUser = OppositeUser)
@TradesPageRoute.route("/trade/<int:tradeid>/accept", methods=['POST'])
@auth.authenticated_required
def acceptTrade(tradeid : int):
AuthenticatedUser : User = auth.GetCurrentUser()
TradeObj : UserTrade = UserTrade.query.filter_by(id=tradeid).first()
if TradeObj is None:
return abort(404)
if TradeObj.recipient_userid != AuthenticatedUser.id:
return abort(403)
if TradeObj.status != TradeStatus.Pending:
return abort(403)
UserCurrentMembership : MembershipType = GetUserMembership(AuthenticatedUser.id)
if UserCurrentMembership == MembershipType.NonBuildersClub:
flash("You must be a Builders Club member to accept trades.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
OppositeUser : User = User.query.filter_by(id=TradeObj.sender_userid).first()
if OppositeUser is None:
flash("An error occured while trying to complete this trade. Please try again later.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
OppositeUserCurrentMembership : MembershipType = GetUserMembership(OppositeUser)
if OppositeUserCurrentMembership == MembershipType.NonBuildersClub:
flash("The opposite user must be a Builders Club member to accept trades.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
if AuthenticatedUser.TOTPEnabled:
TOTPCode : str = request.form.get("totpcode", default=None, type=str)
if TOTPCode is None:
flash("Invalid TOTP code", "error")
return redirect(f"/trade/view/{str(tradeid)}")
if not auth.Validate2FACode(AuthenticatedUser.id, TOTPCode):
flash("Invalid TOTP code", "error")
return redirect(f"/trade/view/{str(tradeid)}")
if TradeObj.expires_at < datetime.utcnow():
flash("This trade has expired.", "error")
TradeObj.status = TradeStatus.Expired
db.session.commit()
return redirect(f"/trade/view/{str(tradeid)}")
TradeItems : list[UserTradeItem] = UserTradeItem.query.filter_by(tradeid=TradeObj.id).all()
for TradeItem in TradeItems:
UserAssetObj : UserAsset = UserAsset.query.filter_by(id=TradeItem.user_asset_id).first()
if UserAssetObj is None:
flash("One of the items no longer exists and this trade cannot be completed.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
if UserAssetObj.userid != TradeItem.userid:
flash("One of the items no longer belongs to its original owner and this trade cannot be completed.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
SenderEconomyLock = redislock.acquire_lock(f"economy:{str(TradeObj.sender_userid)}", acquire_timeout=5, lock_timeout=3)
RecipientEconomyLock = redislock.acquire_lock(f"economy:{str(TradeObj.recipient_userid)}", acquire_timeout=5, lock_timeout=3)
if not SenderEconomyLock or not RecipientEconomyLock:
if SenderEconomyLock:
redislock.release_lock(f"economy:{str(TradeObj.sender_userid)}", SenderEconomyLock)
flash("An error occured while trying to complete this trade. Please try again later.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
SenderEconomyObj : UserEconomy = UserEconomy.query.filter_by(userid=TradeObj.sender_userid).first()
RecipientEconomyObj : UserEconomy = UserEconomy.query.filter_by(userid=TradeObj.recipient_userid).first()
if SenderEconomyObj.robux < TradeObj.sender_userid_robux:
flash("The sender does not have enough robux to complete this trade.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
if RecipientEconomyObj.robux < TradeObj.recipient_userid_robux:
flash("The recipient does not have enough robux to complete this trade.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
ItemLocks = []
for TradeItem in TradeItems:
TradeItemLock = redislock.acquire_lock(f"item:{str(TradeItem.user_asset_id)}", acquire_timeout=20, lock_timeout=5)
if not TradeItemLock:
redislock.release_lock(f"economy:{str(TradeObj.sender_userid)}", SenderEconomyLock)
redislock.release_lock(f"economy:{str(TradeObj.recipient_userid)}", RecipientEconomyLock)
for ItemLock in ItemLocks:
redislock.release_lock(f"item:{str(ItemLock[1])}", ItemLock[0])
flash("An error occured while trying to complete this trade. Please try again later.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
ItemLocks.append([TradeItemLock, TradeItem.user_asset_id])
def ReleaseAllLocks():
redislock.release_lock(f"economy:{str(TradeObj.sender_userid)}", SenderEconomyLock)
redislock.release_lock(f"economy:{str(TradeObj.recipient_userid)}", RecipientEconomyLock)
for ItemLock in ItemLocks:
redislock.release_lock(f"item:{str(ItemLock[1])}", ItemLock[0])
SenderEconomyObj.robux -= TradeObj.sender_userid_robux
RecipientEconomyObj.robux -= TradeObj.recipient_userid_robux
if TradeObj.sender_userid_robux > 0:
FinalAdded = math.floor(TradeObj.sender_userid_robux * 0.7)
RecipientEconomyObj.robux += FinalAdded
if TradeObj.recipient_userid_robux > 0:
FinalAdded = math.floor(TradeObj.recipient_userid_robux * 0.7)
SenderEconomyObj.robux += FinalAdded
def CreateItemTransferLog( original_owner_id : int, new_owner_id : int, asset_id : int, user_asset_id : int ):
NewTransferLog = LimitedItemTransfer(
original_owner_id = original_owner_id,
new_owner_id = new_owner_id,
asset_id = asset_id,
user_asset_id = user_asset_id,
transfer_method = LimitedItemTransferMethod.Trade,
associated_trade_id = TradeObj.id
)
db.session.add(NewTransferLog)
for TradeItem in TradeItems:
UserAssetObj : UserAsset = UserAsset.query.filter_by(id=TradeItem.user_asset_id).first()
if UserAssetObj is None:
ReleaseAllLocks()
flash("One of the items no longer exists and this trade cannot be completed.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
if UserAssetObj.userid == TradeObj.sender_userid:
CreateItemTransferLog( original_owner_id = TradeObj.sender_userid, new_owner_id = TradeObj.recipient_userid, asset_id = UserAssetObj.assetid, user_asset_id = UserAssetObj.id )
UserAssetObj.userid = TradeObj.recipient_userid
else:
CreateItemTransferLog( original_owner_id = TradeObj.sender_userid, new_owner_id = TradeObj.recipient_userid, asset_id = UserAssetObj.assetid, user_asset_id = UserAssetObj.id )
UserAssetObj.userid = TradeObj.sender_userid
UserAssetObj.is_for_sale = False
UserAssetObj.price = 0
UserAssetObj.updated = datetime.utcnow()
db.session.commit()
ReleaseAllLocks()
TradeObj.status = TradeStatus.Accepted
TradeObj.updated_at = datetime.utcnow()
db.session.commit()
return redirect(f"/trade/view/{str(tradeid)}")
@TradesPageRoute.route("/trade/<int:tradeid>/cancel", methods=['POST'])
@auth.authenticated_required
def cancelTrade(tradeid : int):
AuthenticatedUser : User = auth.GetCurrentUser()
TradeObj : UserTrade = UserTrade.query.filter_by(id=tradeid).first()
if TradeObj is None:
return abort(404)
if TradeObj.sender_userid != AuthenticatedUser.id:
return abort(403)
if TradeObj.status != TradeStatus.Pending:
return abort(403)
if TradeObj.expires_at < datetime.utcnow():
TradeObj.status = TradeStatus.Expired
TradeObj.updated_at = datetime.utcnow()
db.session.commit()
flash("This trade has expired.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
TradeObj.status = TradeStatus.Cancelled
TradeObj.updated_at = datetime.utcnow()
db.session.commit()
return redirect(f"/trade/view/{str(tradeid)}")
@TradesPageRoute.route("/trade/<int:tradeid>/decline", methods=['POST'])
@auth.authenticated_required
@csrf.exempt
def declineTrade(tradeid : int):
AuthenticatedUser : User = auth.GetCurrentUser()
TradeObj : UserTrade = UserTrade.query.filter_by(id=tradeid).first()
if TradeObj is None:
return abort(404)
if TradeObj.recipient_userid != AuthenticatedUser.id:
return abort(403)
if TradeObj.status != TradeStatus.Pending:
return abort(403)
if TradeObj.expires_at < datetime.utcnow():
TradeObj.status = TradeStatus.Expired
TradeObj.updated_at = datetime.utcnow()
db.session.commit()
flash("This trade has expired.", "error")
return redirect(f"/trade/view/{str(tradeid)}")
TradeObj.status = TradeStatus.Declined
TradeObj.updated_at = datetime.utcnow()
db.session.commit()
return redirect(f"/trade/view/{str(tradeid)}")
@TradesPageRoute.route("/trade/<int:userid>/inventory", methods=['GET'])
@auth.authenticated_required_api
def getInventory(userid : int):
pageNumber = request.args.get("page", default=1, type=int)
TargetUser : User = User.query.filter_by(id=userid).first()
if TargetUser is None:
return abort(404)
if TargetUser.accountstatus == 4 or TargetUser.accountstatus == 3:
return abort(404)
UserAssets : list[UserAsset] = UserAsset.query.join(Asset, UserAsset.assetid == Asset.id).filter(UserAsset.userid == TargetUser.id, Asset.is_limited == True, Asset.is_for_sale == False).paginate(page=pageNumber, error_out=False, max_per_page=12).items
ReturnInfoData = []
AssetRAPCache = {}
def GetAssetRAP( AssetID : int ):
if AssetID in AssetRAPCache:
return AssetRAPCache[AssetID]
AssetRAPObj : AssetRap | None = AssetRap.query.filter_by(assetid=AssetID).first()
if AssetRAPObj is None:
return 0
AssetRAPCache[AssetID] = AssetRAPObj.rap
return AssetRAPCache[AssetID]
for UserAssetObj in UserAssets:
AssetObj : Asset = Asset.query.filter_by(id=UserAssetObj.assetid).first()
if AssetObj is None:
continue
ReturnInfoData.append({
"id": AssetObj.id,
"name": AssetObj.name,
"serialNumber": UserAssetObj.serial,
"uaid": UserAssetObj.id,
"rap": GetAssetRAP(AssetObj.id),
})
isThereNextPage = len(UserAssets) == 12
return jsonify({
"data": ReturnInfoData,
"nextPage": isThereNextPage
})