Files
root 1fe7392063 feat: Complete Sonarr integration with security enhancements
This commit adds comprehensive Sonarr webhook integration and implements
critical security improvements identified in code review.

## Sonarr Integration
- Full webhook support for Grab, Download, Rename, Delete, and Test events
- HMAC SHA256 signature verification for webhook authentication
- Series mapping system (Sonarr TVDB ID → Anime Provider URL)
- 11 new API endpoints for configuration, mappings, search, and downloads
- Comprehensive test suite (31 tests, all passing)
- Complete documentation in docs/SONARR_INTEGRATION.md

## Security Enhancements
- CORS restricted to specific origins (user's IP: 192.168.1.204:3000)
- Path traversal prevention via sanitize_filename() and is_safe_filename()
- Structured logging infrastructure (replaced all print() statements)
- Environment-based configuration with .env support
- Filename sanitization prevents malicious path attacks

## New Features
- Lpayer and Sibnet downloader support
- Kitsu API integration for anime metadata
- Recommendation engine based on download history
- Latest releases endpoint for new anime
- Modular web interface with component-based templates

## Configuration
- Centralized settings via app/config.py with pydantic-settings
- Sonarr config auto-created in config/ directory
- Example configurations provided for easy setup

## Tests
- 31 Sonarr integration tests (23 functionality + 9 security)
- 100+ tests passing in core test files
- Security utilities fully tested

## Documentation
- Updated CLAUDE.md with Sonarr and testing info
- Added IMPROVEMENTS_2024-01-24.md analysis
- Added SONARR_IMPLEMENTATION.md technical summary

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
2026-01-24 21:25:47 +00:00

82 lines
2.9 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>Test</title>
</head>
<body>
<h1>Test Home Content</h1>
<div id="homeLoading">Loading...</div>
<div id="recommendationsSection" style="display:none;">
<h2>Recommendations</h2>
<div id="recommendationsList"></div>
</div>
<div id="releasesSection" style="display:none;">
<h2>Releases</h2>
<div id="releasesList"></div>
</div>
<script>
const API_BASE = '/api';
async function loadRecommendations() {
console.log('Loading recommendations...');
const container = document.getElementById('recommendationsList');
const section = document.getElementById('recommendationsSection');
const loading = document.getElementById('homeLoading');
try {
loading.style.display = 'block';
const response = await fetch(`${API_BASE}/recommendations?limit=5`);
const data = await response.json();
console.log('Response:', data);
loading.style.display = 'none';
section.style.display = 'block';
if (data.recommendations && data.recommendations.length > 0) {
container.innerHTML = data.recommendations.map(anime =>
`<div><strong>${anime.title}</strong> (score: ${anime.score})</div>`
).join('');
} else {
container.innerHTML = '<p>No recommendations</p>';
}
} catch (error) {
console.error('Error:', error);
loading.innerHTML = 'Error: ' + error.message;
}
}
async function loadLatestReleases() {
console.log('Loading releases...');
const container = document.getElementById('releasesList');
const section = document.getElementById('releasesSection');
try {
const response = await fetch(`${API_BASE}/releases/latest?limit=5`);
const data = await response.json();
console.log('Response:', data);
section.style.display = 'block';
if (data.releases && data.releases.length > 0) {
container.innerHTML = data.releases.map(anime =>
`<div><strong>${anime.title}</strong> (score: ${anime.score})</div>`
).join('');
} else {
container.innerHTML = '<p>No releases</p>';
}
} catch (error) {
console.error('Error:', error);
container.innerHTML = 'Error: ' + error.message;
}
}
// Load on start
window.onload = async () => {
await loadRecommendations();
await loadLatestReleases();
};
</script>
</body>
</html>