def create_player( token: UUID4, data: schema.UserRegistrationIn, session: db.Session = Depends(get_session), _=Depends(anonymous_required), ): """Create a new player using the token obtained by requesting an invite. Will fail if requested by an authenticated user. """ invite = session.query(Invite).filter(Invite.uuid == token).first() if invite is None: raise NotFoundException( detail="Token not found. Please request a new invite.") user = create_user( session, invite.email, data.password, username=data.username, description=data.description, newsletter_opt_in=data.newsletter_opt_in, ) session.delete(invite) session.commit() access_token = access_token_for_user(user) return {"access_token": access_token, "token_type": "bearer", "user": user}
def test_list_snapshots_bad_id(client: TestClient, session: db.Session, user1): """Not found error thrown when viewing non-existent deck""" deck = create_deck_for_user(session, user1) deleted_id = deck.id session.delete(deck) session.commit() response = client.get(f"/v2/decks/{deleted_id}/snapshots") assert response.status_code == status.HTTP_404_NOT_FOUND
def test_delete_deck_bad_deck(client: TestClient, session: db.Session, user_token): """Must disallow access to deck IDs that don't exist""" user, token = user_token deck = create_deck_for_user(session, user) bad_id = deck.id session.delete(deck) session.commit() response = client.delete(f"/v2/decks/{bad_id}", headers={"Authorization": f"Bearer {token}"}) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_put_deck_bad_id(client: TestClient, session: db.Session, user_token): """Must not allow uploading a deck with a bad ID""" # Create a deck so that we can ensure no accidental ID collisions user, token = user_token deck = create_deck_for_user(session, user) bad_id = deck.id session.delete(deck) session.commit() valid_deck = _valid_deck_dict(session) valid_deck["id"] = bad_id response = client.put("/v2/decks", json=valid_deck, headers={"Authorization": f"Bearer {token}"}) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_post_snapshot_bad_deck_id(client: TestClient, session: db.Session, user_token): """Must not allow creating a snapshot for a bad deck ID""" # Create a deck so that we can ensure no accidental ID collisions user, token = user_token deck = create_deck_for_user(session, user) bad_id = deck.id session.delete(deck) session.commit() response = client.post( f"/v2/decks/{bad_id}/snapshot", headers={"Authorization": f"Bearer {token}"}, ) assert response.status_code == status.HTTP_403_FORBIDDEN
def test_get_deck_no_record(client: TestClient, session: db.Session, user1): """Trying to fetch an ID that no longer exists must fail correctly""" deck = create_deck_for_user(session, user1) deleted_id = deck.id session.delete(deck) session.commit() token = create_access_token( data={"sub": user1.badge}, expires_delta=timedelta(minutes=15), ) response = client.get( f"/v2/decks/{deleted_id}", headers={"Authorization": f"Bearer {token}"} ) assert response.status_code == status.HTTP_404_NOT_FOUND
def test_edit_snapshot_bad_id(client: TestClient, session: db.Session, user1, deck1): """Not found error thrown when viewing non-existent ID""" snapshot = create_snapshot_for_deck(session, user1, deck1) deleted_id = snapshot.id session.delete(snapshot) session.commit() token = create_access_token( data={"sub": user1.badge}, expires_delta=timedelta(minutes=15), ) response = client.patch( f"/v2/decks/snapshots/{deleted_id}", headers={"Authorization": f"Bearer {token}"}, json={"title": "New title"}, ) assert response.status_code == status.HTTP_404_NOT_FOUND, response.json()
def delete_deck( deck_id: int, session: db.Session = Depends(get_session), current_user: "******" = Depends(login_required), ): """Delete a deck. When requested for a source deck: * If there are no snapshots, the deck is truly deleted (unrecoverable). Intended use-case is for decks that get auto-saved, but not completed. * For decks with snapshots, it's a soft deletion that can potentially be recovered from (no user-facing support for recovering deleted decks currently planned, though). Deleted decks and their snapshots will be removed from all listings (including the stream). When requested for a snapshot, it's a soft deletion and the snapshot will no longer show up in any listings (including the stream). """ deck: Deck = session.query(Deck).options(db.joinedload("source")).get(deck_id) if not deck or deck.user_id != current_user.id: raise NoUserAccessException(detail="You cannot delete a deck you do not own.") if deck.is_legacy: raise APIException(detail="You cannot delete legacy decks.") success_response = Response(status_code=status.HTTP_204_NO_CONTENT) # If the deck was previously deleted, just claim success and call it good if deck.is_deleted: return success_response # Check if we have any snapshots for source decks, and just delete that sucker for real if not if ( not deck.is_snapshot and session.query(Deck).filter(Deck.source_id == deck.id).count() == 0 ): session.query(DeckCard).filter(DeckCard.deck_id == deck.id).delete( synchronize_session=False ) session.query(DeckDie).filter(DeckDie.deck_id == deck_id).delete( synchronize_session=False ) session.query(DeckSelectedCard).filter( DeckSelectedCard.deck_id == deck_id ).delete(synchronize_session=False) session.query(Deck).filter(Deck.id == deck_id).delete(synchronize_session=False) session.commit() return success_response # Otherwise, we're looking at a snapshot or a source deck that has snapshots deck.is_deleted = True # For snapshots, we need to remove only the Stream entries for that snapshot (leave the rest # of the source deck's snapshots alone). if deck.is_snapshot and deck.is_public: # Check to see if we have a Stream entry that needs updating stream_entry: Stream = ( session.query(Stream) .filter( Stream.source_entity_id == deck.source.entity_id, Stream.entity_type == "deck", Stream.entity_id == deck.entity_id, ) .first() ) if stream_entry: # We have a stream entry pointed to this snapshot, so check if we have an older snapshot # that we can swap in previous_snapshot: Deck = ( session.query(Deck) .filter( Deck.source_id == deck.source_id, Deck.created < deck.created, Deck.is_deleted.is_(False), ) .order_by(Deck.created.desc()) .first() ) if previous_snapshot: stream_entry.entity_id = previous_snapshot.entity_id stream_entry.posted = previous_snapshot.created else: # Otherwise, just delete the stream entry because this deck no longer has any public # snapshots session.delete(stream_entry) elif not deck.is_snapshot: # If we're not deleting a snapshot, then we need to completely clear out the Stream entry session.query(Stream).filter( Stream.source_entity_id == deck.entity_id, Stream.entity_type == "deck" ).delete(synchronize_session=False) # And mark all snapshots as deleted session.query(Deck).filter( Deck.source_id == deck.id, Deck.is_snapshot.is_(True), Deck.is_deleted.is_(False), ).update({"is_deleted": True}, synchronize_session=False) # Commit any pending changes, and return success session.commit() return success_response