Movie categorization and stats #4

Merged
quoije merged 1 commits from cursor/movie-categorization-and-stats-4456 into 2026 2025-12-20 17:16:18 -05:00
quoije commented 2025-12-20 17:14:59 -05:00 (Migrated from github.com)

Add movie year categorization and admin movie management, enabling year-specific movie listings, stats, and bulk deletion.


Open in Cursor Open in Web


Note

Introduce year-based movie categorization with API endpoints and admin UI for filtering/listing and bulk deletion; make user stats year-aware.

  • Backend/API:
    • Year segmentation: Add optional year filter to GET /api/movies via query year; new GET /api/movies/years to list distinct years.
    • Admin add: POST /api/movies/add now requires year; validates via parseOscarYear and stores on movie.
    • Admin bulk delete: New DELETE /api/movies/delete accepts { ids[] | imdb_ids[] }, removes movies and cleans User.watchedMovies accordingly.
    • User stats: GET /api/users/stats accepts optional year, computes stats against filtered movies and returns year in response; lookup optimized via map.
  • Data model:
    • Add year: Number (indexed) to models/Movie.js.
  • Frontend/Admin UI (public/control.html, public/js/app_control.js):
    • Add Oscar year field to add form; populate defaults from GET /api/movies/years.
    • New management section: select year, list movies, select-all, show selection count, and trigger bulk deletion via DELETE /api/movies/delete.
    • Refresh flows after add/delete and improved validation/UX messages.

Written by Cursor Bugbot for commit eeb337fe08. This will update automatically on new commits. Configure here.

Add movie year categorization and admin movie management, enabling year-specific movie listings, stats, and bulk deletion. --- <a href="https://cursor.com/background-agent?bcId=bc-4128bf23-8a6b-4628-9080-c75b38b7b034"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-cursor-light.svg"><img alt="Open in Cursor" src="https://cursor.com/open-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?id=bc-4128bf23-8a6b-4628-9080-c75b38b7b034"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/open-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/open-in-web-light.svg"><img alt="Open in Web" src="https://cursor.com/open-in-web.svg"></picture></a> <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Introduce year-based movie categorization with API endpoints and admin UI for filtering/listing and bulk deletion; make user stats year-aware. > > - **Backend/API**: > - **Year segmentation**: Add optional `year` filter to `GET /api/movies` via query `year`; new `GET /api/movies/years` to list distinct years. > - **Admin add**: `POST /api/movies/add` now requires `year`; validates via `parseOscarYear` and stores on movie. > - **Admin bulk delete**: New `DELETE /api/movies/delete` accepts `{ ids[] | imdb_ids[] }`, removes movies and cleans `User.watchedMovies` accordingly. > - **User stats**: `GET /api/users/stats` accepts optional `year`, computes stats against filtered movies and returns `year` in response; lookup optimized via map. > - **Data model**: > - Add `year: Number` (indexed) to `models/Movie.js`. > - **Frontend/Admin UI (`public/control.html`, `public/js/app_control.js`)**: > - Add `Oscar year` field to add form; populate defaults from `GET /api/movies/years`. > - New management section: select year, list movies, select-all, show selection count, and trigger bulk deletion via `DELETE /api/movies/delete`. > - Refresh flows after add/delete and improved validation/UX messages. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit eeb337fe086a3eb3d454b427f941763a42b6f30d. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
cursor[bot] commented 2025-12-20 17:15:00 -05:00 (Migrated from github.com)

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

Cursor Agent can help with this pull request. Just `@cursor` in comments and I'll start working on changes in this branch. <sub>[Learn more](https://cursor.com/docs/background-agent/web-and-mobile) about Cursor Agents</sub>
cursor[bot] (Migrated from github.com) reviewed 2025-12-20 17:21:08 -05:00
cursor[bot] (Migrated from github.com) left a comment

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on January 10

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

### This is the final PR Bugbot will review for you during this billing cycle Your free Bugbot reviews will reset on January 10 <details> <summary>Details</summary> You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle. To receive Bugbot reviews on all of your PRs, visit the [Cursor dashboard](https://www.cursor.com/dashboard?tab=bugbot) to activate Pro and start your 14-day free trial. </details>
@@ -91,0 +250,4 @@
});
// Initial load
await refreshYears();
cursor[bot] (Migrated from github.com) commented 2025-12-20 17:21:08 -05:00

Stored XSS via unescaped movie data in innerHTML

Movie properties like m.title, m.category, and m.imdb_id are inserted directly into the DOM via innerHTML without HTML escaping. The category field in particular is admin-provided and stored in the database with no sanitization. If malicious HTML/JavaScript is stored in these fields, it would execute when any admin views the movie management page. While this is an admin-only page reducing the attack surface, it still represents a stored XSS vulnerability.

Fix in Cursor Fix in Web

