mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 04:40:09 +00:00
Add BookDate card stack animations and thumbnail caching
Implements pure CSS card stack animations for BookDate recommendations, including smooth exit and advance transitions. Adds local caching of library cover thumbnails during scans, updates database schema and API to serve cached covers, and enhances BookDate to support 'favorites' scope with a book picker modal. Updates admin settings validation logic for Prowlarr, improves indexer state management, and documents new features and backend changes.
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
import { prisma } from '../db';
|
||||
import { RMABLogger } from '../utils/logger';
|
||||
import { getLibraryService } from '../services/library';
|
||||
import { getThumbnailCacheService } from '../services/thumbnail-cache.service';
|
||||
|
||||
export interface PlexRecentlyAddedPayload {
|
||||
jobId?: string;
|
||||
@@ -66,6 +67,7 @@ export async function processPlexRecentlyAddedCheck(payload: PlexRecentlyAddedPa
|
||||
|
||||
// Get library service (automatically selects Plex or Audiobookshelf)
|
||||
const libraryService = await getLibraryService();
|
||||
const thumbnailCacheService = getThumbnailCacheService();
|
||||
|
||||
try {
|
||||
// Get configured library ID
|
||||
@@ -73,6 +75,9 @@ export async function processPlexRecentlyAddedCheck(payload: PlexRecentlyAddedPa
|
||||
? await configService.get('audiobookshelf.library_id')
|
||||
: await configService.get('plex_audiobook_library_id');
|
||||
|
||||
// Get cover caching parameters (needed for thumbnail caching)
|
||||
const coverCachingParams = await (libraryService as any).getCoverCachingParams();
|
||||
|
||||
// Fetch top 10 recently added items using abstraction layer
|
||||
const recentItems = await libraryService.getRecentlyAdded(libraryId!, 10);
|
||||
|
||||
@@ -93,7 +98,7 @@ export async function processPlexRecentlyAddedCheck(payload: PlexRecentlyAddedPa
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
await prisma.plexLibrary.create({
|
||||
const newLibraryItem = await prisma.plexLibrary.create({
|
||||
data: {
|
||||
plexGuid: item.externalId,
|
||||
plexRatingKey: item.id,
|
||||
@@ -111,6 +116,26 @@ export async function processPlexRecentlyAddedCheck(payload: PlexRecentlyAddedPa
|
||||
lastScannedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
// Cache library cover (synchronous with smart skip-if-exists logic)
|
||||
if (item.coverUrl && item.externalId) {
|
||||
const cachedPath = await thumbnailCacheService.cacheLibraryThumbnail(
|
||||
item.externalId,
|
||||
item.coverUrl,
|
||||
coverCachingParams.backendBaseUrl,
|
||||
coverCachingParams.authToken,
|
||||
coverCachingParams.backendMode
|
||||
);
|
||||
|
||||
// Update database with cached path if successful
|
||||
if (cachedPath) {
|
||||
await prisma.plexLibrary.update({
|
||||
where: { id: newLibraryItem.id },
|
||||
data: { cachedLibraryCoverPath: cachedPath },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
newCount++;
|
||||
logger.info(`New item added: ${item.title} by ${item.author}`);
|
||||
} else {
|
||||
@@ -129,6 +154,26 @@ export async function processPlexRecentlyAddedCheck(payload: PlexRecentlyAddedPa
|
||||
lastScannedAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
// Cache library cover (synchronous with smart skip-if-exists logic)
|
||||
if (item.coverUrl && item.externalId) {
|
||||
const cachedPath = await thumbnailCacheService.cacheLibraryThumbnail(
|
||||
item.externalId,
|
||||
item.coverUrl,
|
||||
coverCachingParams.backendBaseUrl,
|
||||
coverCachingParams.authToken,
|
||||
coverCachingParams.backendMode
|
||||
);
|
||||
|
||||
// Update database with cached path if successful
|
||||
if (cachedPath) {
|
||||
await prisma.plexLibrary.update({
|
||||
where: { id: existing.id },
|
||||
data: { cachedLibraryCoverPath: cachedPath },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updatedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ScanPlexPayload } from '../services/job-queue.service';
|
||||
import { prisma } from '../db';
|
||||
import { getLibraryService } from '../services/library';
|
||||
import { getConfigService } from '../services/config.service';
|
||||
import { getThumbnailCacheService } from '../services/thumbnail-cache.service';
|
||||
import { RMABLogger } from '../utils/logger';
|
||||
|
||||
/**
|
||||
@@ -28,6 +29,7 @@ export async function processScanPlex(payload: ScanPlexPayload): Promise<any> {
|
||||
const libraryService = await getLibraryService();
|
||||
const configService = getConfigService();
|
||||
const backendMode = await configService.getBackendMode();
|
||||
const thumbnailCacheService = getThumbnailCacheService();
|
||||
|
||||
logger.info(`Backend mode: ${backendMode}`);
|
||||
|
||||
@@ -50,6 +52,9 @@ export async function processScanPlex(payload: ScanPlexPayload): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
// Get cover caching parameters (needed for thumbnail caching)
|
||||
const coverCachingParams = await (libraryService as any).getCoverCachingParams();
|
||||
|
||||
logger.info(`Fetching content from library ${targetLibraryId}`);
|
||||
|
||||
// 3. Get all audiobooks from library using abstraction layer
|
||||
@@ -97,6 +102,25 @@ export async function processScanPlex(payload: ScanPlexPayload): Promise<any> {
|
||||
},
|
||||
});
|
||||
|
||||
// Cache library cover (synchronous with smart skip-if-exists logic)
|
||||
if (item.coverUrl && item.externalId) {
|
||||
const cachedPath = await thumbnailCacheService.cacheLibraryThumbnail(
|
||||
item.externalId,
|
||||
item.coverUrl,
|
||||
coverCachingParams.backendBaseUrl,
|
||||
coverCachingParams.authToken,
|
||||
coverCachingParams.backendMode
|
||||
);
|
||||
|
||||
// Update database with cached path if successful
|
||||
if (cachedPath) {
|
||||
await prisma.plexLibrary.update({
|
||||
where: { id: existing.id },
|
||||
data: { cachedLibraryCoverPath: cachedPath },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updatedCount++;
|
||||
} else {
|
||||
// Create new plex_library entry
|
||||
@@ -119,6 +143,25 @@ export async function processScanPlex(payload: ScanPlexPayload): Promise<any> {
|
||||
},
|
||||
});
|
||||
|
||||
// Cache library cover (synchronous with smart skip-if-exists logic)
|
||||
if (item.coverUrl && item.externalId) {
|
||||
const cachedPath = await thumbnailCacheService.cacheLibraryThumbnail(
|
||||
item.externalId,
|
||||
item.coverUrl,
|
||||
coverCachingParams.backendBaseUrl,
|
||||
coverCachingParams.authToken,
|
||||
coverCachingParams.backendMode
|
||||
);
|
||||
|
||||
// Update database with cached path if successful
|
||||
if (cachedPath) {
|
||||
await prisma.plexLibrary.update({
|
||||
where: { id: newLibraryItem.id },
|
||||
data: { cachedLibraryCoverPath: cachedPath },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
newCount++;
|
||||
logger.info(`Added new: "${item.title}" by ${item.author}`);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user