Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tags on upload #314

Merged
merged 50 commits into from
Oct 12, 2019
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
812ffb8
Bugfix: Fixed expansion panel opening on clicking visibility toggle.
twobackfromtheend Jul 5, 2019
fdfac2a
Merge remote-tracking branch 'origin/master' into tags_on_upload
twobackfromtheend Jul 5, 2019
7099a27
Remove tag query param (only allow private tag keys for now).
twobackfromtheend Jul 5, 2019
3c024ae
optimised imports
twobackfromtheend Jul 5, 2019
b72969e
Warning fix: replaced Button variant=fab with Fab component.
twobackfromtheend Jul 5, 2019
e7c6a0c
Moved theme context.
twobackfromtheend Jul 10, 2019
49648b5
Added Tags page with UploadTags selector in UploadForm
twobackfromtheend Jul 10, 2019
2fd654c
Shortened import
twobackfromtheend Jul 10, 2019
edd593f
Added privateKey to returned type of Tags,
twobackfromtheend Jul 10, 2019
3c4ddd9
line wrapping.
twobackfromtheend Jul 10, 2019
8eb76d4
skipped tests
dtracers Jul 11, 2019
e194305
Update key to camelCase
twobackfromtheend Jul 11, 2019
a9a1358
Merge branch 'tags_on_upload' of https://github.com/SaltieRL/Distribu…
twobackfromtheend Jul 11, 2019
e3a494a
Skip test for proto upload with tags using tag name
twobackfromtheend Jul 11, 2019
d462443
Removed tag integration test
twobackfromtheend Jul 11, 2019
1a4b4dd
Removed another tag test in integration tests.
twobackfromtheend Jul 11, 2019
69987ea
Merge remote-tracking branch 'origin/master' into tags_on_upload
twobackfromtheend Jul 11, 2019
effb7c3
Added some tags to upload.
twobackfromtheend Jul 11, 2019
207533f
Cleaned up private key/id mixup, formatting.
twobackfromtheend Jul 11, 2019
493b3e3
Propagate private_key private_id fix.
twobackfromtheend Jul 11, 2019
25fc59e
Propagate private_key private_id fix. 2
twobackfromtheend Jul 11, 2019
c712f49
Corrected test request url (missing `/`)
twobackfromtheend Jul 11, 2019
b6ee083
Try creating all tags only after first replay is uploaded.
twobackfromtheend Jul 11, 2019
98f92e7
Fixed url
twobackfromtheend Jul 11, 2019
747102c
Merge branch 'master' into tags_on_upload
dtracers Jul 12, 2019
c5a76b0
moved private key creation to only happen once
dtracers Jul 29, 2019
68e92c6
added delay
dtracers Jul 29, 2019
7e26e53
Merge branch 'master' into tags_on_upload
Sciguymjm Jul 29, 2019
5d87541
Arrays start at 0
dtracers Jul 29, 2019
d643529
Merge branch 'tags_on_upload' of https://github.com/SaltieRL/Distribu…
dtracers Jul 29, 2019
655d5b5
stylin
dtracers Jul 29, 2019
f6edf35
except when they start at 1
dtracers Jul 29, 2019
4461ee1
Merge branch 'master' into tags_on_upload
dtracers Sep 4, 2019
df0f230
Merge branch 'master' into tags_on_upload
dtracers Oct 10, 2019
9f5619c
Merge branch 'master' into tags_on_upload
dtracers Oct 10, 2019
e23bdf6
Merge branch 'master' into tags_on_upload
dtracers Oct 11, 2019
c716dfb
Set logging to be more readable.
dtracers Oct 11, 2019
8a43414
fix logging level in celery
dtracers Oct 11, 2019
3f07bee
Possibly fix decoding issue
dtracers Oct 12, 2019
34e2c72
added logging
dtracers Oct 12, 2019
8de4b4e
fixed str logging error
dtracers Oct 12, 2019
9e9aee6
fixed logging
dtracers Oct 12, 2019
e830da4
url encode the keys.
dtracers Oct 12, 2019
6620ff3
Adding logging for all local dev requests
dtracers Oct 12, 2019
dfa23e3
fix issues with platformid for local players
dtracers Oct 12, 2019
21c48a8
Make the first replay upload have user info
dtracers Oct 12, 2019
e820698
fix imports
dtracers Oct 12, 2019
48d0c1d
fix typescript issue
dtracers Oct 12, 2019
4af5996
Merge branch 'master' into tags_on_upload
dtracers Oct 12, 2019
88b5915
address comments
dtracers Oct 12, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
- cd webapp
- npm start &
- cd $TRAVIS_BUILD_DIR
- celery -A backend.tasks.celery_worker.celery worker --pool=solo -l debug &
- celery -A backend.tasks.celery_worker.celery worker --pool=solo -l WARNING &
# Wait for all other systems to be setup
- sleep 30
- pytest tests/integration_tests --cov=./
Expand Down
7 changes: 7 additions & 0 deletions backend/blueprints/spa_api/errors/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ class TagError(CalculatedError):
message = "Exception with tags"


