Fix shared decks route ordering, fix corrupted middot character
- Move GET /shared before GET /{deck_id} so FastAPI matches it correctly
(was being caught by /{deck_id} and failing int parse on "shared")
- Remove duplicate /shared endpoint left after move
- Fix corrupted middot character (U+FFFD) in My Decks card count display
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
18d0a74348
commit
be7efd73d2
2 changed files with 62 additions and 68 deletions
|
|
@ -147,6 +147,67 @@ def list_trashed_decks(
|
|||
).order_by(FlashcardDeck.deleted_at.desc()).all()
|
||||
|
||||
|
||||
@router.get("/shared")
|
||||
def list_shared_decks(
|
||||
limit: int = Query(20, le=100),
|
||||
offset: int = Query(0),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""List shared decks sorted by average rating (desc), with pagination."""
|
||||
from sqlalchemy import func
|
||||
|
||||
avg_rating = db.query(
|
||||
FlashcardDeckRating.deck_id,
|
||||
func.avg(FlashcardDeckRating.rating).label("avg_rating"),
|
||||
func.count(FlashcardDeckRating.id).label("rating_count"),
|
||||
).group_by(FlashcardDeckRating.deck_id).subquery()
|
||||
|
||||
my_ratings = {}
|
||||
for r in db.query(FlashcardDeckRating).filter(FlashcardDeckRating.user_id == current_user.id).all():
|
||||
my_ratings[r.deck_id] = r.rating
|
||||
|
||||
total = db.query(FlashcardDeck).filter(
|
||||
FlashcardDeck.is_shared == 1,
|
||||
FlashcardDeck.deleted_at.is_(None),
|
||||
).count()
|
||||
|
||||
decks_with_rating = db.query(
|
||||
FlashcardDeck,
|
||||
func.coalesce(avg_rating.c.avg_rating, 0).label("avg_rating"),
|
||||
func.coalesce(avg_rating.c.rating_count, 0).label("rating_count"),
|
||||
).outerjoin(
|
||||
avg_rating, FlashcardDeck.id == avg_rating.c.deck_id
|
||||
).filter(
|
||||
FlashcardDeck.is_shared == 1,
|
||||
FlashcardDeck.deleted_at.is_(None),
|
||||
).order_by(
|
||||
func.coalesce(avg_rating.c.avg_rating, 0).desc(),
|
||||
FlashcardDeck.created_at.desc(),
|
||||
).offset(offset).limit(limit).all()
|
||||
|
||||
user_cache: dict[int, str] = {}
|
||||
result = []
|
||||
for deck, avg_r, r_count in decks_with_rating:
|
||||
if deck.user_id not in user_cache:
|
||||
owner = db.query(User).filter(User.id == deck.user_id).first()
|
||||
user_cache[deck.user_id] = owner.name if owner else "Unknown"
|
||||
result.append({
|
||||
"id": deck.id,
|
||||
"title": deck.title,
|
||||
"card_count": deck.card_count,
|
||||
"user_id": deck.user_id,
|
||||
"owner_name": user_cache[deck.user_id],
|
||||
"is_shared": deck.is_shared,
|
||||
"created_at": deck.created_at,
|
||||
"avg_rating": round(float(avg_r), 1),
|
||||
"rating_count": int(r_count),
|
||||
"my_rating": my_ratings.get(deck.id),
|
||||
})
|
||||
|
||||
return {"total": total, "decks": result}
|
||||
|
||||
|
||||
@router.get("/{deck_id}", response_model=FlashcardDeckDetail)
|
||||
def get_flashcard_deck(
|
||||
deck_id: int,
|
||||
|
|
@ -224,73 +285,6 @@ def admin_unshare_deck(
|
|||
db.commit()
|
||||
|
||||
|
||||
@router.get("/shared")
|
||||
def list_shared_decks(
|
||||
limit: int = Query(20, le=100),
|
||||
offset: int = Query(0),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""List shared decks sorted by average rating (desc), with pagination."""
|
||||
from sqlalchemy import func
|
||||
|
||||
# Subquery for average rating
|
||||
avg_rating = db.query(
|
||||
FlashcardDeckRating.deck_id,
|
||||
func.avg(FlashcardDeckRating.rating).label("avg_rating"),
|
||||
func.count(FlashcardDeckRating.id).label("rating_count"),
|
||||
).group_by(FlashcardDeckRating.deck_id).subquery()
|
||||
|
||||
# Current user's rating per deck
|
||||
my_ratings = {}
|
||||
if current_user:
|
||||
for r in db.query(FlashcardDeckRating).filter(FlashcardDeckRating.user_id == current_user.id).all():
|
||||
my_ratings[r.deck_id] = r.rating
|
||||
|
||||
query = db.query(FlashcardDeck).filter(
|
||||
FlashcardDeck.is_shared == 1,
|
||||
FlashcardDeck.deleted_at.is_(None),
|
||||
)
|
||||
total = query.count()
|
||||
|
||||
# Join with avg rating for sorting
|
||||
decks_with_rating = db.query(
|
||||
FlashcardDeck,
|
||||
func.coalesce(avg_rating.c.avg_rating, 0).label("avg_rating"),
|
||||
func.coalesce(avg_rating.c.rating_count, 0).label("rating_count"),
|
||||
).outerjoin(
|
||||
avg_rating, FlashcardDeck.id == avg_rating.c.deck_id
|
||||
).filter(
|
||||
FlashcardDeck.is_shared == 1,
|
||||
FlashcardDeck.deleted_at.is_(None),
|
||||
).order_by(
|
||||
func.coalesce(avg_rating.c.avg_rating, 0).desc(),
|
||||
FlashcardDeck.created_at.desc(),
|
||||
).offset(offset).limit(limit).all()
|
||||
|
||||
# Get owner names
|
||||
user_cache: dict[int, str] = {}
|
||||
result = []
|
||||
for deck, avg_r, r_count in decks_with_rating:
|
||||
if deck.user_id not in user_cache:
|
||||
owner = db.query(User).filter(User.id == deck.user_id).first()
|
||||
user_cache[deck.user_id] = owner.name if owner else "Unknown"
|
||||
result.append({
|
||||
"id": deck.id,
|
||||
"title": deck.title,
|
||||
"card_count": deck.card_count,
|
||||
"user_id": deck.user_id,
|
||||
"owner_name": user_cache[deck.user_id],
|
||||
"is_shared": deck.is_shared,
|
||||
"created_at": deck.created_at,
|
||||
"avg_rating": round(float(avg_r), 1),
|
||||
"rating_count": int(r_count),
|
||||
"my_rating": my_ratings.get(deck.id),
|
||||
})
|
||||
|
||||
return {"total": total, "decks": result}
|
||||
|
||||
|
||||
@router.post("/{deck_id}/rate")
|
||||
def rate_deck(
|
||||
deck_id: int,
|
||||
|
|
|
|||
|
|
@ -191,7 +191,7 @@ export default function FlashcardsPage() {
|
|||
<div key={deck.id} className="card" style={{ padding: 20 }}>
|
||||
<h3 style={{ fontSize: '1rem', marginBottom: 6 }}>{deck.title}</h3>
|
||||
<p style={{ color: 'var(--text-muted)', fontSize: '0.85rem', marginBottom: 4 }}>
|
||||
{deck.card_count} cards <EFBFBD><EFBFBD> {new Date(deck.created_at).toLocaleDateString()}
|
||||
{deck.card_count} cards · {new Date(deck.created_at).toLocaleDateString()}
|
||||
</p>
|
||||
{deck.is_shared ? (
|
||||
<span style={{ fontSize: '0.72rem', padding: '2px 7px', borderRadius: 10, background: '#dbeafe', color: '#1d4ed8', fontWeight: 600 }}>Shared</span>
|
||||
|
|
|
|||
Loading…
Reference in a new issue