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> <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 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)
left a comment
Copy Link
Copy Source
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>
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.
### 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> <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>
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.
### 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> <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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Add movie year categorization and admin movie management, enabling year-specific movie listings, stats, and bulk deletion.
Cursor Agent can help with this pull request. Just
@cursorin comments and I'll start working on changes in this branch.Learn more about Cursor Agents
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.
@@ -91,0 +250,4 @@});// Initial loadawait refreshYears();Stored XSS via unescaped movie data in innerHTML
Movie properties like
m.title,m.category, andm.imdb_idare inserted directly into the DOM viainnerHTMLwithout HTML escaping. Thecategoryfield 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.Map recreated inside loop causing unnecessary overhead
The
movieByImdbIdMap is created inside theusers.map()loop, rebuilding it fromallMoviesfor every user iteration. SinceallMoviesdoesn'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.