class TagKeyError(TagError):
status_code = 400

def __init__(self, tag_key, exception):
self.message = f'Unable to decode tag_key; [{tag_key}] ' + str(exception)


class TagNotFound(TagError):
status_code = 404
message = "Tag not found"
Expand Down
51 changes: 26 additions & 25 deletions backend/blueprints/spa_api/service_layers/replay/tag.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,41 @@
import base64
from typing import List, Dict
from urllib.parse import unquote, quote

from backend.blueprints.spa_api.service_layers.utils import with_session
from backend.utils.logging import ErrorLogger
from backend.blueprints.spa_api.errors.errors import TagNotFound, PlayerNotFound, TagError
from backend.database.objects import Tag as DBTag, Player
from backend.blueprints.spa_api.errors.errors import TagNotFound, TagError, TagKeyError
from backend.database.objects import Tag as DBTag
from backend.database.wrapper.tag_wrapper import TagWrapper, DBTagNotFound
from backend.utils.safe_flask_globals import get_current_user_id


class Tag:
def __init__(self, name: str, owner: str, db_tag: DBTag = None):
def __init__(self, name: str, owner: str, privateKey: str = None, db_tag: DBTag = None):
super().__init__()
self.name = name
self.owner_id = owner
self.ownerId = owner
self.privateKey = privateKey
self.db_tag = db_tag

def to_JSON(self, with_id=False):
if with_id:
return {
"name": self.name,
"owner_id": self.owner_id,
"ownerId": self.ownerId,
"privateKey": self.privateKey,
"tag_id": self.db_tag.id
}

return {
"name": self.name,
"owner_id": self.owner_id
"ownerId": self.ownerId,
"privateKey": self.privateKey,
}

@staticmethod
def create_from_dbtag(tag: DBTag):
return Tag(tag.name, tag.owner, db_tag=tag)
private_key = None if tag.private_id is None else Tag.encode_tag(tag.id, tag.private_id)
return Tag(tag.name, tag.owner, privateKey=private_key, db_tag=tag)

@staticmethod
@with_session
Expand All @@ -43,13 +47,13 @@ def add_private_key(name: str, private_key: str, session=None, player_id=None):

@staticmethod
@with_session
def create(name: str, session=None, player_id=None, private_key=None) -> 'Tag':
def create(name: str, session=None, player_id=None, private_id=None) -> 'Tag':
"""
Creates a new instance of Tag, add one to the db if it does not exist.
:param name: Tag name
:param session: Database session
:param player_id
:param private_key
:param private_id
:return:
"""
# Check if tag exists
Expand All @@ -59,7 +63,7 @@ def create(name: str, session=None, player_id=None, private_key=None) -> 'Tag':
return tag
except DBTagNotFound:
pass
dbtag = TagWrapper.create_tag(session, get_current_user_id(player_id=player_id), name, private_key=private_key)
dbtag = TagWrapper.create_tag(session, get_current_user_id(player_id=player_id), name, private_id=private_id)
tag = Tag.create_from_dbtag(dbtag)
return tag

