p = 'backend/app/routers/articles.py'
s = open(p).read()
old = s[s.index('@router.get("/{article_id}/cards")'):s.index('@router.patch("/{article_id}")')]
new = '''@router.get("/{article_id}/decks")
def article_decks(
    article_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user),
):
    """The card decks that belong with this article.

    Decks, not cards. A deck is the unit a learner studies and the unit the
    bank files: this used to list every individual card tied to the article,
    which turned the foot of a reading page into a dump of fronts and backs
    nobody came for. The user: "You don't come to an article and start listing
    every card."

    Related through the shared category, which is how a deck is filed —
    generated decks inherit the article's category and a manual one has to be
    given it. A deck generated from this article is included whatever its
    category, because provenance is a stronger claim than filing.
    """
    article = db.get(Article, article_id)
    if not article:
        raise HTTPException(404, "Article not found")
    if article.status != "published" and not current_user.is_moderator:
        raise HTTPException(404, "Article not found")

    matches = [FlashcardDeck.source_article_id == article_id]
    if article.category_id:
        matches.append(FlashcardDeck.category_id == article.category_id)
    decks = db.query(FlashcardDeck).filter(
        FlashcardDeck.deleted_at.is_(None), or_(*matches)).order_by(FlashcardDeck.title).all()

    return [{
        "deck_id": deck.id,
        "title": deck.title,
        "card_count": deck.card_count or 0,
        # Why it is here: made from this article, or filed with it.
        "from_this_article": deck.source_article_id == article_id,
    } for deck in decks
        if current_user.is_moderator or deck.is_shared or deck.user_id == current_user.id]


'''
s = s.replace(old, new)
open(p, 'w').write(s)
print('backend done')
