Skip to content

Commit c3ef7f4

Browse files
authored
Merge pull request #17 from helliott20/feature/v1.3-collections-and-rules-v2
v1.3.0 — Collections, Rules Engine v2, API Key Auth
2 parents 3da293d + 7cc4db4 commit c3ef7f4

62 files changed

Lines changed: 7815 additions & 1346 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 47 additions & 182 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,40 @@
99
</p>
1010

1111
<p align="center">
12-
<a href="#features">Features</a> •
13-
<a href="#installation">Installation</a> •
14-
<a href="#configuration">Configuration</a> •
15-
<a href="#usage">Usage</a> •
16-
<a href="#screenshots">Screenshots</a>
12+
<a href="https://hub.docker.com/r/helliott20/prunerr"><img src="https://img.shields.io/docker/pulls/helliott20/prunerr?style=flat-square&color=0db7ed" alt="Docker Pulls"></a>
13+
<img src="https://img.shields.io/github/license/helliott20/prunerr?style=flat-square" alt="License">
14+
<a href="https://github.com/helliott20/prunerr/releases"><img src="https://img.shields.io/github/v/release/helliott20/prunerr?style=flat-square&color=brightgreen" alt="Release"></a>
15+
<a href="https://forums.unraid.net/topic/196929-support-prunerr-media-library-cleanup-tool/"><img src="https://img.shields.io/badge/Unraid-Community%20App-orange?style=flat-square" alt="Unraid"></a>
1716
</p>
1817

1918
<p align="center">
20-
<img src="https://img.shields.io/docker/pulls/helliott20/prunerr?style=flat-square" alt="Docker Pulls">
21-
<img src="https://img.shields.io/github/license/helliott20/prunerr?style=flat-square" alt="License">
22-
<img src="https://img.shields.io/github/v/release/helliott20/prunerr?style=flat-square" alt="Release">
19+
<a href="https://github.com/helliott20/prunerr/wiki">Wiki</a> &bull;
20+
<a href="https://github.com/helliott20/prunerr/wiki/Installation">Install</a> &bull;
21+
<a href="https://github.com/helliott20/prunerr/wiki/API-Reference">API</a> &bull;
22+
<a href="https://forums.unraid.net/topic/196929-support-prunerr-media-library-cleanup-tool/">Unraid Forum</a>
2323
</p>
2424

2525
---
2626

27-
## What is Prunerr?
27+
If you run a Plex server, you know the pain. Your library keeps growing, nobody watches half of it, and you're constantly running out of disk space. Prunerr sits between your Plex server and your *arr apps and figures out what's worth keeping.
2828

29-
Prunerr is a media library management tool that helps you automatically identify and clean up unwatched or stale content from your Plex library. It integrates with Sonarr, Radarr, Tautulli, and Overseerr to make intelligent decisions about what to keep and what to remove.
30-
31-
Stop manually hunting through your library for content nobody watches. Let Prunerr handle it.
29+
You set up rules like "delete movies nobody's watched in 6 months that are over 20GB" and Prunerr handles the rest. Everything goes through a deletion queue first, so nothing gets removed without you knowing about it.
3230

3331
## Features
3432