Expand Down Expand Up @@ -128,33 +132,30 @@ def get_encoded_private_key(name: str, session=None) -> str:
@staticmethod
def encode_tag(tag_id: int, private_id: str) -> str:
merged = str(tag_id + 1000) + ":" + private_id
return base64.b85encode(merged.encode(encoding="utf-8")).decode('utf-8')
return quote(base64.b85encode(merged.encode(encoding="utf-8")).decode('utf-8'))
dtracers marked this conversation as resolved.
Show resolved Hide resolved

@staticmethod
def decode_tag(encoded_key: str):
decoded_key_bytes = base64.b85decode(encoded_key)
decoded_key = decoded_key_bytes.decode(encoding="utf-8")
decoded_key_bytes = base64.b85decode(unquote(encoded_key).encode('utf-8'))

try:
decoded_key = decoded_key_bytes.decode(encoding="utf-8")
except UnicodeDecodeError as e:
raise TagKeyError(encoded_key, e)

first_index = decoded_key.find(':')
tag_id = int(decoded_key[0: first_index]) - 1000
decoded_private_id = decoded_key[first_index + 1:]
return tag_id, decoded_private_id


@with_session
def apply_tags_to_game(query_params: Dict[str, any]=None, game_id=None, session=None):
def apply_tags_to_game(query_params: Dict[str, any] = None, game_id=None, session=None):
dtracers marked this conversation as resolved.
Show resolved Hide resolved
dtracers marked this conversation as resolved.
Show resolved Hide resolved
if query_params is None:
return None
if 'tags' not in query_params and 'private_tag_keys' not in query_params:
if 'private_tag_keys' not in query_params:
dtracers marked this conversation as resolved.
Show resolved Hide resolved
return None
tags = query_params['tags'] if 'tags' in query_params else []
private_ids = query_params['private_tag_keys'] if 'private_tag_keys' in query_params else []
if len(tags) > 0:
player_id = query_params['player_id']
if session.query(Player).filter(Player.platformid == player_id).first() is None:
ErrorLogger.log_error(PlayerNotFound())
else:
for tag in tags:
created_tag = Tag.create(tag, session=session, player_id=player_id)
TagWrapper.add_tag_to_game(session, game_id, created_tag.db_tag)

for private_id in private_ids:
tag_id, private_key = Tag.decode_tag(private_id)
Expand Down
10 changes: 5 additions & 5 deletions backend/blueprints/spa_api/spa_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,13 @@ def api_upload_proto(query_params=None):
@bp.route('/tag/<name>', methods=["PUT"])
@require_user
@with_query_params(accepted_query_params=[
QueryParam(name='private_key', type_=str, optional=True)
QueryParam(name='private_id', type_=str, optional=True)
])
def api_create_tag(name: str, query_params=None):
private_key = None
if 'private_key' in query_params:
private_key = query_params['private_key']
tag = Tag.create(name, private_key=private_key)
private_id = None
if 'private_id' in query_params:
private_id = query_params['private_id']
tag = Tag.create(name, private_id=private_id)
return better_jsonify(tag), 201


Expand Down
6 changes: 2 additions & 4 deletions backend/blueprints/spa_api/utils/query_param_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,9 @@ def convert_string_to_enum(string: str) -> T:
]

tag_params = [
QueryParam(name="tags", optional=True,
type_=str, is_list=True,
required_siblings=['player_id']),
QueryParam(name="private_tag_keys", optional=True,
tip='This is base 64 encoded it is not the private key directly.',
tip='This is only required only if you wish to apply a tag that is owned by another account. ' +
'This is base 64 encoded.',
type_=str, is_list=True)
]

