def migrate_next_puzzle(puzzle): cur = db.cursor() logging.info("migrating puzzle {}".format(puzzle)) # Update any original that have an empty url cur.execute(query_update_puzzle_file_original_null_url, {'puzzle': puzzle}) # Update any original that have a '0' for url cur.execute(query_update_puzzle_file_original_s3_url, {'puzzle': puzzle}) db.commit() result = cur.execute(query_select_puzzle_file_to_migrate_from_s3, { 'puzzle': puzzle }).fetchall() if result: (result, col_names) = rowify(result, cur.description) for item in result: migrate_s3_puzzle(item) result = cur.execute(query_select_puzzle_file_to_migrate_from_unsplash, { 'puzzle': puzzle, }).fetchall() if result: (result, col_names) = rowify(result, cur.description) for item in result: migrate_unsplash_puzzle(item) cur.close()
def post(self): args = {} if request.form: args.update(request.form.to_dict(flat=True)) # Verify args action = args.get("action") if action not in SLOT_ACTIONS: abort(400) player = args.get("player") if not player: abort(400) cur = db.cursor() if action == "add": cur.execute(fetch_query_string("add-new-user-puzzle-slot.sql"), {"player": player}) elif action == "delete": cur.execute(fetch_query_string("delete-user-puzzle-slot.sql"), {"player": player}) cur.close() db.commit() return redirect( "/chill/site/admin/player/details/{player}/".format(player=player))
def test_next_migrate_script(self): "Get next migrate script to run" with self.app.app_context(): with self.app.test_client(): cur = db.cursor() cur.execute(read_query_file("create_table_puzzle_massive.sql")) cur.execute( read_query_file("upsert_puzzle_massive.sql"), { "key": "database_version", "label": "Database Version", "description": "something", "intvalue": 1, "textvalue": None, "blobvalue": None }) db.commit() script_files = [ "some/path/to/migrate_puzzle_massive_database_version_021.py", "some/path/to/migrate_puzzle_massive_database_version_901.py", "some/path/to/migrate_puzzle_massive_database_version_001.py", "some/path/to/migrate_puzzle_massive_database_version_000.py", "some/path/to/migrate_puzzle_massive_database_version_002.py", ] migrate_script = get_next_migrate_script(script_files) # The initial one should be migrate_puzzle_massive_database_version_000.py assert migrate_script == "some/path/to/migrate_puzzle_massive_database_version_002.py"
def update_user_points_and_m_date(player, points, score): cur = db.cursor() try: result = cur.execute( fetch_query_string("update_user_points_and_m_date.sql"), { "id": player, "points": points, "score": score, "POINTS_CAP": current_app.config["POINTS_CAP"], }, ) except sqlite3.IntegrityError: err_msg = { "msg": "Database integrity error. Does the player ({}) exist?".format( player), "status_code": 400, } cur.close() return err_msg cur.close() db.commit() msg = {"rowcount": result.rowcount, "msg": "Executed", "status_code": 200} return msg
def test_next_migrate_script_when_a_gap_exists(self): "Get next migrate script to run when a gap in version numbers exist" with self.app.app_context(): with self.app.test_client(): cur = db.cursor() cur.execute(read_query_file("create_table_puzzle_massive.sql")) cur.execute( read_query_file("upsert_puzzle_massive.sql"), { "key": "database_version", "label": "Database Version", "description": "something", "intvalue": 21, "textvalue": None, "blobvalue": None }) db.commit() script_files = [ "some/path/to/migrate_puzzle_massive_database_version_021.py", "some/path/to/migrate_puzzle_massive_database_version_901.py", "some/path/to/migrate_puzzle_massive_database_version_001.py", "some/path/to/migrate_puzzle_massive_database_version_000.py", "some/path/to/migrate_puzzle_massive_database_version_002.py", ] self.assertRaises(MigrateGapError, get_next_migrate_script, script_files)
def reward_player_for_score_threshold(player): cur = db.cursor() try: result = cur.execute( fetch_query_string( "reward_player_for_score_threshold--puzzle-instance-slot.sql"), { "player": player, "score_threshold": int( current_app.config.get( "REWARD_INSTANCE_SLOT_SCORE_THRESHOLD", '0')) }, ) except sqlite3.IntegrityError: err_msg = { "msg": "Database integrity error. Does the player ({}) exist?".format( player), "status_code": 400, } cur.close() return err_msg cur.close() db.commit() msg = {"rowcount": result.rowcount, "msg": "Executed", "status_code": 200} return msg
def delete_puzzle_timeline(puzzle_id): "" cur = db.cursor() result = cur.execute( fetch_query_string("select-internal-puzzle-details-for-puzzle_id.sql"), { "puzzle_id": puzzle_id }, ).fetchall() if not result: err_msg = {"msg": "No puzzle found", "status_code": 400} cur.close() return err_msg (result, col_names) = rowify(result, cur.description) puzzle_data = result[0] puzzle = puzzle_data["id"] result = cur.execute(fetch_query_string("delete_puzzle_timeline.sql"), {"puzzle": puzzle}) cur.close() db.commit() redis_connection.delete("timeline:{puzzle}".format(puzzle=puzzle)) redis_connection.delete("score:{puzzle}".format(puzzle=puzzle)) msg = {"rowcount": result.rowcount, "msg": "Deleted", "status_code": 200} return msg
def get(self): cur = db.cursor() response = make_response(redirect("/chill/site/admin/puzzle/")) # Delete the shareduser cookie if it exists expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) current_app.secure_cookie.set("shareduser", "", response, expires=expires) current_app.secure_cookie.set("user", str(ADMIN_USER_ID), response, expires_days=365) cur.execute( fetch_query_string("extend-cookie_expires-for-user.sql"), {"id": ADMIN_USER_ID}, ) db.commit() cur.close() return response
def post(self): args = {} xhr_data = request.get_json() if xhr_data: args.update(xhr_data) args.update(request.form.to_dict(flat=True)) args.update(request.args.to_dict(flat=True)) if len(args.keys()) == 0: abort(400) # Start db operations cur = db.cursor() llamas = 0 result = cur.execute(fetch_query_string("get-llama-count.sql")).fetchall() if result: (result, col_names) = rowify(result, cur.description) llamas = result[0]["llamas"] # TODO: Process args for llamaness processed = {"llama-check": True, "count": llamas} db.commit() cur.close() return json.jsonify(processed)
def migrate_s3_puzzle(puzzle): """ Download the file from S3 and update the url in the puzzle file. """ if not exists: logging.info("Bucket doesn't exist") return cur = db.cursor() puzzle_id = puzzle.get('puzzle_id') puzzle_dir = create_puzzle_dir(puzzle_id) url_components = urlparse(puzzle.get('url')) key = url_components.path[1:] file_name = os.path.basename(key) file_path = os.path.join(puzzle_dir, file_name) local_url = "/resources/{puzzle_id}/{file_name}".format( puzzle_id=puzzle_id, file_name=file_name) logging.info("Downloading {}".format( os.path.join(puzzle_dir, os.path.basename(key)))) s3.download_file(BUCKET_NAME, key, file_path) cur.execute( query_update_puzzle_file_url, { 'puzzle': puzzle.get('puzzle'), 'name': puzzle.get('name'), 'url': local_url }) db.commit() cur.close() logging.info( "{puzzle} migrating s3 puzzle file {name} ({puzzle_id}): {url}".format( **puzzle))
def set_lost_unsplash_photo(puzzle): """ remove the link the unsplash license is irrevocable: https://unsplash.com/license create a new preview photo from the original? """ logging.info( "{puzzle} unsplash puzzle file {name} not found: {url}".format( **puzzle)) cur = db.cursor() description = "{} / originally found on Unsplash".format( puzzle['description']) cur.execute( """update Puzzle set link = :link, description = :description, permission = -1 -- NOT PUBLIC where puzzle_id = :puzzle_id; """, { 'puzzle_id': puzzle['puzzle_id'], 'link': None, 'description': description }) insert_file = "update PuzzleFile set url = :url where puzzle = :puzzle and name = :name;" cur.execute(insert_file, { 'puzzle': puzzle['puzzle'], 'name': 'preview_full', 'url': '' }) db.commit() cur.close()
def user_id_from_ip(ip, skip_generate=True): cur = db.cursor() shareduser = current_app.secure_cookie.get(u"shareduser") if shareduser != None: # Check if this shareduser is still valid and another user hasn't chosen # a bit icon. result = cur.execute( fetch_query_string("select-user-by-id-and-no-password.sql"), { "id": shareduser }, ).fetchall() if result: cur.close() return int(shareduser) # Handle players that had a cookie in their browser, but then deleted it. # Or new players that are from the same ip that existing players are on. # These players are shown the new-player page. result = cur.execute( fetch_query_string("select-user-id-by-ip-and-no-password.sql"), { "ip": ip }).fetchall() user_id = ANONYMOUS_USER_ID # No ip in db so create it except if the skip_generate flag is set if not result: if skip_generate: cur.close() return None login = generate_user_login() cur.execute( fetch_query_string("add-new-user-for-ip.sql"), { "ip": ip, "login": login, "points": current_app.config["NEW_USER_STARTING_POINTS"], }, ) db.commit() result = cur.execute( fetch_query_string("select-user-id-by-ip-and-login.sql"), { "ip": ip, "login": login }, ).fetchall() (result, col_names) = rowify(result, cur.description) user_id = result[0]["id"] else: (result, col_names) = rowify(result, cur.description) user_id = result[0]["id"] cur.close() return user_id
def delete_puzzle_file(puzzle_id, file_name): "" cur = db.cursor() result = cur.execute( fetch_query_string("select-internal-puzzle-details-for-puzzle_id.sql"), { "puzzle_id": puzzle_id }, ).fetchall() if not result: err_msg = {"msg": "No puzzle found", "status_code": 400} cur.close() return err_msg (result, col_names) = rowify(result, cur.description) puzzle_data = result[0] puzzle = puzzle_data["id"] result = cur.execute( fetch_query_string("select_puzzle_file_with_attribution.sql"), { "puzzle": puzzle, "name": file_name }, ).fetchall() if result: (result, col_names) = rowify(result, cur.description) puzzle_file_with_attribution_data = result[0] # Delete previous attribution if it exists if puzzle_file_with_attribution_data["attribution_id"]: result = cur.execute( fetch_query_string("delete_attribution_from_puzzle_file.sql"), {"id": puzzle_file_with_attribution_data["puzzle_file_id"]}, ) result = cur.execute( fetch_query_string("delete_attribution.sql"), { "attribution": puzzle_file_with_attribution_data["attribution_id"] }, ) result = cur.execute( fetch_query_string("delete_puzzle_file_with_name_for_puzzle.sql"), { "puzzle": puzzle, "name": file_name }, ) db.commit() cur.close() msg = {"rowcount": result.rowcount, "msg": "Deleted", "status_code": 200} return msg
def add_to_timeline(puzzle_id, player, points=0, timestamp=None, message=""): "" if timestamp is None: _timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime()) else: # TODO: Should verify timestamp is in ISO format. _timestamp = timestamp if not isinstance(points, int): err_msg = {"msg": "points needs to be an integer", "status_code": 400} return err_msg cur = db.cursor() result = cur.execute( fetch_query_string("select-internal-puzzle-details-for-puzzle_id.sql"), { "puzzle_id": puzzle_id }, ).fetchall() if not result: err_msg = {"msg": "No puzzle found", "status_code": 400} cur.close() return err_msg (result, col_names) = rowify(result, cur.description) puzzle_data = result[0] puzzle = puzzle_data["id"] # TODO: The message is not added to the Timeline DB table for now since that # information is not currently being used. try: result = cur.execute( fetch_query_string("insert_batchpoints_to_timeline.sql"), { "puzzle": puzzle, "player": player, "points": points, "timestamp": _timestamp, }, ) except sqlite3.IntegrityError: err_msg = { "msg": "Database integrity error. Does the player ({}) exist?".format( player), "status_code": 400, } cur.close() return err_msg cur.close() db.commit() msg = {"rowcount": result.rowcount, "msg": "Inserted", "status_code": 200} return msg
def post(self): """Update shareduser to user""" # Prevent creating a new user if no support for cookies. Player should # have 'ot' already set by viewing the page. uses_cookies = current_app.secure_cookie.get(u"ot") if not uses_cookies: abort(400) cur = db.cursor() response = make_response("", 200) user = user_id_from_ip(request.headers.get("X-Real-IP")) if user == None: abort(400) user = int(user) # Only set new user if enough dots result = cur.execute( fetch_query_string("select-minimum-points-for-user.sql"), { "user": user, "points": current_app.config["NEW_USER_STARTING_POINTS"] + current_app.config["POINT_COST_FOR_CHANGING_BIT"], }, ).fetchone() if result: self.register_new_user(user) # Save as a cookie current_app.secure_cookie.set(u"user", str(user), response, expires_days=365) # Remove shareduser expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) current_app.secure_cookie.set(u"shareduser", "", response, expires=expires) cur.execute( fetch_query_string("decrease-user-points.sql"), { "user": user, "points": current_app.config["POINT_COST_FOR_CHANGING_BIT"], }, ) cur.close() db.commit() return response
def test_next_migrate_script_when_none_are_found(self): "Get next migrate script when none are found" with self.app.app_context(): with self.app.test_client(): cur = db.cursor() cur.execute(read_query_file("create_table_puzzle_massive.sql")) db.commit() script_files = [] self.assertRaises(MigrateError, get_next_migrate_script, script_files)
def migrate(config): "Migrate the sqlite3 database from the current database_version." cur = db.cursor() for filename in [ "create_user_puzzle_index.sql", ]: cur.execute(read_query_file(filename)) db.commit() cur.close()
def test_get_latest_version_when_no_migrate_scripts(self): "Get latest version when no migrate scripts" with self.app.app_context(): with self.app.test_client(): cur = db.cursor() cur.execute(read_query_file("create_table_puzzle_massive.sql")) db.commit() script_files = [] self.assertRaises(Exception, get_latest_version_based_on_migrate_scripts, script_files)
def post(self): "Route is protected by basic auth in nginx" args = {} if request.form: args.update(request.form.to_dict(flat=True)) # Verify args action = args.get("action") if action not in ACTIONS: abort(400) name_register_ids = request.form.getlist("name_register_id") if len(name_register_ids) == 0: abort(400) if not isinstance(name_register_ids, list): name_register_ids = [name_register_ids] name_register_users = request.form.getlist("name_register_user") if len(name_register_users) == 0: abort(400) if not isinstance(name_register_users, list): name_register_users = [name_register_users] cur = db.cursor() if action == "reject": def each(name_register_ids): for id in name_register_ids: yield {"id": id} cur.executemany( fetch_query_string("reject-name-on-name-register-for-id.sql"), each(name_register_ids), ) db.commit() cur.close() routes_to_purge = [] for user in name_register_users: routes_to_purge.append( "/chill/site/internal/player-bit/{}/".format(user)) purge_route_from_nginx_cache( "\n".join(routes_to_purge), current_app.config.get("PURGEURLLIST"), ) return redirect("/chill/site/admin/name-register-review/")
def get(self, anonymous_login): "Set the user cookie if correct anon bit link." login = anonymous_login[:-13] password = anonymous_login[-13:] cur = db.cursor() response = make_response(redirect("/")) result = cur.execute(fetch_query_string("select-user-by-login.sql"), { "login": login }).fetchall() if not result: cur.close() return make_response("no user", 404) (result, col_names) = rowify(result, cur.description) user_data = result[0] expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) if crypt.crypt(password, user_data["password"]) == user_data["password"]: current_app.secure_cookie.set("shareduser", "", response, expires=expires) current_app.secure_cookie.set("user", str(user_data["id"]), response, expires_days=365) cur.execute( fetch_query_string("extend-cookie_expires-for-user.sql"), {"id": user_data["id"]}, ) db.commit() else: # Invalid anon login; delete cookie just in case it's there current_app.secure_cookie.set("user", "", response, expires=expires) cur.close() return response
def update_user_name_approved_for_approved_date_due(): cur = db.cursor() try: result = cur.execute( fetch_query_string( "update-user-name-approved-for-approved_date-due.sql"), ) except sqlite3.IntegrityError: err_msg = { "msg": "Database integrity error. update_user_name_approved_for_approved_date_due", "status_code": 400, } cur.close() return err_msg cur.close() db.commit() msg = {"rowcount": result.rowcount, "msg": "Executed", "status_code": 200} return msg
def get(self): "Return an object to be used by the generateBitLink js call" user = current_app.secure_cookie.get("user") if user is None: return make_response("no user", 403) user = int(user) (p_string, password) = generate_password() # Store encrypted password in db cur = db.cursor() try: result = cur.execute( fetch_query_string("select-login-from-user.sql"), { "id": user }).fetchall() except IndexError: # user may have been added after a db rollback cur.close() return make_response("no user", 404) if not result: cur.close() return make_response("no user", 404) (result, col_names) = rowify(result, cur.description) user_data = result[0] cur.execute( fetch_query_string("update-password-for-user.sql"), { "id": user, "password": password }, ) db.commit() cur.close() data = { "bit": "".join(["", "/puzzle-api/bit/", user_data["login"], p_string]) } return make_response(json.jsonify(data), 200)
def register_new_user(self, user_id): """Update initial ip tracked user to now be cookie tracked with a password.""" cur = db.cursor() login = generate_user_login() (p_string, password) = generate_password() cur.execute( fetch_query_string("set-initial-password-for-user.sql"), { "id": user_id, "password": password, "ip": request.headers.get("X-Real-IP"), }, ) # Other players on the same network that are tracked by shareduser # cookie will have it updated to a new value. db.commit() cur.close()
def add_puzzle_file(puzzle_id, file_name, url, attribution=None): cur = db.cursor() result = cur.execute( fetch_query_string("select-internal-puzzle-details-for-puzzle_id.sql"), { "puzzle_id": puzzle_id }, ).fetchall() if not result: err_msg = {"msg": "No puzzle found", "status_code": 400} cur.close() return err_msg (result, col_names) = rowify(result, cur.description) puzzle_data = result[0] puzzle = puzzle_data["id"] if attribution: attribution_result = cur.execute( fetch_query_string("insert_attribution.sql"), attribution) attribution_id = attribution_result.lastrowid result = cur.execute( fetch_query_string("add-puzzle-file-with-attribution.sql"), { "puzzle": puzzle, "name": file_name, "url": url, "attribution": attribution_id, }, ) else: result = cur.execute( fetch_query_string("add-puzzle-file.sql"), { "puzzle": puzzle, "name": file_name, "url": url }, ) db.commit() cur.close() msg = {"rowcount": result.rowcount, "msg": "Inserted", "status_code": 200} return msg
def update_points_to_minimum_for_all_users(minimum): cur = db.cursor() try: result = cur.execute( fetch_query_string("update_points_to_minimum_for_all_users.sql"), {"minimum": minimum}, ) except sqlite3.IntegrityError: err_msg = { "msg": "Database integrity error. update_points_to_minimum_for_all_users", "status_code": 400, } cur.close() return err_msg cur.close() db.commit() msg = {"rowcount": result.rowcount, "msg": "Executed", "status_code": 200} return msg
def test_get_latest_version_based_on_migrate_scripts(self): "Get latest version based on migrate scripts" with self.app.app_context(): with self.app.test_client(): cur = db.cursor() cur.execute(read_query_file("create_table_puzzle_massive.sql")) db.commit() script_files = [ "some/path/to/migrate_puzzle_massive_database_version_021.py", "some/path/to/migrate_puzzle_massive_database_version_901.py", "some/path/to/migrate_puzzle_massive_database_version_001.py", "some/path/to/migrate_puzzle_massive_database_version_000.py", "some/path/to/migrate_puzzle_massive_database_version_002.py", ] latest_version = get_latest_version_based_on_migrate_scripts( script_files) assert latest_version == 902
def test_next_migrate_script_for_initial_migration(self): "Get next migrate script to run for the initial migration" with self.app.app_context(): with self.app.test_client(): cur = db.cursor() cur.execute(read_query_file("create_table_puzzle_massive.sql")) db.commit() script_files = [ "some/path/to/migrate_puzzle_massive_database_version_021.py", "some/path/to/migrate_puzzle_massive_database_version_901.py", "some/path/to/migrate_puzzle_massive_database_version_001.py", "some/path/to/migrate_puzzle_massive_database_version_000.py", "some/path/to/migrate_puzzle_massive_database_version_002.py", ] migrate_script = get_next_migrate_script(script_files) # The initial one should be migrate_puzzle_massive_database_version_000.py assert migrate_script == "some/path/to/migrate_puzzle_massive_database_version_000.py"
def get(self): "Show a batch of available bit icon names" save_cookie = False offset_seconds = current_app.secure_cookie.get(u"ot") if not offset_seconds: save_cookie = True offset_seconds = str(randint(1, 900)) offset_time = "{} seconds".format(int(offset_seconds)) limit = request.args.get("limit") if not limit: limit = 7 try: limit = int(limit) except ValueError: limit = 7 if limit not in (48, 7): limit = 7 cur = db.cursor() # List of bit icon names that are available result = cur.execute( fetch_query_string("select_random_bit_batch.sql"), { "offset_time": offset_time, "limit": limit }, ).fetchall() (result, col_names) = rowify(result, cur.description) bits = [x["icon"] for x in result] response = make_response(encoder.encode({"data": bits}), 200) if save_cookie: current_app.secure_cookie.set(u"ot", str(offset_seconds), response, expires_days=1) cur.close() db.commit() return response
def update_bit_icon_expiration(player): cur = db.cursor() try: result = cur.execute( fetch_query_string("update_bit_icon_expiration.sql"), {"user": player}, ) except sqlite3.IntegrityError: err_msg = { "msg": "Database integrity error. Does the player ({}) exist?".format( player), "status_code": 400, } cur.close() return err_msg cur.close() db.commit() msg = {"rowcount": result.rowcount, "msg": "Executed", "status_code": 200} return msg
def post(self, puzzle_id): "Ping and record the time in milliseconds for this player." now_ms = int(time.time() * 1000) response = {"message": "", "name": ""} user = current_app.secure_cookie.get(u"user") or user_id_from_ip( request.headers.get("X-Real-IP"), skip_generate=True) if user == None: response["message"] = "Player not currently logged in." response["name"] = "error" return make_response(json.jsonify(response), 400) user = int(user) cur = db.cursor() # Validate the puzzle_id result = cur.execute( fetch_query_string("select_viewable_puzzle_id.sql"), { "puzzle_id": puzzle_id }, ).fetchall() if not result: response["message"] = "Invalid puzzle id." response["name"] = "error" cur.close() db.commit() return make_response(json.jsonify(response), 400) else: (result, col_names) = rowify(result, cur.description) puzzle = result[0].get("puzzle") status = result[0].get("status") if status != ACTIVE: response["message"] = "Puzzle not active" response["name"] = "invalid" cur.close() db.commit() return make_response(json.jsonify(response), 200) # publish to the puzzle channel the ping with the user id. This will # allow that player to determine their latency. token = uuid.uuid4().hex[:4] pingtoken_key = get_pingtoken_key(puzzle, user, token) redis_connection.setex(pingtoken_key, 60, now_ms) current_app.logger.debug( "publish ping {puzzle_id}".format(puzzle_id=puzzle_id)) sse.publish( "{user}:{token}".format(user=user, token=token), type="ping", channel="puzzle:{puzzle_id}".format(puzzle_id=puzzle_id), ) response["message"] = "ping accepted" response["name"] = "accepted" response = make_response(json.jsonify(response), 202) cur.close() db.commit() return response