Compare commits

...

3 Commits

Author SHA1 Message Date
11c80a16a3 docs: add Telegram notifications and updated dashboard to README and FEATURES
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 46s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 06:40:34 +01:00
c366b44c54 chore: bump version to 1.24.1 2026-03-22 06:39:23 +01:00
92f80542e6 perf: skip Next.js image re-optimization and stream proxy responses
Thumbnails are already optimized (WebP) by the API, so disable Next.js
image optimization to avoid redundant CPU work. Switch route handlers
from buffering (arrayBuffer) to streaming (response.body) to reduce
memory usage and latency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 06:38:46 +01:00
8 changed files with 69 additions and 33 deletions

10
Cargo.lock generated
View File

@@ -64,7 +64,7 @@ checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]] [[package]]
name = "api" name = "api"
version = "1.24.0" version = "1.24.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
@@ -1233,7 +1233,7 @@ dependencies = [
[[package]] [[package]]
name = "indexer" name = "indexer"
version = "1.24.0" version = "1.24.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
@@ -1667,7 +1667,7 @@ dependencies = [
[[package]] [[package]]
name = "notifications" name = "notifications"
version = "1.24.0" version = "1.24.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"reqwest", "reqwest",
@@ -1786,7 +1786,7 @@ dependencies = [
[[package]] [[package]]
name = "parsers" name = "parsers"
version = "1.24.0" version = "1.24.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"flate2", "flate2",
@@ -2923,7 +2923,7 @@ dependencies = [
[[package]] [[package]]
name = "stripstream-core" name = "stripstream-core"
version = "1.24.0" version = "1.24.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"serde", "serde",

View File

@@ -10,7 +10,7 @@ resolver = "2"
[workspace.package] [workspace.package]
edition = "2021" edition = "2021"
version = "1.24.0" version = "1.24.1"
license = "MIT" license = "MIT"
[workspace.dependencies] [workspace.dependencies]

View File

@@ -110,6 +110,12 @@ The backoffice will be available at http://localhost:7082
- Batch auto-matching and scheduled metadata refresh - Batch auto-matching and scheduled metadata refresh
- Field locking to protect manual edits from sync - Field locking to protect manual edits from sync
### Notifications
- **Telegram**: real-time notifications via Telegram Bot API
- 12 granular event toggles (scans, thumbnails, conversions, metadata)
- Book thumbnail images included in notifications where applicable
- Test connection from settings
### External Integrations ### External Integrations
- **Komga**: import reading progress - **Komga**: import reading progress
- **Prowlarr**: search for missing volumes - **Prowlarr**: search for missing volumes
@@ -130,9 +136,11 @@ The backoffice will be available at http://localhost:7082
- Rate limiting, token expiration and revocation - Rate limiting, token expiration and revocation
### Web UI (Backoffice) ### Web UI (Backoffice)
- Dashboard with statistics, charts, and reading progress - Dashboard with statistics, interactive charts (recharts), and reading progress
- Currently reading & recently read sections
- Library, book, series, author management - Library, book, series, author management
- Live job monitoring, metadata search modals, settings panel - Live job monitoring, metadata search modals, settings panel
- Notification settings with per-event toggle configuration
## Environment Variables ## Environment Variables

View File

@@ -21,19 +21,16 @@ export async function GET(
const response = await fetch(apiUrl.toString(), { const response = await fetch(apiUrl.toString(), {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
if (!response.ok) { if (!response.ok) {
return new NextResponse(`Failed to fetch image: ${response.status}`, { return new NextResponse(`Failed to fetch image: ${response.status}`, {
status: response.status status: response.status
}); });
} }
// Récupérer le content-type et les données
const contentType = response.headers.get("content-type") || "image/webp"; const contentType = response.headers.get("content-type") || "image/webp";
const imageBuffer = await response.arrayBuffer();
return new NextResponse(response.body, {
// Retourner l'image avec le bon content-type
return new NextResponse(imageBuffer, {
headers: { headers: {
"Content-Type": contentType, "Content-Type": contentType,
"Cache-Control": "public, max-age=300", "Cache-Control": "public, max-age=300",

View File

@@ -6,23 +6,22 @@ export async function GET(
{ params }: { params: Promise<{ bookId: string }> } { params }: { params: Promise<{ bookId: string }> }
) { ) {
const { bookId } = await params; const { bookId } = await params;
try { try {
const { baseUrl, token } = config(); const { baseUrl, token } = config();
const response = await fetch(`${baseUrl}/books/${bookId}/thumbnail`, { const response = await fetch(`${baseUrl}/books/${bookId}/thumbnail`, {
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
}); });
if (!response.ok) { if (!response.ok) {
return new NextResponse(`Failed to fetch thumbnail: ${response.status}`, { return new NextResponse(`Failed to fetch thumbnail: ${response.status}`, {
status: response.status status: response.status
}); });
} }
const contentType = response.headers.get("content-type") || "image/webp"; const contentType = response.headers.get("content-type") || "image/webp";
const imageBuffer = await response.arrayBuffer();
return new NextResponse(response.body, {
return new NextResponse(imageBuffer, {
headers: { headers: {
"Content-Type": contentType, "Content-Type": contentType,
"Cache-Control": "public, max-age=31536000, immutable", "Cache-Control": "public, max-age=31536000, immutable",

View File

@@ -4,6 +4,7 @@ const nextConfig = {
typedRoutes: true, typedRoutes: true,
images: { images: {
minimumCacheTTL: 86400, minimumCacheTTL: 86400,
unoptimized: true,
}, },
}; };

View File

@@ -1,6 +1,6 @@
{ {
"name": "stripstream-backoffice", "name": "stripstream-backoffice",
"version": "1.24.0", "version": "1.24.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev -p 7082", "dev": "next dev -p 7082",

View File

@@ -170,6 +170,34 @@
--- ---
## Notifications
### Telegram
- Real-time notifications via Telegram Bot API (`sendMessage` and `sendPhoto`)
- Configuration: bot token, chat ID, enable/disable toggle
- Test connection button in settings
### Granular Event Toggles
12 individually configurable notification events grouped by category:
| Category | Events |
|----------|--------|
| Scans | `scan_completed`, `scan_failed`, `scan_cancelled` |
| Thumbnails | `thumbnail_completed`, `thumbnail_failed`, `thumbnail_cancelled` |
| Conversion | `conversion_completed`, `conversion_failed`, `conversion_cancelled` |
| Metadata | `metadata_approved`, `metadata_batch_completed`, `metadata_refresh_completed` |
### Thumbnail Images in Notifications
- Book cover thumbnails attached to applicable notifications (conversion, metadata approval)
- Uses `sendPhoto` multipart upload with fallback to text-only `sendMessage`
### Implementation
- Shared `crates/notifications` crate used by both API and indexer
- Fire-and-forget: notification failures are logged but never block the main operation
- Messages formatted in HTML with event-specific icons
---
## Page Rendering & Caching ## Page Rendering & Caching
### Page Extraction ### Page Extraction
@@ -238,13 +266,16 @@
## Backoffice (Web UI) ## Backoffice (Web UI)
### Dashboard ### Dashboard
- Statistics cards: books, series, authors, libraries - Statistics cards: books, series, authors, libraries, pages, total size
- Donut charts: reading status breakdown, format distribution - Interactive charts (recharts): donut, area, stacked bar, horizontal bar
- Bar charts: books per language - Reading status breakdown, format distribution, library distribution
- Per-library reading progress bars - Currently reading section with progress bars
- Top series by book/page count - Recently read section with cover thumbnails
- Monthly addition timeline - Reading activity over time (area chart)
- Metadata coverage stats - Books added over time (area chart)
- Per-library stacked reading progress
- Top series by book count
- Metadata coverage and provider breakdown
### Pages ### Pages
- **Libraries**: list, create, delete, configure monitoring and metadata provider - **Libraries**: list, create, delete, configure monitoring and metadata provider
@@ -253,7 +284,7 @@
- **Authors**: list with book/series counts, detail with author's books - **Authors**: list with book/series counts, detail with author's books
- **Jobs**: history, live progress via SSE, error details - **Jobs**: history, live progress via SSE, error details
- **Tokens**: create, list, revoke API tokens - **Tokens**: create, list, revoke API tokens
- **Settings**: image processing, cache, thumbnails, external services (Prowlarr, qBittorrent) - **Settings**: image processing, cache, thumbnails, external services (Prowlarr, qBittorrent), notifications (Telegram)
### Interactive Features ### Interactive Features
- Real-time search with suggestions - Real-time search with suggestions