syntaxwebsite/app/routes/datastoreservice.py

277 lines
12 KiB
Python

from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify
from app.models.gameservers import GameServer
from app.models.place_ordered_datastore import PlaceOrderedDatastore
from app.models.place_datastore import PlaceDatastore
from app.models.asset import Asset
from app.models.universe import Universe
from app.models.place import Place
from app.enums.AssetType import AssetType
from app.extensions import get_remote_address, db, redis_controller, csrf
from functools import wraps
DataStoreRoute = Blueprint('datastore', __name__)
def GameServerRequired(f):
@wraps(f)
def decorated_function(*args, **kwargs):
RequestIP = get_remote_address()
if RequestIP is None:
return jsonify({"success": False, "message": "Unauthorized"}),401
server : GameServer = GameServer.query.filter_by(serverIP=RequestIP).first()
if server is None:
return jsonify({"success": False, "message": "Unauthorized"}),401
ServerAccessKey = request.headers.get("AccessKey", default = None, type = str)
if ServerAccessKey is None or ServerAccessKey != server.accessKey:
return jsonify({"success": False, "message": "Unauthorized"}),401
return f(*args, **kwargs)
return decorated_function
@DataStoreRoute.route("/persistence/getSortedValues", methods=["POST"])
@csrf.exempt
@GameServerRequired
def getSortedValues():
placeId : int = request.args.get("placeId", default=None, type=int)
dataType : str = request.args.get("type", default=None, type=str)
scope : str = request.args.get("scope", default="global", type=str)
pageSize : int = request.args.get("pageSize", default=50, type=int)
exclusiveStartKey : str = request.args.get("exclusiveStartKey", default=None, type=int) # I have no idea how Roblox uses this, but I am going to use it as a page number with pagination
key : str = request.args.get("key", default=None, type=str)
ascending : bool = request.args.get("ascending", default="False", type=str) == "True"
inclusiveMinValue : int = request.args.get("inclusiveMinValue", default=None, type=int)
exclusiveMaxValue : int = request.args.get("inclusiveMaxValue", default=None, type=int)
PlaceObj : Place = Place.query.filter_by(placeid = placeId).first()
if PlaceObj is None:
return jsonify({"data":[], "message": "Place does not exist"}), 200
UniverseObj : Universe = Universe.query.filter_by(id = PlaceObj.parent_universe_id).first()
if UniverseObj is None:
return jsonify({"data":[], "message": "Place does not exist"}), 200
if pageSize == 0 or pageSize > 100:
return jsonify({"data":[], "message": "Page size is too large"}), 200
if dataType != "sorted":
return jsonify({"data":[], "message": "Invalid data type"}), 200
if exclusiveStartKey is None:
exclusiveStartKey = 1
else:
if exclusiveStartKey < 1:
return jsonify({"data":[], "message": "Invalid exclusive start key"}), 200
DataStoreObj = PlaceOrderedDatastore.query.filter_by(
universe_id = UniverseObj.id,
scope = scope,
key = key
)
if inclusiveMinValue is not None:
DataStoreObj = DataStoreObj.filter(PlaceOrderedDatastore.value >= inclusiveMinValue)
if exclusiveMaxValue is not None:
DataStoreObj = DataStoreObj.filter(PlaceOrderedDatastore.value < exclusiveMaxValue)
DataStoreObj : list[PlaceOrderedDatastore] = DataStoreObj.order_by(PlaceOrderedDatastore.value.asc() if ascending else PlaceOrderedDatastore.value.desc()).paginate( page=exclusiveStartKey, per_page=pageSize, error_out=False )
if DataStoreObj is None:
return jsonify({"Entries": [], "ExclusiveStartKey": None}), 200
AllEntries = []
for entry in DataStoreObj.items:
AllEntries.append({
"Target": entry.name,
"Value": entry.value
})
return jsonify({"data":{
"Entries": AllEntries,
"ExclusiveStartKey": str(DataStoreObj.next_num) if DataStoreObj.has_next else None
}}) # TODO: Implement
@DataStoreRoute.route("/persistence/set", methods=["POST"])
@csrf.exempt
@GameServerRequired
def setKey():
placeId : int = request.args.get("placeId", default=None, type=int)
dataType : str = request.args.get("type", default=None, type=str)
scope : str = request.args.get("scope", default="global", type=str)
key : str = request.args.get("key", default=None, type=str)
target : str = request.args.get("target", default=None, type=str)
valueLength : int = request.args.get("valueLength", default=None, type=int)
value : str = request.form.get("value", default=None, type=str)
if valueLength is not None and not valueLength < 1024 * 1024 * 1: # 1MB
return jsonify({"success": False, "message": "Value too large"}),400
PlaceObj : Place = Place.query.filter_by(placeid = placeId).first()
if PlaceObj is None:
return jsonify({"data":[], "message": "Place does not exist"}), 200
UniverseObj : Universe = Universe.query.filter_by(id = PlaceObj.parent_universe_id).first()
if UniverseObj is None:
return jsonify({"data":[], "message": "Place does not exist"}), 200
if dataType == "standard":
DataStoreObj : PlaceDatastore = PlaceDatastore.query.filter_by(
universe_id = UniverseObj.id,
scope = scope,
key = key,
name = target
).order_by(PlaceDatastore.updated_at.desc()).first()
if DataStoreObj is None:
DataStoreObj : PlaceDatastore = PlaceDatastore(
placeid = placeId,
universe_id = UniverseObj.id,
scope=scope,
key=key,
name=target,
value=value
)
db.session.add(DataStoreObj)
else:
DataStoreObj.value = value
db.session.commit()
elif dataType == "sorted":
try:
value : int = int(value)
except:
return jsonify({"success": False, "message": "Value is not an integer"}), 400
DataStoreObj : PlaceOrderedDatastore = PlaceOrderedDatastore.query.filter_by(
universe_id = UniverseObj.id,
scope=scope,
key=key,
name=target
).order_by(PlaceOrderedDatastore.updated_at.desc()).first()
if DataStoreObj is None:
DataStoreObj : PlaceOrderedDatastore = PlaceOrderedDatastore(
placeid = placeId,
universe_id = UniverseObj.id,
scope=scope,
key=key,
name=target,
value=value
)
db.session.add(DataStoreObj)
else:
DataStoreObj.value = value
db.session.commit()
return jsonify({"data":value}) # TODO: Implement
@DataStoreRoute.route("/persistence/getV2", methods=["POST"])
@DataStoreRoute.route("/persistence/getv2", methods=["POST"])
@csrf.exempt
@GameServerRequired
def getv2():
placeId : int = request.args.get("placeId", default=None, type=int)
dataType : str = request.args.get("type", default=None, type=str)
scope : str = request.args.get("scope", default="global", type=str)
PlaceObj : Place = Place.query.filter_by(placeid = placeId).first()
if PlaceObj is None:
return jsonify({"data":[], "message": "Place does not exist"}), 200
UniverseObj : Universe = Universe.query.filter_by(id = PlaceObj.parent_universe_id).first()
if UniverseObj is None:
return jsonify({"data":[], "message": "Place does not exist"}), 200
dataBeingRequested = []
StartingCount = 0
while True:
ReqScope : str = request.form.get(f"qkeys[{str(StartingCount)}].scope", default=None, type=str)
if ReqScope is None:
break
Target : str = request.form.get(f"qkeys[{str(StartingCount)}].target", default=None, type=str)
DataStoreName : str = request.form.get(f"qkeys[{str(StartingCount)}].key", default=None, type=str)
if DataStoreName is None or Target is None or ReqScope is None:
break
dataBeingRequested.append({"Scope": ReqScope, "Target": Target, "Key": DataStoreName})
StartingCount += 1
if len(dataBeingRequested) == 0:
return jsonify({"data":[], "message": "No data being requested"}), 200
ReturnData = []
if dataType == 'standard':
for targetReq in dataBeingRequested:
DataStoreObj : PlaceDatastore = PlaceDatastore.query.filter_by(universe_id = UniverseObj.id, scope=targetReq["Scope"], key=targetReq["Key"], name=targetReq["Target"]).order_by(PlaceDatastore.updated_at.desc()).first()
if DataStoreObj is not None:
ReturnData.append({
"Value": DataStoreObj.value,
"Scope": DataStoreObj.scope,
"Key": DataStoreObj.key,
"Target": DataStoreObj.name
})
elif dataType == 'sorted':
for targetReq in dataBeingRequested:
DataStoreObj : PlaceOrderedDatastore = PlaceOrderedDatastore.query.filter_by(universe_id = UniverseObj.id, scope=targetReq["Scope"], key=targetReq["Key"], name=targetReq["Target"]).order_by(PlaceOrderedDatastore.updated_at.desc()).first()
if DataStoreObj is not None:
ReturnData.append({
"Value": str(DataStoreObj.value),
"Scope": DataStoreObj.scope,
"Key": DataStoreObj.key,
"Target": DataStoreObj.name
})
return jsonify({"data":ReturnData})
@DataStoreRoute.route("/persistence/increment", methods=["POST"])
@csrf.exempt
@GameServerRequired
def increment():
placeId : int = request.args.get("placeId", default=None, type=int)
key : str = request.args.get("key", default=None, type=str)
dataType : str = request.args.get("type", default=None, type=str)
scope : str = request.args.get("scope", default="global", type=str)
target : str = request.args.get("target", default=None, type=str)
value : int = request.args.get("value", default=None, type=int)
PlaceObj : Place = Place.query.filter_by(placeid = placeId).first()
if PlaceObj is None:
return jsonify({"data":[], "message": "Place does not exist"}), 200
UniverseObj : Universe = Universe.query.filter_by(id = PlaceObj.parent_universe_id).first()
if UniverseObj is None:
return jsonify({"data":[], "message": "Place does not exist"}), 200
if dataType == "standard":
DataStoreObj : PlaceDatastore = PlaceDatastore.query.filter_by(
universe_id=UniverseObj.id,
scope=scope,
key=key,
name=target
).order_by(PlaceDatastore.updated_at.desc()).first()
if DataStoreObj is None:
DataStoreObj : PlaceDatastore = PlaceDatastore(
placeid = placeId,
universe_id=UniverseObj.id,
scope=scope,
key=key,
name=target,
value=str(value)
)
db.session.add(DataStoreObj)
else:
try:
DataStoreObj.value = str(int(DataStoreObj.value) + value)
except:
return jsonify({"data":[], "message": "Value is not an integer"}), 200
db.session.commit()
elif dataType == "sorted":
DataStoreObj : PlaceOrderedDatastore = PlaceOrderedDatastore.query.filter_by(
universe_id=UniverseObj.id,
scope=scope,
key=key,
name=target
).order_by(PlaceOrderedDatastore.updated_at.desc()).first()
if DataStoreObj is None:
DataStoreObj : PlaceOrderedDatastore = PlaceOrderedDatastore(
placeid = placeId,
universe_id=UniverseObj.id,
scope=scope,
key=key,
name=target,
value=value
)
db.session.add(DataStoreObj)
else:
DataStoreObj.value += value # PlaceOrderedDatastore values should always be integers
return jsonify({"data":value})