示例#1
0
def validate(db: StandardDatabase, token, email=None):
    """
    validate player token
    :param db: connection
    :param token: jwt token
    :param email: mail
    :return Player or None
    """
    if not db.has_collection('players'):
        logger.error('cannot validate token if players do not exist')
        raise PlayerStateException('players collection does not exist')
    col = db.collection('players')
    if email:
        db_player = next(col.find({'mail': email}), None)
        if not db_player:
            logger.error(f'could not resolve player {email}')
            raise PlayerStateException(f'player {email} does not exist')
        if token in db_player['tokens']:
            player = Player(db_player['mail'], '')
            player.id = UUID(db_player['_key'])
            player.password = db_player['password']
            return player
    else:
        for db_player in col.all():
            if 'tokens' in db_player and token in db_player['tokens']:
                player = Player(db_player['mail'], '')
                player.id = UUID(db_player['_key'])
                player.password = db_player['password']
                return player
    return None
示例#2
0
def get_all_player_emails(db: StandardDatabase) -> [str]:
    """
    return all player email addresses
    :param db: connection
    :return: list of known mail addresses
    """
    if not db.has_collection('players'):
        logger.error('cannot resolve player if players do not exist')
        raise PlayerStateException('players collection does not exist')
    col = db.collection('players')
    return [p['mail'] for p in col.all()]
示例#3
0
def delete(db: StandardDatabase, player: Player):
    """
    remove existing player
    :param db: connection
    :param player: target
    """
    if not db.has_collection('players'):
        logger.error('cannot remove player if players do not exist')
        raise PlayerStateException(f'players collection does not exist')
    col = db.collection('players')
    db_player = next(col.find({'mail': player.email}), None)
    if not db_player:
        logger.error(f'cannot find player {player.email}')
        raise PlayerStateException(f'could not find player {player.email}')
    col.delete(db_player)
示例#4
0
def read(db: StandardDatabase, player_id):
    """
    find existing player
    :param db: connection
    :param player_id: id
    :return: Player or None
    """
    if not db.has_collection('players'):
        logger.error('cannot resolve player if players do not exist')
        raise PlayerStateException('players collection does not exist')
    col = db.collection('players')
    db_player = next(col.find({'_key': player_id}), None)
    if not db_player:
        return None
    return Player(db_player['email'], db_player['password'])
示例#5
0
def save(db: StandardDatabase, instance: GameInstance):
    """
    save a game instance
    :param db: connection
    :param instance: game
    """
    logger.info(f'save called for {instance.id} ({instance.game.title})')
    if not db.has_collection('instances'):
        logger.info('creating collection instances')
        db.create_collection('instances')
    col = db.collection('instances')
    txn_db = db.begin_transaction(read=col.name, write=col.name)
    txn_col = txn_db.collection('instances')
    txn_col.insert(json.dumps(instance, cls=MarugotoEncoder))
    txn_db.commit_transaction()
示例#6
0
def remove_token(db: StandardDatabase, email, token):
    """
    remove a token from player
    :param db: connection
    :param email: mail
    :param token: jwt token
    """
    if not db.has_collection('players'):
        logger.error('cannot remove token if players do not exist')
        raise PlayerStateException('players collection does not exist')
    col = db.collection('players')
    db_player = next(col.find({'mail': email}), None)
    if not db_player:
        logger.error(f'could not resolve player {email}')
        raise PlayerStateException(f'player {email} does not exist')
    db_player['tokens'].remove(token)
    col.update(db_player)
示例#7
0
def create(db: StandardDatabase, email, password, salt) -> Player:
    """
    create a new player
    :param db: connection
    :param email: mail
    :param password: plain text password
    :param salt: encryption salt
    :return: Player
    """
    if not db.has_collection('players'):
        logger.info('creating collection players')
        db.create_collection('players')
    player = Player(email, hash_password(password, salt))
    col = db.collection('players')
    col.insert({
        '_key': player.id.hex,
        'mail': player.email,
        'password': player.password,
        'tokens': []
    })
    return player
示例#8
0
def hosts(db: StandardDatabase, player: Player) -> [(datetime, str, str)]:
    """
    list all games hosted by a player as game master
    :param db: connection
    :param player: game master
    :return: list of (creation date, game instance name, game title)
    """
    logger.info(f'hosts requested for player {player.email}')
    if not db.has_collection('instances'):
        logger.warning('no collections for instances')
        raise InstanceStateException('no collections for instances')
    col = db.collection('instances')
    result = []
    for db_game_instance in col.all():
        game_instance = json.loads(json.dumps(db_game_instance),
                                   cls=MarugotoDecoder)
        game_instance.game = game_read(db, game_instance.game.title)
        if game_instance.game_master and game_instance.game_master.id == player.id:
            result.append((game_instance.created_at, game_instance.name,
                           game_instance.game.title))
    return result