35-
- **Smart Rules Engine** - Create flexible rules based on watch status, age, file size, and more
36-
- **Plex Integration** - Syncs with your Plex library to track what's been watched
37-
- **Tautulli Support** - Deep watch history analysis for accurate cleanup decisions
38-
- **Sonarr & Radarr Integration** - Automatically unmonitor or delete content from your *arr apps
39-
- **Overseerr Integration** - Reset requests when content is removed so users can re-request
40-
- **Grace Periods** - Configure how long items wait in the deletion queue before removal
41-
- **Protection Rules** - Mark content as protected to prevent accidental deletion
42-
- **Deletion Queue** - Review and approve deletions before they happen
43-
- **Activity History** - Full audit log of all actions taken
44-
- **Discord Notifications** - Get notified when content is flagged or deleted
45-
- **Modern Web UI** - Beautiful, responsive dashboard that works on desktop and mobile
33+
- **Rules Engine** &mdash; 28 condition fields across quality, ratings, watch history, collections, and metadata. Three ways to build rules: templates, natural language, or a full nested condition editor with live preview. [More &rarr;](https://github.com/helliott20/prunerr/wiki/Rules-Engine)
34+
35+
- **Collections** &mdash; Syncs movie collections from Radarr. Protect entire collections to prevent cleanup, or queue them for bulk deletion. [More &rarr;](https://github.com/helliott20/prunerr/wiki/Collections)
36+
37+
- **Smart Deletion** &mdash; Grace periods, four deletion actions (unmonitor, delete files, full removal, etc.), Overseerr request resets, and a review queue. Nothing gets deleted without your say-so. [More &rarr;](https://github.com/helliott20/prunerr/wiki/Deletion-Management)
38+
39+
- **Dashboard** &mdash; Library stats, storage trends, service health monitoring, upcoming deletions, and recommendations at a glance.
40+
41+
- **Per-User Watch History** &mdash; Integrates with Tautulli or Tracearr to track who watched what. Build rules around specific users' watching habits.
4642

47-
## Installation
43+
- **API** &mdash; Full REST API with key authentication for scripts, automation, and mobile apps like nzb360. [More &rarr;](https://github.com/helliott20/prunerr/wiki/API-Reference)
4844

49-
### Docker (Recommended)
45+
## Quick Start
5046

5147
```bash
5248
docker run -d \
@@ -62,173 +58,42 @@ docker run -d \
6258
helliott20/prunerr:latest
6359
```
6460

65-
### Docker Compose
66-
67-
```yaml
68-
version: '3.8'
69-
services:
70-
prunerr:
71-
image: helliott20/prunerr:latest
72-
container_name: prunerr
73-
ports:
74-
- "3000:3000"
75-
volumes:
76-
- ./data:/app/data
77-
environment:
78-
- PLEX_URL=http://plex:32400
79-
- PLEX_TOKEN=your-plex-token
80-
- SONARR_URL=http://sonarr:8989
81-
- SONARR_API_KEY=your-sonarr-api-key
82-
- RADARR_URL=http://radarr:7878
83-
- RADARR_API_KEY=your-radarr-api-key
84-
# Optional integrations
85-
- TAUTULLI_URL=http://tautulli:8181
86-
- TAUTULLI_API_KEY=your-tautulli-api-key
87-
- OVERSEERR_URL=http://overseerr:5055
88-
- OVERSEERR_API_KEY=your-overseerr-api-key
89-
- DISCORD_WEBHOOK_URL=your-discord-webhook
90-
restart: unless-stopped
91-
```
92-
93-
### Unraid
94-
95-
Prunerr is available in the Unraid Community Applications. Search for "Prunerr" or use the template URL:
96-
97-
```
98-
https://raw.githubusercontent.com/helliott20/prunerr/main/my-prunerr.xml
99-
```
100-
101-
## Configuration
102-
103-
### Required
104-
105-
| Variable | Description |
106-
|----------|-------------|
107-
| `PLEX_URL` | URL to your Plex server (e.g., `http://192.168.1.100:32400`) |
108-
| `PLEX_TOKEN` | Your Plex authentication token ([how to find](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/)) |
109-
110-
### Sonarr/Radarr (Recommended)
111-
112-
| Variable | Description |
113-
|----------|-------------|
114-
| `SONARR_URL` | URL to your Sonarr instance |
115-
| `SONARR_API_KEY` | Sonarr API key (Settings → General → Security) |
116-
| `RADARR_URL` | URL to your Radarr instance |
117-
| `RADARR_API_KEY` | Radarr API key (Settings → General → Security) |
118-
119-
### Optional Integrations
120-
121-
| Variable | Description |
122-
|----------|-------------|
123-
| `TAUTULLI_URL` | URL to your Tautulli instance |
124-
| `TAUTULLI_API_KEY` | Tautulli API key (Settings → Web Interface) |
125-
| `OVERSEERR_URL` | URL to your Overseerr instance |
126-
| `OVERSEERR_API_KEY` | Overseerr API key (Settings → General) |
127-
| `DISCORD_WEBHOOK_URL` | Discord webhook for notifications |
128-
129-
### Application Settings
130-
131-
| Variable | Default | Description |
132-
|----------|---------|-------------|
133-
| `LOG_LEVEL` | `info` | Logging level: `error`, `warn`, `info`, `debug` |
134-
| `PUID` | `99` | User ID for file permissions |
135-
| `PGID` | `100` | Group ID for file permissions |
136-
137-
## Usage
138-
139-
### Creating Rules
140-
141-
Prunerr uses a smart rule builder to create cleanup policies:
61+
Also available via **Docker Compose** and the **Unraid Community Apps** store. See the [Installation guide](https://github.com/helliott20/prunerr/wiki/Installation) for full details.
14262

143-
1. **Template Rules** - Start with pre-built templates like "Never Watched" or "Watched Once"
144-
2. **Sentence Builder** - Create rules using natural language: "Delete movies that have never been watched and were added more than 30 days ago"
145-
3. **Advanced Mode** - Full control over conditions and operators
63+
## Integrations
14664

147-
### Rule Actions
65+
| Service | Purpose | Required |
66+
|---------|---------|----------|
67+
| **Plex** | Media server &mdash; library data, watch status | Yes |
68+
| **Sonarr** | TV show management | Recommended |
69+
| **Radarr** | Movie management, collections | Recommended |
70+
| **Tautulli** / **Tracearr** | Per-user watch history | One required |
71+
| **Overseerr** / **Seerr** | Request management | Optional |
72+
| **Unraid** | Server monitoring | Optional |
73+
| **Discord** | Notifications | Optional |
14874

149-
- **Flag** - Mark items for review without scheduling deletion
150-
- **Delete** - Add items to the deletion queue with a grace period
151-
- **Notify** - Send a notification without taking action
75+
## Mobile
15276

153-
### Deletion Actions
77+
Prunerr works as a custom web app in [nzb360](https://nzb360.com/) on Android, or in any mobile browser. The UI is fully responsive. See the [Mobile Access guide](https://github.com/helliott20/prunerr/wiki/Mobile-Access).
15478

155-
When deleting content, choose how it's removed:
79+
## Documentation
15680

157-
- **Unmonitor Only** - Stop monitoring but keep files
158-
- **Delete Files Only** - Remove files but keep in Sonarr/Radarr
159-
- **Unmonitor & Delete** - Remove files and stop monitoring
160-
- **Full Removal** - Completely remove from Sonarr/Radarr
81+
Full docs are in the **[Wiki](https://github.com/helliott20/prunerr/wiki)**:
16182

162-
### Protection
163-
164-
Mark important content as protected to prevent deletion:
165-
- Manually protect individual items
166-
- Use bulk actions to protect multiple items
167-
- Protected items are ignored by all rules
168-
169-
## Screenshots
170-
171-
*Coming soon*
172-
173-
## API
174-
175-
Prunerr exposes a REST API for automation and integration:
176-
177-
- `GET /api/library` - List library items
178-
- `GET /api/queue` - View deletion queue
179-
- `GET /api/rules` - List rules
180-
- `POST /api/rules/:id/run` - Run a rule manually
181-
- `GET /api/stats` - Dashboard statistics
182-
183-
Full API documentation coming soon.
184-
185-
## Development
186-
187-
### Prerequisites
188-
189-
- Node.js 18+
190-
- npm or yarn
191-
192-
### Setup
193-
194-
```bash
195-
# Clone the repository
196-
git clone https://github.com/helliott20/prunerr.git
197-
cd prunerr
198-
199-
# Install dependencies
200-
npm install
201-
202-
# Start development server
203-
npm run dev
204-
```
205-
206-
### Building
207-
208-
```bash
209-
# Build for production
210-
npm run build
211-
212-
# Build Docker image
213-
docker build -t prunerr .
214-
```
215-
216-
## Contributing
217-
218-
Contributions are welcome! Please feel free to submit a Pull Request.
83+
- [Installation](https://github.com/helliott20/prunerr/wiki/Installation) &mdash; Docker, Compose, Unraid
84+
- [Configuration](https://github.com/helliott20/prunerr/wiki/Configuration) &mdash; Environment variables and service connections
85+
- [Rules Engine](https://github.com/helliott20/prunerr/wiki/Rules-Engine) &mdash; Building and managing rules
86+
- [Collections](https://github.com/helliott20/prunerr/wiki/Collections) &mdash; Protection and bulk actions
87+
- [Deletion Management](https://github.com/helliott20/prunerr/wiki/Deletion-Management) &mdash; Queue, grace periods, actions
88+
- [API Reference](https://github.com/helliott20/prunerr/wiki/API-Reference) &mdash; Endpoints and authentication
89+
- [Troubleshooting](https://github.com/helliott20/prunerr/wiki/Troubleshooting) &mdash; Common issues
21990

22091
## Support
22192

222-
- **Issues**: [GitHub Issues](https://github.com/helliott20/prunerr/issues)
223-
- **Discussions**: [GitHub Discussions](https://github.com/helliott20/prunerr/discussions)
93+
- **Unraid Forum:** [Support Thread](https://forums.unraid.net/topic/196929-support-prunerr-media-library-cleanup-tool/)
94+
- **GitHub Issues:** [Report a Bug](https://github.com/helliott20/prunerr/issues)
95+
- **Docker Hub:** [helliott20/prunerr](https://hub.docker.com/r/helliott20/prunerr)
22496

22597
## License
22698

227-
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
228-
229-
## Acknowledgments
230-
231-
- [Plex](https://www.plex.tv/) for the amazing media server
232-
- [Sonarr](https://sonarr.tv/) and [Radarr](https://radarr.video/) for *arr automation
233-
- [Tautulli](https://tautulli.com/) for detailed Plex statistics
234-
- [Overseerr](https://overseerr.dev/) for request management
99+
MIT License. See [LICENSE](LICENSE).

client/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"dev": "vite",
88
"build": "tsc && vite build",
99
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
10-
"preview": "vite preview"
10+
"preview": "vite preview",
11+
"test": "vitest run",
12+
"test:watch": "vitest"
1113
},
1214
"dependencies": {
1315
"@tanstack/react-query": "^5.90.19",
@@ -22,6 +24,8 @@
2224
"tailwind-merge": "^3.4.0"
2325
},
2426
"devDependencies": {
27+
"@testing-library/jest-dom": "^6.9.1",
28+
"@testing-library/react": "^16.3.2",
2529
"@types/react": "^18.3.18",
2630
"@types/react-dom": "^18.3.5",
2731
"@typescript-eslint/eslint-plugin": "^8.53.1",
@@ -31,9 +35,11 @@
3135
"eslint": "^9.39.2",
3236
"eslint-plugin-react-hooks": "^7.0.1",
3337
"eslint-plugin-react-refresh": "^0.4.26",
38+
"jsdom": "^29.0.1",
3439
"postcss": "^8.5.6",
3540
"tailwindcss": "^3.4.19",
3641
"typescript": "^5.9.3",
37-
"vite": "^7.3.1"
42+
"vite": "^7.3.1",
43+
"vitest": "^4.1.2"
3844
}
3945
}

client/src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import Settings from './components/Settings/Settings';
1111
import Recommendations from './components/Recommendations/Recommendations';
1212

1313
const MediaItemDetail = lazy(() => import('./components/Library/MediaItemDetail'));
14+
const Collections = lazy(() => import('./components/Collections/Collections'));
15+
const CollectionDetail = lazy(() => import('./components/Collections/CollectionDetail'));
1416

1517
function App() {
1618
return (
@@ -21,6 +23,8 @@ function App() {
2123
<Route path="/library" element={<Library />} />
2224
<Route path="/library/:id" element={<MediaItemDetail />} />
2325
<Route path="/recommendations" element={<Recommendations />} />
26+
<Route path="/collections" element={<Collections />} />
27+
<Route path="/collections/:id" element={<CollectionDetail />} />
2428
<Route path="/rules" element={<Rules />} />
2529
<Route path="/queue" element={<Queue />} />
2630
<Route path="/history" element={<History />} />

client/src/components/ActivityLog/ActivityLog.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ function ActivityRow({ item }: { item: ActivityLogEntry }) {
339339
<td className="px-4 py-3 text-sm text-surface-300">
340340
{item.targetTitle && item.targetId ? (
341341
<Link
342-
to={`/library/${item.targetId}`}
342+
to={item.targetType === 'collection' ? `/collections/${item.targetId}` : `/library/${item.targetId}`}
343343
className="hover:text-accent-400 transition-colors"
344344
>
345345
{item.targetTitle}

0 commit comments

Comments
 (0)