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//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//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/", 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//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//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//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//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 })