Expand Down
5 changes: 0 additions & 5 deletions backend/blueprints/spa_api/utils/query_params_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from typing import List, Dict, Any, Callable, Optional, Type
from urllib.parse import urlencode

from flask import Request

Expand Down Expand Up @@ -151,7 +150,3 @@ def validate(created_query_params) -> Optional[CalculatedError]:
len(created_query_params[query]),
len(created_query_params[sibling_query.name]))
return validate


def create_query_string(query_params):
return urlencode(query_params)
2 changes: 1 addition & 1 deletion backend/database/wrapper/player_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def create_default_player(session=None):
if player is None:
player = Player()

player.platformid = "LOCAL_PLATFORMID" if player is None else player.platformid
player.platformid = "LOCAL_PLATFORMID" if player.platformid is None else player.platformid
player.platformname = 'test user with a really long name but even longer'
if bool(random.getrandbits(1)):
player.avatar = "https://media.istockphoto.com/photos/golden-retriever-puppy-looking-up-isolated-on-black-backround-picture-id466614709?k=6&m=466614709&s=612x612&w=0&h=AVW-4RuYXFPXxLBMHiqoAKnvLrMGT9g62SduH2eNHxA="
Expand Down
4 changes: 2 additions & 2 deletions backend/database/wrapper/tag_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

class TagWrapper:
@staticmethod
def create_tag(session, user_id: str, name: str, private_key: str = None) -> Tag:
tag = Tag(name=name, owner=user_id, private_id=private_key)
def create_tag(session, user_id: str, name: str, private_id: str = None) -> Tag:
tag = Tag(name=name, owner=user_id, private_id=private_id)
session.add(tag)
session.commit()
return tag
Expand Down
1 change: 1 addition & 0 deletions backend/initial_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from backend.utils.checks import is_local_dev
from backend.utils.metrics import MetricsHandler
from backend.utils.logging import ErrorLogger
from backend.utils.safe_flask_globals import UserManager

logger = logging.getLogger(__name__)
logger.info("Setting up server.")
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ kiwisolver==1.0.1
kombu==4.4
MarkupSafe==1.0
matplotlib==2.2.3
numpy==1.15.1
numpy==1.17.0
pandas==0.24.2
proj==0.1.0
protobuf==3.6.1
Expand Down
2 changes: 2 additions & 0 deletions tests/integration_tests/no_react/heatmaps_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import logging
import time

import requests
Expand All @@ -15,6 +16,7 @@ class Test_Heatmaps:

@classmethod
def setup_class(cls):
logging.basicConfig(level=logging.ERROR)
cls.thread = KillableThread(target=start_server)
cls.thread.daemon = True
cls.thread.start()
Expand Down
44 changes: 35 additions & 9 deletions tests/integration_tests/no_react/test_upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
import requests

from RLBotServer import start_server
from backend.database.objects import GameVisibilitySetting, User, Player
from backend.blueprints.spa_api.errors.errors import PlayerNotFound
from backend.database.objects import GameVisibilitySetting, Player
from tests.utils.killable_thread import KillableThread
from tests.utils.replay_utils import get_complex_replay_list, download_replay_discord

Expand All @@ -23,6 +22,8 @@ class Test_BasicServerCommands():

@classmethod
def setup_class(cls):
logging.basicConfig(level=logging.ERROR)
logger.setLevel(logging.DEBUG)
cls.thread = KillableThread(target=start_server)
cls.thread.daemon = True
cls.thread.start()
Expand All @@ -37,21 +38,47 @@ def test_upload_files(self, mock_user, no_errors_are_logged):

replay_list = get_complex_replay_list()[0:4]