### Stored XSS via unescaped movie data in innerHTML <!-- **Low Severity** --> <!-- DESCRIPTION START --> Movie properties like `m.title`, `m.category`, and `m.imdb_id` are inserted directly into the DOM via `innerHTML` without HTML escaping. The `category` field in particular is admin-provided and stored in the database with no sanitization. If malicious HTML/JavaScript is stored in these fields, it would execute when any admin views the movie management page. While this is an admin-only page reducing the attack surface, it still represents a stored XSS vulnerability. <!-- DESCRIPTION END --> <!-- LOCATIONS START public/js/app_control.js#L180-L191 LOCATIONS END --> <a href="https://cursor.com/open?data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJ1Z2JvdC12MiJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9JTl9DVVJTT1IiLCJkYXRhIjp7InJlZGlzS2V5IjoiYnVnYm90OjgzZTcxOWE5LTMzYzQtNDE0NC04MGY3LWMxNDBlNTUwZDU4YSIsImVuY3J5cHRpb25LZXkiOiJkYl80aVZjUFRhb0tlWExoblByRDREUGJJMy0tZVpua0dZSmFHTmF1Zl9FIiwiYnJhbmNoIjoiY3Vyc29yL21vdmllLWNhdGVnb3JpemF0aW9uLWFuZC1zdGF0cy00NDU2In0sImlhdCI6MTc2NjI2OTI2OCwiZXhwIjoxNzY4ODYxMjY4fQ.euJZnwunDd4Ux5IT_oHsJ41OYGtB_plw0c9XjTukaGYLxyL9N3LFux3rJiNTARuo4rUxB3mIYYnWnCIcA4CL0LE7UJq3yoxhHpbznFK9I8qgKQWbIiPH3psbs-29OlFQbch8R5LjYdd6v1jlxNtWA6PeMt239HF8TNPgXpfPBXEj8DW6tZB_zhH9uC7uiwq5Iikdy03sKU5bc-efl1uBqkUmiNUnsW5qgCIRR6vvk4LYeURF9fbKMj1mbLVTRWs23HTHZcv19x4fim8o61tqbLzLkRNmc4p6gvqN_ZRFHzbYBR4jtVOi_ASyYSZZWuCOBZ6NUY5AtnoO5grFmfjUqA" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/fix-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/fix-in-cursor-light.svg"><img alt="Fix in Cursor" src="https://cursor.com/fix-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJ1Z2JvdC12MiJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9JTl9XRUIiLCJkYXRhIjp7InJlZGlzS2V5IjoiYnVnYm90OjgzZTcxOWE5LTMzYzQtNDE0NC04MGY3LWMxNDBlNTUwZDU4YSIsImVuY3J5cHRpb25LZXkiOiJkYl80aVZjUFRhb0tlWExoblByRDREUGJJMy0tZVpua0dZSmFHTmF1Zl9FIiwiYnJhbmNoIjoiY3Vyc29yL21vdmllLWNhdGVnb3JpemF0aW9uLWFuZC1zdGF0cy00NDU2IiwicmVwb093bmVyIjoicXVvaWplIiwicmVwb05hbWUiOiJvc2NhcnMtcG9vbCIsInByTnVtYmVyIjo0LCJjb21taXRTaGEiOiJlZWIzMzdmZTA4NmEzZWIzZDQ1NGI0MjdmOTQxNzYzYTQyYjZmMzBkIiwicHJvdmlkZXIiOiJnaXRodWIifSwiaWF0IjoxNzY2MjY5MjY4LCJleHAiOjE3Njg4NjEyNjh9.x-dHcYZ_hrjOaFrDLtxngKjLG1YfENS4hyTqIlOptEnG98GxqVlS4hPrKTAAsMRe3iDsgFCGu7RFNHiLW2sf1V-ZOeNkV2QqirsPZDuoKJxZuJlrfPAjTvex2YNxplcvlslMbw3wvx38t9w37Qg3ejVSP47gzl_4xkTCkOPlUpC_0qDIdy2ZfSTzYGcSFr3TWpdA2W_wQDDKPw2rDIb50OWSuOD6gpV9MFnH9xYv7GuuflwegUJ_7SYqK2nckYNjZ0U07zMhEOOZT8owAgbkJ9TZnPgjjiLemIbxIUYjpY4WesJbWFxf3taFJhC2mbhVP5PJniFMr1rH3wgnEKTriA" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/fix-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/fix-in-web-light.svg"><img alt="Fix in Web" src="https://cursor.com/fix-in-web.svg"></picture></a>
cursor[bot] (Migrated from github.com) commented 2025-12-20 17:21:08 -05:00

Map recreated inside loop causing unnecessary overhead

The movieByImdbId Map is created inside the users.map() loop, rebuilding it from allMovies for every user iteration. Since allMovies doesn't change between iterations, this Map should be constructed once before the loop. With many users and movies, this creates significant unnecessary overhead by repeatedly creating identical Map objects.