示例#9
0
def update(db: StandardDatabase,
           player: Player,
           salt,
           email: str = None,
           password: str = None,
           tokens: [str] = None) -> Player:
    """
    update attributes of existing player
    :param db: connection
    :param player: target
    :param salt: encryption salt
    :param email: new mail
    :param password: new password in plain text
    :param tokens: non-expired tokens for player
    :return: Player
    """
    if not db.has_collection('players'):
        logger.error('cannot resolve player if players do not exist')
        raise PlayerStateException('players collection does not exist')
    col = db.collection('players')
    db_player = next(col.find({'mail': player.email}), None)
    if not db_player:
        logger.error(f'cannot find player {player.email}')
        raise PlayerStateException(f'could not find player {player.email}')
    if email and password:
        player = Player(email, hash_password(password, salt))
    elif email:
        player = Player(email, db_player['password'])
    elif password:
        player = Player(db_player['mail'], hash_password(password, salt))
    else:
        player = Player(db_player['mail'], db_player['password'])
    player.id = UUID(db_player['_key'])
    col.update({
        '_key': player.id.hex,
        'mail': player.email,
        'password': player.password
    })
    return player
示例#10
0
def load(db: StandardDatabase, game_id: str) -> GameInstance:
    """
    load a game instance by id
    :param db: connection
    :param game_id: game instance id
    :return Game Instance
    """
    logger.info(f'load called for {game_id}')
    if not db.has_collection('instances'):
        logger.warning('no collections for instances')
        raise InstanceStateException('no collections for instances')
    col = db.collection('instances')
    db_game_instance = next(col.find({'_key': game_id}), None)
    if not db_game_instance:
        logger.warning(f'could not find game instance {game_id}')
        raise InstanceStateException(f'could not find game instance {game_id}')
    game_instance = json.loads(json.dumps(db_game_instance),
                               cls=MarugotoDecoder)
    game_instance.game = game_read(db, game_instance.game.title)
    for player_state in game_instance.player_states:
        if not isinstance(player_state.player, Player):
            player_state.player = player_read(db, player_state.player)
        player_state.game_instance = game_instance
    for npc_state in game_instance.npc_states:
        npc_state.game_instance = game_instance
        if isinstance(npc_state.dialog.start, UUID):
            npc_state.dialog = next(
                iter([
                    d for d in [n.dialog for n in game_instance.game.npcs]
                    if d.id == npc_state.dialog.id
                ]), None)
        if not npc_state.dialog:
            logger.warning(
                f'could not find dialog for NPC {npc_state.first_name} {npc_state.last_name} '
                f'in {game_instance.game.title} ({game_instance.id})')
            raise InstanceStateException(
                f'could not find dialog for NPC {npc_state.first_name} {npc_state.last_name} '
                f'in {game_instance.game.title} ({game_instance.id})')
    return game_instance
示例#11
0
def authenticate(db: StandardDatabase, email, password, salt):
    """
    validate player password
    :param db: connection
    :param email: mail
    :param password: plain text password
    :param salt: encryption salt
    :return: Player or None
    """
    if not db.has_collection('players'):
        logger.warning('cannot authenticate: no players defined')
        return None
    col = db.collection('players')
    db_player = next(col.find({'mail': email}), None)
    if not db_player:
        logger.warning(f'could not find player {email}')
        return None
    if verify_password(db_player['password'], hash_password(password, salt),
                       salt):
        player = Player(db_player['mail'], '')
        player.id = UUID(db_player['_key'])
        player.password = db_player['password']
        return player
示例#12
0
def saves(db: StandardDatabase, player: Player) -> [(datetime, str, str, str)]:
    """
    get all saves for a player
    :param db: connection
    :param player: target
    :return list of (creation date, game instance name, game title, pseudonym)
    """
    logger.info(f'saves requested for player {player.email}')
    if not db.has_collection('instances'):
        logger.warning('no collections for instances')
        raise InstanceStateException('no collections for instances')
    col = db.collection('instances')
    result = []
    for db_game_instance in col.all():
        game_instance = json.loads(json.dumps(db_game_instance),
                                   cls=MarugotoDecoder)
        game_instance.game = game_read(db, game_instance.game.title)
        for player_state in game_instance.player_states:
            if player_state.player.id == player.id:
                result.append(
                    (game_instance.created_at, game_instance.name,
                     game_instance.game.title,
                     f'{player_state.first_name} {player_state.last_name}'))
    return result
