Add ASIN support to file organization and metadata

This update enhances audiobook file organization by including the ASIN in folder names and embedding it as a custom metadata tag in audio files (M4B/M4A/MP3). Documentation is updated to reflect the new folder naming convention and metadata tagging. Additionally, local login and registration can now be disabled via an environment variable, and the interactive torrent search modal allows custom search titles for all modes.
This commit is contained in:
kikootwo
2025-12-24 23:37:40 -05:00
parent 1374e66f13
commit d617e26c92
12 changed files with 145 additions and 51 deletions
+8
View File
@@ -15,6 +15,14 @@ import { getEncryptionService } from '@/lib/services/encryption.service';
*/
export async function POST(request: NextRequest) {
try {
// Check if local login is disabled
if (process.env.DISABLE_LOCAL_LOGIN === 'true') {
return NextResponse.json(
{ error: 'Local login is disabled' },
{ status: 403 }
);
}
const { username, password } = await request.json();
if (!username || !password) {
+8
View File
@@ -8,6 +8,14 @@ import { LocalAuthProvider } from '@/lib/services/auth/LocalAuthProvider';
export async function POST(request: NextRequest) {
try {
// Check if local login is disabled
if (process.env.DISABLE_LOCAL_LOGIN === 'true') {
return NextResponse.json(
{ error: 'Local login is disabled' },
{ status: 403 }
);
}
const { username, password } = await request.json();
if (!username || !password) {
+10 -2
View File
@@ -12,6 +12,9 @@ export async function GET() {
const configService = new ConfigurationService();
const backendMode = await configService.get('system.backend_mode');
// Check if local login is disabled via environment variable
const localLoginDisabled = process.env.DISABLE_LOCAL_LOGIN === 'true';
if (backendMode === 'audiobookshelf') {
// Audiobookshelf mode - check which auth methods are enabled
const oidcEnabled = (await configService.get('oidc.enabled')) === 'true';
@@ -25,14 +28,16 @@ export async function GET() {
const providers: string[] = [];
if (oidcEnabled) providers.push('oidc');
if (hasLocalUsers) providers.push('local');
// Only add 'local' provider if not disabled and users exist
if (hasLocalUsers && !localLoginDisabled) providers.push('local');
return NextResponse.json({
backendMode: 'audiobookshelf',
providers,
registrationEnabled,
registrationEnabled: !localLoginDisabled && registrationEnabled,
hasLocalUsers,
oidcProviderName: oidcEnabled ? oidcProviderName : null,
localLoginDisabled,
});
} else {
// Plex mode - check if local admin exists (setup admin)
@@ -49,17 +54,20 @@ export async function GET() {
registrationEnabled: false,
hasLocalUsers,
oidcProviderName: null,
localLoginDisabled,
});
}
} catch (error) {
console.error('[Auth] Failed to fetch auth providers:', error);
// Default to Plex mode if config can't be read
const localLoginDisabled = process.env.DISABLE_LOCAL_LOGIN === 'true';
return NextResponse.json({
backendMode: 'plex',
providers: ['plex'],
registrationEnabled: false,
hasLocalUsers: false,
oidcProviderName: null,
localLoginDisabled,
});
}
}
+8
View File
@@ -29,6 +29,14 @@ function checkRateLimit(ip: string): boolean {
}
export async function POST(request: NextRequest) {
// Check if local login is disabled
if (process.env.DISABLE_LOCAL_LOGIN === 'true') {
return NextResponse.json(
{ error: 'Local registration is disabled' },
{ status: 403 }
);
}
// Rate limiting
const ip = request.headers.get('x-forwarded-for') || 'unknown';
if (!checkRateLimit(ip)) {
@@ -12,6 +12,7 @@ import { rankTorrents } from '@/lib/utils/ranking-algorithm';
/**
* POST /api/requests/[id]/interactive-search
* Search for torrents and return results for user selection
* Body (optional): { customTitle?: string }
*/
export async function POST(
request: NextRequest,
@@ -28,6 +29,15 @@ export async function POST(
const { id } = await params;
// Parse optional request body
let customTitle: string | undefined;
try {
const body = await req.json();
customTitle = body.customTitle;
} catch (e) {
// No body or invalid JSON - that's okay, customTitle will be undefined
}
const requestRecord = await prisma.request.findUnique({
where: { id },
include: {
@@ -74,9 +84,13 @@ export async function POST(
// Search Prowlarr for torrents - ONLY enabled indexers
const prowlarr = await getProwlarrService();
const searchQuery = requestRecord.audiobook.title; // Title only - cast wide net
// Use custom title if provided, otherwise use audiobook's title
const searchQuery = customTitle || requestRecord.audiobook.title;
console.log(`[InteractiveSearch] Searching ${enabledIndexerIds.length} enabled indexers for: ${searchQuery}`);
if (customTitle) {
console.log(`[InteractiveSearch] Using custom search title (original: "${requestRecord.audiobook.title}")`);
}
const results = await prowlarr.search(searchQuery, {
indexerIds: enabledIndexerIds,
@@ -94,6 +108,7 @@ export async function POST(
}
// Rank torrents using the ranking algorithm
// Always use the audiobook's title/author for ranking (not custom search query)
const rankedResults = rankTorrents(results, {
title: requestRecord.audiobook.title,
author: requestRecord.audiobook.author,
@@ -108,8 +123,9 @@ export async function POST(
const top3 = filteredResults.slice(0, 3);
if (top3.length > 0) {
console.log(`[InteractiveSearch] ==================== RANKING DEBUG ====================`);
console.log(`[InteractiveSearch] Requested Title: "${requestRecord.audiobook.title}"`);
console.log(`[InteractiveSearch] Requested Author: "${requestRecord.audiobook.author}"`);
console.log(`[InteractiveSearch] Search Query: "${searchQuery}"`);
console.log(`[InteractiveSearch] Requested Title (for ranking): "${requestRecord.audiobook.title}"`);
console.log(`[InteractiveSearch] Requested Author (for ranking): "${requestRecord.audiobook.author}"`);
console.log(`[InteractiveSearch] Top ${top3.length} results (out of ${filteredResults.length} above threshold):`);
console.log(`[InteractiveSearch] --------------------------------------------------------`);
top3.forEach((result, index) => {