Fix in Cursor Fix in Web

### Map recreated inside loop causing unnecessary overhead <!-- **Medium Severity** --> <!-- DESCRIPTION START --> The `movieByImdbId` Map is created inside the `users.map()` loop, rebuilding it from `allMovies` for every user iteration. Since `allMovies` doesn't change between iterations, this Map should be constructed once before the loop. With many users and movies, this creates significant unnecessary overhead by repeatedly creating identical Map objects. <!-- DESCRIPTION END --> <!-- LOCATIONS START routes/users.js#L77-L78 LOCATIONS END --> <a href="https://cursor.com/open?data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJ1Z2JvdC12MiJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9JTl9DVVJTT1IiLCJkYXRhIjp7InJlZGlzS2V5IjoiYnVnYm90OjBiYzg4N2ZlLWYxN2QtNDA0Mi1hM2QwLTNjMWE2OWQ4YjllZiIsImVuY3J5cHRpb25LZXkiOiJ4VHBxX1NBdHVuQ3YxT05IMERyYzJlS0VQTkx6ZWxzNWxrM3JTOXJVSlJJIiwiYnJhbmNoIjoiY3Vyc29yL21vdmllLWNhdGVnb3JpemF0aW9uLWFuZC1zdGF0cy00NDU2In0sImlhdCI6MTc2NjI2OTI2OCwiZXhwIjoxNzY4ODYxMjY4fQ.Wy9CvJcbkem-1k5XiOvyLWpwOVgKuJhkAqCw2eVFKp62rWt-uKX7gmD4XYuJQAOmAGVE4FcKQXpYCtSzfhCdI2f12U7TT3juQL4Y6kXZlVbePWxExqB6-P3tBVARX-G4ND3aH_ZNPHN3TqSgRoHWVGfKgm4cIDEwRZK31v8-09AKCaUKnGPxWf31HhTHGan8-ewYYuaCzpdNnxTr3t0X-5c5CU-uKgY27dFDqbu7y5ODyoDPupHdkEkE3ITjhkI7wxH3IyiQTCsaWRS6thW089vqb_-LHoOYkQjCKLzcH3A1FB73f8h6CfcKbIDArbF_WakCJP1l5WXZaI1NGsUefA" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/fix-in-cursor-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/fix-in-cursor-light.svg"><img alt="Fix in Cursor" src="https://cursor.com/fix-in-cursor.svg"></picture></a>&nbsp;<a href="https://cursor.com/agents?data=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImJ1Z2JvdC12MiJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiQlVHQk9UX0ZJWF9JTl9XRUIiLCJkYXRhIjp7InJlZGlzS2V5IjoiYnVnYm90OjBiYzg4N2ZlLWYxN2QtNDA0Mi1hM2QwLTNjMWE2OWQ4YjllZiIsImVuY3J5cHRpb25LZXkiOiJ4VHBxX1NBdHVuQ3YxT05IMERyYzJlS0VQTkx6ZWxzNWxrM3JTOXJVSlJJIiwiYnJhbmNoIjoiY3Vyc29yL21vdmllLWNhdGVnb3JpemF0aW9uLWFuZC1zdGF0cy00NDU2IiwicmVwb093bmVyIjoicXVvaWplIiwicmVwb05hbWUiOiJvc2NhcnMtcG9vbCIsInByTnVtYmVyIjo0LCJjb21taXRTaGEiOiJlZWIzMzdmZTA4NmEzZWIzZDQ1NGI0MjdmOTQxNzYzYTQyYjZmMzBkIiwicHJvdmlkZXIiOiJnaXRodWIifSwiaWF0IjoxNzY2MjY5MjY4LCJleHAiOjE3Njg4NjEyNjh9.JL8ZsJN-vdCyD1g0wLopnt2CB5eYjC6DXaYnT59xnr4-sYYrBROBYqn3sAWEKlvfgTTxAoOHZ2tAPBpLDgVZsteMHghsIvsH0BbX6LkoGcScmivnteVBOdKXUapu_sCPiadS1gyxMZFISBoOma99MeCzitb37QqqwYO45KFB4FQOukYMsJNLI7wl_2SmNORTy23ln03AGLbjgOCp0YhuJRWhVKMLEQkP5QSe7NgXuW536BqeaJ7I1DGNrE38mEeMQRCMVzKsvXBgFOB9qiwHMSHOI6f1cTlTTaCbHHu8HV7iKvdOa3c9PlW_USGCkunvsOoMfDYpawy0B0_YQxch2Q" target="_blank" rel="noopener noreferrer"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/fix-in-web-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/fix-in-web-light.svg"><img alt="Fix in Web" src="https://cursor.com/fix-in-web.svg"></picture></a>
Sign in to join this conversation.