tags = ['TAG1', 'TAG2', 'TAG3', ['TAG4', 'TAG2']]
tags = [['TAG1'], ['TAG2'], ['TAG3'], ['TAG4', 'TAG2']]
privacy = [GameVisibilitySetting.DEFAULT.name,
GameVisibilitySetting.PUBLIC.name,
GameVisibilitySetting.PRIVATE.name,
GameVisibilitySetting.PRIVATE.name]
users = [
'invalid',
'76561198018756583',
'invalid',
'76561198018756583',
'76561198018756583'
]

tag_keys = ['invalid_key']

def create_all_tags():
created_tags = []
for _tags in tags:
keys = []
for _tag in _tags:
if _tag not in created_tags:
r = requests.put(LOCAL_URL + f'/api/tag/{_tag}')
r.raise_for_status()
created_tags.append(_tag)

# create private id
r = requests.put(LOCAL_URL + f'/api/tag/{_tag}/private_key/{_tag}')
r.raise_for_status()
json = requests.get(LOCAL_URL + f'/api/tag/{_tag}/private_key').json()
keys.append(json)
tag_keys.append(keys)

for index, replay_url in enumerate(replay_list):
params = {'tags': tags[index], 'visibility': privacy[index], 'player_id': users[index]}
logger.debug('Testing:', replay_url)
if index == 1:
logger.error("CREATING INITIAL TAG DATA")
time.sleep(20)
create_all_tags()
time.sleep(1)

params = {'visibility': privacy[index], 'player_id': users[index], 'private_tag_keys': tag_keys[index]}
logger.debug('TESTING URL:' + str(replay_url))
logger.debug('TESTING PARAMS:' + str(params))
f = download_replay_discord(replay_url)
r = requests.post(LOCAL_URL + '/api/upload', files={'replays': ('fake_file.replay', f)}, params=params)
r.raise_for_status()
Expand All @@ -68,11 +95,10 @@ def test_upload_files(self, mock_user, no_errors_are_logged):
assert(int(result) == len(replay_list))

response = requests.get(LOCAL_URL + '/api/tag')

result = json.loads(response.content)
assert result[0]['owner_id'] == "76561198018756583"
assert result[0]['ownerId'] == "76561198018756583"
assert result[0]['name'].startswith('TAG')
assert len(result) == 3
assert len(result) == 4

response = requests.get(LOCAL_URL + '/api/player/76561198018756583/match_history?page=0&limit=10')
assert response.status_code == 200
Expand Down
16 changes: 10 additions & 6 deletions tests/integration_tests/no_react/upload_proto_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import base64
import json
import logging
import time
import zlib

Expand All @@ -17,6 +18,7 @@ class Test_UploadingProtos():

@classmethod
def setup_class(cls):
logging.basicConfig(level=logging.ERROR)
cls.thread = KillableThread(target=start_server)
cls.thread.daemon = True
cls.thread.start()
Expand All @@ -37,9 +39,9 @@ def test_upload_proto(self):
'proto': encoded_proto,
'pandas': encoded_pandas
}
r = requests.post(LOCAL_URL + '/api/upload/proto', json=obj, params={'tags': ['TAG'],
'visibility': GameVisibilitySetting.PRIVATE.name,
'player_id': proto_game.players[0].id.id})
r = requests.post(LOCAL_URL + '/api/upload/proto', json=obj,
params={'visibility': GameVisibilitySetting.PRIVATE.name,
'player_id': proto_game.players[0].id.id})

r.raise_for_status()
assert r.status_code == 200
Expand All @@ -51,9 +53,11 @@ def test_upload_proto(self):
response = requests.get(LOCAL_URL + '/api/tag')

result = json.loads(response.content)
assert result[0]['owner_id'] == "76561198018756583"
assert result[0]['name'].startswith('TAG')
assert len(result) == 1

# TODO: Readd test for tag using private key
# assert result[0]['ownerId'] == "76561198018756583"
# assert result[0]['name'].startswith('TAG')
# assert len(result) == 1

response = requests.get(LOCAL_URL + '/api/player/76561198018756583/match_history?page=0&limit=10')
assert response.status_code == 200
Expand Down
Loading