示例#13
0
def create_collection(db: StandardDatabase, name: str, unique_fields: Tuple,
                      _schema: Dict):
    if not db.has_collection(name):
        collection = db.create_collection(name=name, schema=_schema)
        collection.add_hash_index(fields=unique_fields, unique=True)
示例#14
0
def create(db: StandardDatabase, game: Game, creator=None):
    """
    save a game to the database
    :param db: connection
    :param game: target
    :param creator: creator of the game
    """
    logger.info(f'create called for {game.title}')
    if not db.has_collection('games'):
        logger.info(f'games collection does not exist, creating it.')
        db.create_collection('games')
    db_games = db.collection('games')
    if db_games.find({'key': game.title}):
        logger.warning(f'{game.title} already in metadata')
        raise GameStateException(f'{game.title} already in metadata')
    if db.has_graph(game.title):
        logger.warning(f'{game.title} already defined')
        raise GameStateException(f'{game.title} already defined')
    if not nx.is_directed_acyclic_graph(game.graph):
        logger.warning(f'{game.title} is not acyclic')
        raise GameStateException(f'{game.title} is not acyclic')
    if not game.start_is_set():
        logger.warning(f'{game.title} has no starting point')
        raise GameStateException(f'{game.title} has no starting point')
    db_game_graph = db.create_graph(f'game_{game.title}')
    if not db_game_graph.has_vertex_collection('waypoints'):
        logger.info(f'waypoints vertex collection does not exist, creating it.')
        db_game_graph.create_vertex_collection('waypoints')
    path = db_game_graph.create_edge_definition(
        edge_collection='path',
        from_vertex_collections=['waypoints'],
        to_vertex_collections=['waypoints']
    )
    # add all game nodes
    for waypoint_vertex in set(game.start.all_path_nodes()):
        logger.debug(f'inserting waypoint vertex {repr(waypoint_vertex)}')
        db_game_graph.insert_vertex('waypoints', json.dumps(waypoint_vertex, cls=MarugotoEncoder))

    # tasks as documents
    if not db.has_collection('tasks'):
        logger.info(f'tasks collection does not exist, creating it.')
        db.create_collection('tasks')
    tasks = db.collection('tasks')

    # dialogs metadata
    if not db.has_collection('dialogs'):
        logger.info(f'dialogs does not exist, creating it.')
        db.create_collection('dialogs')
    db_dialogs = db.collection('dialogs')

    # npc dialog metadata
    if not db.has_collection('npcs'):
        logger.info(f'npcs collection does not exist, creating it.')
        db.create_collection('npcs')
    db_npcs = db.collection('npcs')

    for npc in game.npcs:
        if db_npcs.find({'_key': f'{game.title}-{npc.first_name}-{npc.last_name}'}):
            logger.warning(f'dialog {game.title}-{npc.first_name}-{npc.last_name} already in metadata')
            raise GameStateException(f'dialog {game.title}-{npc.first_name}-{npc.last_name} already in metadata')
        logger.debug(f'inserting npc {game.title}-{npc.first_name}-{npc.last_name} with dialog {npc.dialog.id.hex}')
        db_npcs.insert({
            '_key': f'{game.title}-{npc.first_name}-{npc.last_name}',
            'game': f'game_{game.title}',
            'first_name': npc.first_name,
            'last_name': npc.last_name,
            'salutation': npc.salutation,
            'mail': npc.mail,
            'image': base64.encodebytes(npc.image) if npc.image else None,
            'dialog': npc.dialog.id.hex
        })

        if db_dialogs.find({'_key': npc.dialog.id.hex}):
            logger.warning(f'dialog {npc.dialog.id} already in metadata')
            raise GameStateException(f'dialog {npc.dialog.id} already in metadata')
        if db.has_graph(f'dialog_{npc.dialog.id.hex}'):
            logger.warning(f'dialog {npc.dialog.id} already defined')
            raise GameStateException(f'dialog {npc.dialog.id} already defined')
        if not nx.is_directed_acyclic_graph(npc.dialog.graph):
            logger.warning(f'dialog {npc.dialog.id} is not acyclic')
            raise GameStateException(f'dialog {npc.dialog.id} is not acyclic')
        if not npc.dialog.start_is_set():
            logger.warning(f'dialog {npc.dialog.id} has no starting point')
            raise GameStateException(f'dialog {npc.dialog.id} has no starting point')
        db_dialog_graph = db.create_graph(f'dialog_{npc.dialog.id.hex}')
        if not db_dialog_graph.has_vertex_collection('interactions'):
            logger.debug(f'interaction vertex collection does not exist, creating it.')
            db_dialog_graph.create_vertex_collection('interactions')
        conversation = db_dialog_graph.create_edge_definition(
            edge_collection='conversation',
            from_vertex_collections=['interactions'],
            to_vertex_collections=['interactions']
        )
        # add all dialog nodes
        for interaction_vertex in set(npc.dialog.start.all_path_nodes()):
            logger.debug(f'inserting interaction vertex {repr(interaction_vertex)}')
            db_dialog_graph.insert_vertex('interactions', json.dumps(interaction_vertex, cls=MarugotoEncoder))

        dia_visited = {}

        def dialog_traversal(s, i):
            """
            Walk through the game path from a starting source
            :param s: starting waypoint for traversal
            :param i: dict containing identified steps
            """
            for successor in npc.dialog.graph.successors(s):
                logger.debug(f'successor {repr(successor)} for {repr(s)}')
                if s not in i.keys():
                    logger.debug(f'{repr(s)} is new, adding it')
                    i[s] = []
                if successor not in i[s]:
                    if successor.task:
                        logger.debug(f'{repr(successor)} has {repr(successor.task)}, adding to tasks')
                        task_dump = json.loads(json.dumps(successor.task, cls=MarugotoEncoder))
                        task_dump['_key'] = successor.task.id.hex
                        task_dump['for'] = successor.id.hex
                        if not tasks.find(task_dump):
                            tasks.insert(json.dumps(task_dump))
                    logger.debug(f'inserting interaction edge {repr(s)} to {repr(successor)}')
                    conversation.insert({
                        '_key': f'{s.id.hex}-{successor.id.hex}',
                        '_from': f'interactions/{s.id.hex}',
                        '_to': f'interactions/{successor.id.hex}'
                    })
                    dia_visited[s].append(successor)
                    dialog_traversal(successor, i)

        logger.debug(f'traversing dialog graph for {npc.first_name} {npc.last_name}')
        dialog_traversal(npc.dialog.start, dia_visited)
        logger.debug(f'inserting dialog {npc.dialog.id.hex}')
        db_dialogs.insert({
            '_key': npc.dialog.id.hex,
            'start': npc.dialog.start.id.hex
        })

    wp_visited = {}

    def game_traversal(s, i):
        """
        Walk through the game path from a starting source
        :param s: starting waypoint for traversal
        :param i: dict containing identified steps
        """
        for successor in game.graph.successors(s):
            logger.debug(f'successor {repr(successor)} for {repr(s)}')
            if s not in i.keys():
                logger.debug(f'{repr(s)} is new, adding it.')
                i[s] = []
            if successor not in i[s]:
                for task in successor.tasks:
                    logger.debug(f'{repr(successor)} has {repr(task)}, adding to tasks')
                    task_dump = json.loads(json.dumps(task, cls=MarugotoEncoder))
                    task_dump['_key'] = task.id.hex
                    task_dump['for'] = successor.id.hex
                    if not tasks.find(task_dump):
                        tasks.insert(json.dumps(task_dump))
                logger.debug(f'waypoint interaction edge {repr(s)} to {repr(successor)}')
                weight = game.graph.edges[s, successor]['weight'] if 'weight' in game.graph.edges[s, successor] and game.graph.edges[s, successor]['weight'] else None
                path.insert({
                    '_key': f'{s.id.hex}-{successor.id.hex}',
                    '_from': f'waypoints/{s.id.hex}',
                    '_to': f'waypoints/{successor.id.hex}',
                    'weight': weight
                })
                wp_visited[s].append(successor)
                game_traversal(successor, i)

    logger.debug(f'traversing game graph for {game.title}')
    game_traversal(game.start, wp_visited)
    logger.debug(f'inserting game {game.title}')
    db_games.insert({
        '_key': game.title,
        'start': game.start.id.hex,
        'image': base64.encodebytes(game.image) if game.image else None,
        'creator': creator
    })