diff --git a/main.py b/main.py index 473ffb6..012edf5 100644 --- a/main.py +++ b/main.py @@ -187,4 +187,16 @@ async def rate_movie(request: Request, trakt_id: int, body: RateBody): return {"success": True} +@app.delete("/api/rate/{trakt_id}") +async def remove_rating(request: Request, trakt_id: int): + token = request.session.get("access_token") + if not token: + raise HTTPException(401, "Not authenticated") + + trakt = TraktClient(TRAKT_CLIENT_ID, token) + await trakt.remove_rating(trakt_id) + cache_del(f"movies_{token[:16]}") + return {"success": True} + + app.mount("/static", StaticFiles(directory="static"), name="static") diff --git a/static/app.js b/static/app.js index d7db2e8..13375b7 100644 --- a/static/app.js +++ b/static/app.js @@ -160,9 +160,11 @@ function buildRow(movie) { ).join(''); const skipLabel = isSkipped ? 'Remettre' : 'Passer'; - const skipBtn = ``; + const skipBtn = ``; + const removeBtn = movie.current_rating !== null + ? `` : ''; - const ratingEl = `
${btns}${skipBtn}
`; + const ratingEl = `
${btns}${removeBtn}${skipBtn}
`; row.innerHTML = poster + info + synopsis + ratingEl; @@ -196,6 +198,12 @@ function buildRow(movie) { }); }); + // Remove rating button + const removeBtnEl = container.querySelector('.remove-btn'); + if (removeBtnEl) { + removeBtnEl.addEventListener('click', () => removeRating(movie, row)); + } + // Skip button container.querySelector('.skip-btn').addEventListener('click', () => { if (isSkipped) { @@ -211,6 +219,20 @@ function buildRow(movie) { return row; } +/* ── Remove rating ──────────────────────────────────────── */ +async function removeRating(movie, row) { + row.style.pointerEvents = 'none'; + try { + const r = await fetch(`/api/rate/${movie.trakt_id}`, { method: 'DELETE' }); + if (!r.ok) throw new Error('Failed'); + showToast(`${movie.title_fr} — note supprimée`); + animateOut(row); + } catch { + row.style.pointerEvents = ''; + showToast('Erreur lors de la suppression', true); + } +} + /* ── Rate ───────────────────────────────────────────────── */ async function rateMovie(movie, rating, row) { row.style.pointerEvents = 'none'; diff --git a/static/style.css b/static/style.css index 600859d..ba22e1c 100644 --- a/static/style.css +++ b/static/style.css @@ -316,6 +316,24 @@ main { } .r-btn:active { transform: scale(.92); } +.remove-btn { + margin-left: 6px; + width: 24px; + height: 24px; + border-radius: 5px; + border: 1px solid rgba(244,63,94,.25); + background: transparent; + color: #f87191; + font-size: .75rem; + cursor: pointer; + font-family: inherit; + transition: border-color .15s, background .15s; +} +.remove-btn:hover { + border-color: rgba(244,63,94,.5); + background: rgba(244,63,94,.1); +} + .skip-btn { margin-left: 6px; padding: 0 9px; diff --git a/trakt.py b/trakt.py index 83a412d..68f4e4b 100644 --- a/trakt.py +++ b/trakt.py @@ -23,6 +23,16 @@ class TraktClient: ratings_r.raise_for_status() return watched_r.json(), ratings_r.json() + async def remove_rating(self, trakt_id: int): + async with httpx.AsyncClient(timeout=30) as client: + r = await client.post( + f"{self.BASE_URL}/sync/ratings/remove", + headers=self.headers, + json={"movies": [{"ids": {"trakt": trakt_id}}]}, + ) + r.raise_for_status() + return r.json() + async def rate_movie(self, trakt_id: int, rating: int): async with httpx.AsyncClient(timeout=30) as client: r = await client.post(