mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 04:40:09 +00:00
Implement centralized logging with RMABLogger
Replaces scattered console statements with a unified RMABLogger across backend API routes and services. Adds LOG_LEVEL-based filtering, job-aware database persistence, and context-based logging. Updates documentation to describe the new logging system and usage patterns. Also documents qBittorrent CSRF header fix
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { ConfigurationService } from '@/lib/services/config.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BackendMode');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -18,7 +21,7 @@ export async function GET(request: NextRequest) {
|
||||
isAudiobookshelf: backendMode === 'audiobookshelf'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[BackendMode] Failed to get backend mode:', error);
|
||||
logger.error('Failed to get backend mode', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to get backend mode' },
|
||||
{ status: 500 }
|
||||
@@ -50,7 +53,7 @@ export async function PUT(request: NextRequest) {
|
||||
const { clearLibraryServiceCache } = await import('@/lib/services/library');
|
||||
clearLibraryServiceCache();
|
||||
|
||||
console.log(`[BackendMode] Backend mode changed to: ${mode}`);
|
||||
logger.info(`Backend mode changed to: ${mode}`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -58,7 +61,7 @@ export async function PUT(request: NextRequest) {
|
||||
message: `Backend mode set to ${mode}`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[BackendMode] Failed to set backend mode:', error);
|
||||
logger.error('Failed to set backend mode', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to set backend mode' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.BookDate.Toggle');
|
||||
|
||||
async function handler(req: AuthenticatedRequest) {
|
||||
try {
|
||||
@@ -31,7 +34,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Admin toggle error:', error);
|
||||
logger.error('Admin toggle error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to toggle BookDate' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -9,6 +9,9 @@ import { prisma } from '@/lib/db';
|
||||
import { getQBittorrentService } from '@/lib/integrations/qbittorrent.service';
|
||||
import { getSABnzbdService } from '@/lib/integrations/sabnzbd.service';
|
||||
import { getConfigService } from '@/lib/services/config.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Downloads');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -96,7 +99,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
} catch (error) {
|
||||
// Download client unavailable or download not found - use defaults
|
||||
console.error(`[Admin] Failed to get download info:`, error);
|
||||
logger.error('Failed to get download info', { error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -117,7 +120,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json({ downloads: formatted });
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch active downloads:', error);
|
||||
logger.error('Failed to fetch active downloads', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch active downloads' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { verifyAccessToken } from '@/lib/utils/jwt';
|
||||
import { getJobQueueService } from '@/lib/services/job-queue.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.JobStatus');
|
||||
|
||||
/**
|
||||
* GET /api/admin/job-status/:id
|
||||
@@ -30,17 +33,17 @@ export async function GET(
|
||||
// Await params in Next.js 15+
|
||||
const { id } = await params;
|
||||
|
||||
console.log(`[JobStatus] Fetching status for job ID: ${id}`);
|
||||
logger.debug(`Fetching status for job ID: ${id}`);
|
||||
|
||||
const jobQueueService = getJobQueueService();
|
||||
const job = await jobQueueService.getJob(id);
|
||||
|
||||
if (!job) {
|
||||
console.log(`[JobStatus] Job not found: ${id}`);
|
||||
logger.debug(`Job not found: ${id}`);
|
||||
return NextResponse.json({ error: 'Job not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
console.log(`[JobStatus] Job ${id} status: ${job.status}, type: ${job.type}`);
|
||||
logger.debug(`Job ${id} status: ${job.status}, type: ${job.type}`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -58,7 +61,7 @@ export async function GET(
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get job status:', error);
|
||||
logger.error('Failed to get job status', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'InternalError',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { verifyAccessToken } from '@/lib/utils/jwt';
|
||||
import { getSchedulerService } from '@/lib/services/scheduler.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Jobs');
|
||||
|
||||
/**
|
||||
* PUT /api/admin/jobs/:id
|
||||
@@ -45,7 +48,7 @@ export async function PUT(
|
||||
job,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to update scheduled job:', error);
|
||||
logger.error('Failed to update scheduled job', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'InternalError',
|
||||
@@ -87,7 +90,7 @@ export async function DELETE(
|
||||
message: 'Job deleted successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to delete scheduled job:', error);
|
||||
logger.error('Failed to delete scheduled job', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'InternalError',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { verifyAccessToken } from '@/lib/utils/jwt';
|
||||
import { getSchedulerService } from '@/lib/services/scheduler.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.JobTrigger');
|
||||
|
||||
/**
|
||||
* POST /api/admin/jobs/:id/trigger
|
||||
@@ -30,12 +33,12 @@ export async function POST(
|
||||
// Await params in Next.js 15+
|
||||
const { id } = await params;
|
||||
|
||||
console.log(`[JobTrigger] Triggering scheduled job: ${id}`);
|
||||
logger.info(`Triggering scheduled job: ${id}`);
|
||||
|
||||
const schedulerService = getSchedulerService();
|
||||
const jobId = await schedulerService.triggerJobNow(id);
|
||||
|
||||
console.log(`[JobTrigger] Job triggered successfully, database job ID: ${jobId}`);
|
||||
logger.info(`Job triggered successfully, database job ID: ${jobId}`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -43,7 +46,7 @@ export async function POST(
|
||||
message: 'Job triggered successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to trigger job:', error);
|
||||
logger.error('Failed to trigger job', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'InternalError',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { verifyAccessToken } from '@/lib/utils/jwt';
|
||||
import { getSchedulerService } from '@/lib/services/scheduler.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Jobs');
|
||||
|
||||
/**
|
||||
* GET /api/admin/jobs
|
||||
@@ -31,7 +34,7 @@ export async function GET(request: NextRequest) {
|
||||
jobs,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get scheduled jobs:', error);
|
||||
logger.error('Failed to get scheduled jobs', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'InternalError',
|
||||
@@ -74,7 +77,7 @@ export async function POST(request: NextRequest) {
|
||||
job,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to create scheduled job:', error);
|
||||
logger.error('Failed to create scheduled job', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'InternalError',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Logs');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -94,7 +97,7 @@ export async function GET(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch logs:', error);
|
||||
logger.error('Failed to fetch logs', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch logs' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Metrics');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -77,7 +80,7 @@ export async function GET(request: NextRequest) {
|
||||
systemHealth,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch metrics:', error);
|
||||
logger.error('Failed to fetch metrics', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch metrics' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin } from '@/lib/middleware/auth';
|
||||
import { processScanPlex } from '@/lib/processors/scan-plex.processor';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Plex.Scan');
|
||||
|
||||
/**
|
||||
* POST /api/admin/plex/scan
|
||||
@@ -27,7 +30,7 @@ export async function POST(request: NextRequest) {
|
||||
...result,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[API] Plex scan failed:', error);
|
||||
logger.error('Plex scan failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'ScanFailed',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { deleteRequest } from '@/lib/services/request-delete.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Requests');
|
||||
|
||||
/**
|
||||
* DELETE /api/admin/requests/[id]
|
||||
@@ -62,7 +65,7 @@ export async function DELETE(
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to delete request:', error);
|
||||
logger.error('Failed to delete request', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'DeleteError',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Requests.Recent');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -61,7 +64,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json({ requests: formatted });
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch recent requests:', error);
|
||||
logger.error('Failed to fetch recent requests', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch recent requests' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -5,20 +5,23 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.ABSLibraries');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
console.log('[ABS Libraries] GET request received');
|
||||
logger.debug('GET request received');
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
console.log('[ABS Libraries] Auth passed, user:', req.user);
|
||||
logger.debug('Auth passed', { user: req.user });
|
||||
return requireAdmin(req, async () => {
|
||||
console.log('[ABS Libraries] Admin check passed');
|
||||
logger.debug('Admin check passed');
|
||||
try {
|
||||
// Use getConfigService like Plex endpoint does
|
||||
const { getConfigService } = await import('@/lib/services/config.service');
|
||||
const configService = getConfigService();
|
||||
const serverUrl = await configService.get('audiobookshelf.server_url');
|
||||
const apiToken = await configService.get('audiobookshelf.api_token');
|
||||
console.log('[ABS Libraries] Config loaded:', { hasServerUrl: !!serverUrl, hasApiToken: !!apiToken });
|
||||
logger.debug('Config loaded', { hasServerUrl: !!serverUrl, hasApiToken: !!apiToken });
|
||||
|
||||
if (!serverUrl || !apiToken) {
|
||||
return NextResponse.json(
|
||||
@@ -55,7 +58,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json({ libraries });
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch ABS libraries:', error);
|
||||
logger.error('Failed to fetch ABS libraries', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch libraries' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { ConfigUpdate } from '@/lib/services/config.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.Audiobookshelf');
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -41,7 +44,7 @@ export async function PUT(request: NextRequest) {
|
||||
message: 'Audiobookshelf settings saved successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to save Audiobookshelf settings:', error);
|
||||
logger.error('Failed to save Audiobookshelf settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to save settings' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireLocalAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.ChangePassword');
|
||||
|
||||
/**
|
||||
* POST /api/admin/settings/change-password
|
||||
@@ -114,14 +117,14 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`[Auth] Local admin password changed successfully for user ${user.id}`);
|
||||
logger.info(`Local admin password changed successfully`, { userId: user.id });
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Password changed successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Auth] Failed to change password:', error);
|
||||
logger.error('Failed to change password', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { PathMapper } from '@/lib/utils/path-mapper';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.DownloadClient');
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -135,7 +138,7 @@ export async function PUT(request: NextRequest) {
|
||||
create: { key: 'download_client_local_path', value: localPath || '' },
|
||||
});
|
||||
|
||||
console.log('[Admin] Download client settings updated');
|
||||
logger.info('Download client settings updated');
|
||||
|
||||
// Invalidate download client service singleton to force reload of credentials and URL
|
||||
if (type === 'qbittorrent') {
|
||||
@@ -151,7 +154,7 @@ export async function PUT(request: NextRequest) {
|
||||
message: 'Download client settings updated successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to update download client settings:', error);
|
||||
logger.error('Failed to update download client settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.Ebook');
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -73,7 +76,7 @@ export async function PUT(request: NextRequest) {
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Failed to save e-book settings:', error);
|
||||
logger.error('Failed to save e-book settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to save settings' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { testFlareSolverrConnection } from '@/lib/services/ebook-scraper';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.TestFlareSolverr');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -31,7 +34,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
console.error('FlareSolverr test failed:', error);
|
||||
logger.error('FlareSolverr test failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.OIDC');
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -62,7 +65,7 @@ export async function PUT(request: NextRequest) {
|
||||
message: 'OIDC settings saved successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to save OIDC settings:', error);
|
||||
logger.error('Failed to save OIDC settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to save settings' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.Paths');
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -65,7 +68,7 @@ export async function PUT(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[Admin] Paths settings updated');
|
||||
logger.info('Paths settings updated');
|
||||
|
||||
// Invalidate qBittorrent service singleton to force reload of download_dir
|
||||
const { invalidateQBittorrentService } = await import('@/lib/integrations/qbittorrent.service');
|
||||
@@ -76,7 +79,7 @@ export async function PUT(request: NextRequest) {
|
||||
message: 'Paths settings updated successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to update paths settings:', error);
|
||||
logger.error('Failed to update paths settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { getPlexService } from '@/lib/integrations/plex.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.PlexLibraries');
|
||||
|
||||
/**
|
||||
* GET /api/admin/settings/plex/libraries
|
||||
@@ -51,7 +54,7 @@ export async function GET(request: NextRequest) {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Plex] Failed to fetch libraries:', error);
|
||||
logger.error('Failed to fetch libraries', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getPlexService } from '@/lib/integrations/plex.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.AdminPlexSettings');
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -69,24 +72,24 @@ export async function PUT(request: NextRequest) {
|
||||
update: { value: serverInfo.info.machineIdentifier },
|
||||
create: { key: 'plex_machine_identifier', value: serverInfo.info.machineIdentifier },
|
||||
});
|
||||
console.log('[Admin] machineIdentifier updated:', serverInfo.info.machineIdentifier);
|
||||
logger.info('machineIdentifier updated', { machineIdentifier: serverInfo.info.machineIdentifier });
|
||||
} else {
|
||||
console.warn('[Admin] Could not fetch machineIdentifier');
|
||||
logger.warn('Could not fetch machineIdentifier');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Admin] Error fetching machineIdentifier:', error);
|
||||
logger.error('Error fetching machineIdentifier', { error: error instanceof Error ? error.message : String(error) });
|
||||
// Don't fail the request if machineIdentifier fetch fails
|
||||
}
|
||||
|
||||
console.log('[Admin] Plex settings updated');
|
||||
logger.info('Plex settings updated');
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Plex settings updated successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to update Plex settings:', error);
|
||||
logger.error('Failed to update Plex settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { getProwlarrService } from '@/lib/integrations/prowlarr.service';
|
||||
import { getConfigService } from '@/lib/services/config.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.ProwlarrIndexers');
|
||||
|
||||
interface SavedIndexerConfig {
|
||||
id: number;
|
||||
@@ -65,7 +68,7 @@ export async function GET(request: NextRequest) {
|
||||
flagConfigs,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Prowlarr] Failed to fetch indexers:', error);
|
||||
logger.error('Failed to fetch indexers', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
@@ -128,7 +131,7 @@ export async function PUT(request: NextRequest) {
|
||||
message: 'Indexer configuration saved',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Prowlarr] Failed to save indexer config:', error);
|
||||
logger.error('Failed to save indexer config', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.Prowlarr');
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -36,14 +39,14 @@ export async function PUT(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[Admin] Prowlarr settings updated');
|
||||
logger.info('Prowlarr settings updated');
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Prowlarr settings updated successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to update Prowlarr settings:', error);
|
||||
logger.error('Failed to update Prowlarr settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.Registration');
|
||||
|
||||
export async function PUT(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -26,7 +29,7 @@ export async function PUT(request: NextRequest) {
|
||||
message: 'Registration settings saved successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to save registration settings:', error);
|
||||
logger.error('Failed to save registration settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to save settings' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -103,7 +106,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json(settings);
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch settings:', error);
|
||||
logger.error('Failed to fetch settings', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch settings' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -8,6 +8,9 @@ import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middlewar
|
||||
import { prisma } from '@/lib/db';
|
||||
import { QBittorrentService } from '@/lib/integrations/qbittorrent.service';
|
||||
import { SABnzbdService } from '@/lib/integrations/sabnzbd.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.TestDownloadClient');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -24,7 +27,7 @@ export async function POST(request: NextRequest) {
|
||||
localPath,
|
||||
} = await request.json();
|
||||
|
||||
console.log('[TestDownloadClient] Received request:', { type, url, hasUsername: !!username, hasPassword: !!password });
|
||||
logger.debug('Received request', { type, url, hasUsername: !!username, hasPassword: !!password });
|
||||
|
||||
if (!type || !url) {
|
||||
return NextResponse.json(
|
||||
@@ -61,7 +64,7 @@ export async function POST(request: NextRequest) {
|
||||
let version: string | undefined;
|
||||
|
||||
if (type === 'qbittorrent') {
|
||||
console.log('[TestDownloadClient] Testing qBittorrent connection');
|
||||
logger.debug('Testing qBittorrent connection');
|
||||
if (!username || !actualPassword) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Username and password are required for qBittorrent' },
|
||||
@@ -77,7 +80,7 @@ export async function POST(request: NextRequest) {
|
||||
disableSSLVerify || false
|
||||
);
|
||||
} else if (type === 'sabnzbd') {
|
||||
console.log('[TestDownloadClient] Testing SABnzbd connection');
|
||||
logger.debug('Testing SABnzbd connection');
|
||||
if (!actualPassword) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'API key (password) is required for SABnzbd' },
|
||||
@@ -134,7 +137,7 @@ export async function POST(request: NextRequest) {
|
||||
version,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin Settings] Download client test failed:', error);
|
||||
logger.error('Download client test failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getPlexService } from '@/lib/integrations/plex.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.TestPlex');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -70,7 +73,7 @@ export async function POST(request: NextRequest) {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin Settings] Plex test failed:', error);
|
||||
logger.error('Plex test failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { ProwlarrService } from '@/lib/integrations/prowlarr.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Settings.TestProwlarr');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -59,7 +62,7 @@ export async function POST(request: NextRequest) {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin Settings] Prowlarr test failed:', error);
|
||||
logger.error('Prowlarr test failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Users.Approve');
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
@@ -64,7 +67,7 @@ export async function POST(
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to approve/reject user:', error);
|
||||
logger.error('Failed to approve/reject user', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to process user approval' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Users');
|
||||
|
||||
export async function PUT(
|
||||
request: NextRequest,
|
||||
@@ -89,7 +92,7 @@ export async function PUT(
|
||||
|
||||
return NextResponse.json({ user: updatedUser });
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to update user:', error);
|
||||
logger.error('Failed to update user', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to update user' },
|
||||
{ status: 500 }
|
||||
@@ -184,7 +187,7 @@ export async function DELETE(
|
||||
message: `User "${targetUser.plexUsername}" has been deleted. Their ${targetUser._count.requests} request(s) have been preserved.`
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to delete user:', error);
|
||||
logger.error('Failed to delete user', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to delete user' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Users.Pending');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -30,7 +33,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json({ users: pendingUsers });
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch pending users:', error);
|
||||
logger.error('Failed to fetch pending users', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch pending users' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Admin.Users');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
||||
@@ -40,7 +43,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return NextResponse.json({ users });
|
||||
} catch (error) {
|
||||
console.error('[Admin] Failed to fetch users:', error);
|
||||
logger.error('Failed to fetch users', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to fetch users' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getAudibleService } from '@/lib/integrations/audible.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Audiobooks.Details');
|
||||
|
||||
/**
|
||||
* GET /api/audiobooks/[asin]
|
||||
@@ -45,7 +48,7 @@ export async function GET(
|
||||
audiobook,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get audiobook details:', error);
|
||||
logger.error('Failed to get audiobook details', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'FetchError',
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Audiobooks.Covers');
|
||||
|
||||
/**
|
||||
* GET /api/audiobooks/covers?count=100
|
||||
@@ -64,7 +67,7 @@ export async function GET() {
|
||||
count: shuffled.length,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get audiobook covers:', error);
|
||||
logger.error('Failed to get audiobook covers', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
// Return empty array on error (login page will show placeholders)
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -9,6 +9,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { enrichAudiobooksWithMatches } from '@/lib/utils/audiobook-matcher';
|
||||
import { getCurrentUser } from '@/lib/middleware/auth';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Audiobooks.NewReleases');
|
||||
|
||||
/**
|
||||
* GET /api/audiobooks/new-releases?page=1&limit=20
|
||||
@@ -128,7 +131,7 @@ export async function GET(request: NextRequest) {
|
||||
lastSync: audiobooks[0]?.lastSyncedAt?.toISOString() || null,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get new releases:', error);
|
||||
logger.error('Failed to get new releases', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'FetchError',
|
||||
|
||||
@@ -9,6 +9,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { enrichAudiobooksWithMatches } from '@/lib/utils/audiobook-matcher';
|
||||
import { getCurrentUser } from '@/lib/middleware/auth';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Audiobooks.Popular');
|
||||
|
||||
/**
|
||||
* GET /api/audiobooks/popular?page=1&limit=20
|
||||
@@ -128,7 +131,7 @@ export async function GET(request: NextRequest) {
|
||||
lastSync: audiobooks[0]?.lastSyncedAt?.toISOString() || null,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get popular audiobooks:', error);
|
||||
logger.error('Failed to get popular audiobooks', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'FetchError',
|
||||
|
||||
@@ -11,6 +11,9 @@ import { prisma } from '@/lib/db';
|
||||
import { getJobQueueService } from '@/lib/services/job-queue.service';
|
||||
import { findPlexMatch } from '@/lib/utils/audiobook-matcher';
|
||||
import { z } from 'zod';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.RequestWithTorrent');
|
||||
|
||||
const RequestWithTorrentSchema = z.object({
|
||||
audiobook: z.object({
|
||||
@@ -153,7 +156,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Delete the existing failed/warn/cancelled request
|
||||
console.log(`[RequestWithTorrent] Deleting existing ${existingRequest.status} request ${existingRequest.id}`);
|
||||
logger.debug(`Deleting existing ${existingRequest.status} request ${existingRequest.id}`);
|
||||
await prisma.request.delete({
|
||||
where: { id: existingRequest.id },
|
||||
});
|
||||
@@ -190,14 +193,14 @@ export async function POST(request: NextRequest) {
|
||||
torrent
|
||||
);
|
||||
|
||||
console.log(`[RequestWithTorrent] Queued download monitor job for request ${newRequest.id}`);
|
||||
logger.info(`Queued download monitor job for request ${newRequest.id}`);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
request: newRequest,
|
||||
}, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('Failed to create request with torrent:', error);
|
||||
logger.error('Failed to create request with torrent', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -10,6 +10,9 @@ import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { getProwlarrService } from '@/lib/integrations/prowlarr.service';
|
||||
import { rankTorrents } from '@/lib/utils/ranking-algorithm';
|
||||
import { z } from 'zod';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.AudiobookSearch');
|
||||
|
||||
const SearchSchema = z.object({
|
||||
title: z.string(),
|
||||
@@ -68,14 +71,14 @@ export async function POST(request: NextRequest) {
|
||||
const prowlarr = await getProwlarrService();
|
||||
const searchQuery = title; // Title only - cast wide net
|
||||
|
||||
console.log(`[AudiobookSearch] Searching ${enabledIndexerIds.length} enabled indexers for: ${searchQuery}`);
|
||||
logger.info(`Searching ${enabledIndexerIds.length} enabled indexers`, { searchQuery });
|
||||
|
||||
const results = await prowlarr.search(searchQuery, {
|
||||
indexerIds: enabledIndexerIds,
|
||||
maxResults: 100, // Increased limit for broader search
|
||||
});
|
||||
|
||||
console.log(`[AudiobookSearch] Found ${results.length} raw results for "${title}" by ${author}`);
|
||||
logger.debug(`Found ${results.length} raw results`, { title, author });
|
||||
|
||||
if (results.length === 0) {
|
||||
return NextResponse.json({
|
||||
@@ -90,41 +93,30 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// No threshold filtering - show all results like interactive search
|
||||
// User can see scores and make their own decision
|
||||
console.log(`[AudiobookSearch] Ranked ${rankedResults.length} results (no threshold filter - user decides)`);
|
||||
logger.debug(`Ranked ${rankedResults.length} results (no threshold filter - user decides)`);
|
||||
|
||||
// Log top 3 results with detailed score breakdown for debugging
|
||||
const top3 = rankedResults.slice(0, 3);
|
||||
if (top3.length > 0) {
|
||||
console.log(`[AudiobookSearch] ==================== RANKING DEBUG ====================`);
|
||||
console.log(`[AudiobookSearch] Requested Title: "${title}"`);
|
||||
console.log(`[AudiobookSearch] Requested Author: "${author}"`);
|
||||
console.log(`[AudiobookSearch] Top ${top3.length} results (out of ${rankedResults.length} total):`);
|
||||
console.log(`[AudiobookSearch] --------------------------------------------------------`);
|
||||
logger.debug('==================== RANKING DEBUG ====================');
|
||||
logger.debug('Search parameters', { requestedTitle: title, requestedAuthor: author });
|
||||
logger.debug(`Top ${top3.length} results (out of ${rankedResults.length} total)`);
|
||||
logger.debug('--------------------------------------------------------');
|
||||
top3.forEach((result, index) => {
|
||||
console.log(`[AudiobookSearch] ${index + 1}. "${result.title}"`);
|
||||
console.log(`[AudiobookSearch] Indexer: ${result.indexer}${result.indexerId ? ` (ID: ${result.indexerId})` : ''}`);
|
||||
console.log(`[AudiobookSearch] `);
|
||||
console.log(`[AudiobookSearch] Base Score: ${result.score.toFixed(1)}/100`);
|
||||
console.log(`[AudiobookSearch] - Title/Author Match: ${result.breakdown.matchScore.toFixed(1)}/60`);
|
||||
console.log(`[AudiobookSearch] - Format Quality: ${result.breakdown.formatScore.toFixed(1)}/25 (${result.format || 'unknown'})`);
|
||||
console.log(`[AudiobookSearch] - Seeder Count: ${result.breakdown.seederScore.toFixed(1)}/15 (${result.seeders !== undefined ? result.seeders + ' seeders' : 'N/A for Usenet'})`);
|
||||
console.log(`[AudiobookSearch] `);
|
||||
console.log(`[AudiobookSearch] Bonus Points: +${result.bonusPoints.toFixed(1)}`);
|
||||
if (result.bonusModifiers.length > 0) {
|
||||
result.bonusModifiers.forEach(mod => {
|
||||
console.log(`[AudiobookSearch] - ${mod.reason}: +${mod.points.toFixed(1)}`);
|
||||
});
|
||||
}
|
||||
console.log(`[AudiobookSearch] `);
|
||||
console.log(`[AudiobookSearch] Final Score: ${result.finalScore.toFixed(1)}`);
|
||||
if (result.breakdown.notes.length > 0) {
|
||||
console.log(`[AudiobookSearch] Notes: ${result.breakdown.notes.join(', ')}`);
|
||||
}
|
||||
if (index < top3.length - 1) {
|
||||
console.log(`[AudiobookSearch] --------------------------------------------------------`);
|
||||
}
|
||||
logger.debug(`${index + 1}. "${result.title}"`, {
|
||||
indexer: result.indexer,
|
||||
indexerId: result.indexerId,
|
||||
baseScore: `${result.score.toFixed(1)}/100`,
|
||||
matchScore: `${result.breakdown.matchScore.toFixed(1)}/60`,
|
||||
formatScore: `${result.breakdown.formatScore.toFixed(1)}/25 (${result.format || 'unknown'})`,
|
||||
seederScore: `${result.breakdown.seederScore.toFixed(1)}/15 (${result.seeders !== undefined ? result.seeders + ' seeders' : 'N/A for Usenet'})`,
|
||||
bonusPoints: `+${result.bonusPoints.toFixed(1)}`,
|
||||
bonusModifiers: result.bonusModifiers.map(mod => `${mod.reason}: +${mod.points.toFixed(1)}`),
|
||||
finalScore: result.finalScore.toFixed(1),
|
||||
notes: result.breakdown.notes,
|
||||
});
|
||||
});
|
||||
console.log(`[AudiobookSearch] ========================================================`);
|
||||
logger.debug('========================================================');
|
||||
}
|
||||
|
||||
// Add rank position to each result
|
||||
@@ -141,7 +133,7 @@ export async function POST(request: NextRequest) {
|
||||
: 'No results found',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to search for torrents:', error);
|
||||
logger.error('Failed to search for torrents', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getAudibleService } from '@/lib/integrations/audible.service';
|
||||
import { enrichAudiobooksWithMatches } from '@/lib/utils/audiobook-matcher';
|
||||
import { getCurrentUser } from '@/lib/middleware/auth';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Audiobooks.Search');
|
||||
|
||||
/**
|
||||
* GET /api/audiobooks/search?q=query&page=1
|
||||
@@ -47,7 +50,7 @@ export async function GET(request: NextRequest) {
|
||||
hasMore: results.hasMore,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to search audiobooks:', error);
|
||||
logger.error('Failed to search audiobooks', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'SearchError',
|
||||
|
||||
@@ -8,6 +8,9 @@ import { prisma } from '@/lib/db';
|
||||
import bcrypt from 'bcrypt';
|
||||
import { generateAccessToken, generateRefreshToken } from '@/lib/utils/jwt';
|
||||
import { getEncryptionService } from '@/lib/services/encryption.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.AdminLogin');
|
||||
|
||||
/**
|
||||
* POST /api/auth/admin/login
|
||||
@@ -58,7 +61,7 @@ export async function POST(request: NextRequest) {
|
||||
const decryptedHash = encryptionService.decrypt(user.authToken || '');
|
||||
passwordValid = await bcrypt.compare(password, decryptedHash);
|
||||
} catch (error) {
|
||||
console.error('[AdminLogin] Password verification failed:', error);
|
||||
logger.error('Password verification failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'AuthenticationError',
|
||||
@@ -109,7 +112,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to authenticate admin user:', error);
|
||||
logger.error('Failed to authenticate admin user', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'AuthenticationError',
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { LocalAuthProvider } from '@/lib/services/auth/LocalAuthProvider';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.LocalLogin');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -25,30 +28,30 @@ export async function POST(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('[LocalLogin] Attempting login for username:', username);
|
||||
logger.info('Attempting login', { username });
|
||||
|
||||
const provider = new LocalAuthProvider();
|
||||
const result = await provider.handleCallback({ username, password });
|
||||
|
||||
if (!result.success) {
|
||||
if (result.requiresApproval) {
|
||||
console.log('[LocalLogin] Account pending approval:', username);
|
||||
logger.info('Account pending approval', { username });
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
pendingApproval: true,
|
||||
message: 'Account pending admin approval.',
|
||||
});
|
||||
}
|
||||
console.error('[LocalLogin] Login failed:', result.error);
|
||||
logger.error('Login failed', { error: result.error });
|
||||
return NextResponse.json(
|
||||
{ error: result.error },
|
||||
{ status: 401 }
|
||||
);
|
||||
}
|
||||
|
||||
console.log('[LocalLogin] Login successful for:', username);
|
||||
console.log('[LocalLogin] User data:', result.user);
|
||||
console.log('[LocalLogin] Token generated successfully');
|
||||
logger.info('Login successful', { username });
|
||||
logger.debug('User data', { user: result.user });
|
||||
logger.debug('Token generated successfully');
|
||||
|
||||
// Return tokens for login
|
||||
return NextResponse.json({
|
||||
@@ -58,7 +61,7 @@ export async function POST(request: NextRequest) {
|
||||
refreshToken: result.tokens!.refreshToken,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[LocalLogin] Error:', error);
|
||||
logger.error('Error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Login failed' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getAuthProvider } from '@/lib/services/auth';
|
||||
import { getBaseUrl } from '@/lib/utils/url';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.OIDC.Callback');
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
@@ -71,7 +74,7 @@ export async function GET(request: NextRequest) {
|
||||
if (result.isFirstLogin) {
|
||||
// First login - redirect to initializing page to show job progress
|
||||
redirectUrl = `${baseUrl}/setup/initializing#authData=${authDataEncoded}`;
|
||||
console.log('[OIDC Callback] First login detected - redirecting to initializing page');
|
||||
logger.info('First login detected - redirecting to initializing page');
|
||||
} else {
|
||||
// Normal login - redirect to login page with auth success
|
||||
redirectUrl = `${baseUrl}/login?auth=success#authData=${authDataEncoded}`;
|
||||
@@ -132,7 +135,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('[OIDC Callback] Authentication failed:', error);
|
||||
logger.error('Authentication failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
const errorMsg = error instanceof Error ? error.message : 'Authentication failed';
|
||||
return NextResponse.redirect(`${baseUrl}/login?error=${encodeURIComponent(errorMsg)}`);
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAuthProvider } from '@/lib/services/auth';
|
||||
import { getBaseUrl } from '@/lib/utils/url';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.OIDC.Login');
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
@@ -25,7 +28,7 @@ export async function GET() {
|
||||
// Redirect to OIDC provider
|
||||
return NextResponse.redirect(redirectUrl);
|
||||
} catch (error) {
|
||||
console.error('[OIDC Login] Failed to initiate login:', error);
|
||||
logger.error('Failed to initiate login', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
// Redirect to login page with error
|
||||
const baseUrl = getBaseUrl();
|
||||
|
||||
@@ -9,6 +9,9 @@ import { getEncryptionService } from '@/lib/services/encryption.service';
|
||||
import { getConfigService } from '@/lib/services/config.service';
|
||||
import { generateAccessToken, generateRefreshToken } from '@/lib/utils/jwt';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.PlexCallback');
|
||||
|
||||
/**
|
||||
* GET /api/auth/plex/callback?pinId=12345
|
||||
@@ -52,7 +55,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Validate user info
|
||||
if (!plexUser || !plexUser.id) {
|
||||
console.error('[Plex OAuth] Invalid user info received:', plexUser);
|
||||
logger.error('Invalid user info received', { plexUser });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'OAuthError',
|
||||
@@ -64,7 +67,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
if (!plexUser.username) {
|
||||
console.error('[Plex OAuth] Username missing from Plex user:', plexUser);
|
||||
logger.error('Username missing from Plex user', { plexUser });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'OAuthError',
|
||||
@@ -84,7 +87,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Verify server is configured
|
||||
if (!plexConfig.serverUrl || !plexConfig.authToken) {
|
||||
console.error('[Plex OAuth] Server not configured');
|
||||
logger.error('Server not configured');
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'ConfigurationError',
|
||||
@@ -99,7 +102,7 @@ export async function GET(request: NextRequest) {
|
||||
const serverMachineId = plexConfig.machineIdentifier;
|
||||
|
||||
if (!serverMachineId) {
|
||||
console.error('[Plex OAuth] machineIdentifier not found in configuration');
|
||||
logger.error('machineIdentifier not found in configuration');
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'ConfigurationError',
|
||||
@@ -109,7 +112,7 @@ export async function GET(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('[Plex OAuth] Using stored machineIdentifier:', serverMachineId);
|
||||
logger.debug('Using stored machineIdentifier', { serverMachineId });
|
||||
|
||||
// SECURITY: Verify user has access to the configured Plex server
|
||||
// This checks if the server appears in the user's list of accessible servers from plex.tv
|
||||
@@ -121,7 +124,7 @@ export async function GET(request: NextRequest) {
|
||||
);
|
||||
|
||||
if (!hasAccess) {
|
||||
console.warn('[Plex OAuth] User attempted to authenticate without server access:', {
|
||||
logger.warn('User attempted to authenticate without server access', {
|
||||
plexId: plexIdString,
|
||||
username: plexUser.username,
|
||||
serverMachineId,
|
||||
@@ -135,16 +138,16 @@ export async function GET(request: NextRequest) {
|
||||
);
|
||||
}
|
||||
|
||||
console.log('[Plex OAuth] User verified with server access:', plexUser.username);
|
||||
logger.info('User verified with server access', { username: plexUser.username });
|
||||
|
||||
// Check for Plex Home profiles
|
||||
const homeUsers = await plexService.getHomeUsers(authToken);
|
||||
console.log('[Plex OAuth] Found home users:', homeUsers.length);
|
||||
logger.debug('Found home users', { count: homeUsers.length });
|
||||
|
||||
// If multiple home users exist, redirect to profile selection
|
||||
// (Only show selection if there's more than just the main account)
|
||||
if (homeUsers.length > 1) {
|
||||
console.log('[Plex OAuth] Account has multiple home profiles, redirecting to profile selection');
|
||||
logger.info('Account has multiple home profiles, redirecting to profile selection');
|
||||
|
||||
// Detect if this is a browser request (mobile redirect) vs AJAX (desktop popup polling)
|
||||
const accept = request.headers.get('accept') || '';
|
||||
@@ -157,7 +160,7 @@ export async function GET(request: NextRequest) {
|
||||
(process.env.NODE_ENV === 'production' ? 'https' : 'http');
|
||||
const selectProfileUrl = `${protocol}://${host}/auth/select-profile?pinId=${pinId}`;
|
||||
|
||||
console.log('[Plex OAuth] Redirecting to profile selection:', selectProfileUrl);
|
||||
logger.debug('Redirecting to profile selection', { selectProfileUrl });
|
||||
|
||||
// Return HTML page with JavaScript to store token in sessionStorage and redirect
|
||||
const html = `
|
||||
@@ -197,7 +200,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
console.log('[Plex OAuth] Single profile or no additional profiles, continuing with main account authentication');
|
||||
logger.debug('Single profile or no additional profiles, continuing with main account authentication');
|
||||
|
||||
// No home users - continue with normal authentication flow using main account
|
||||
// Check if this is the first user (should be promoted to admin)
|
||||
@@ -248,8 +251,8 @@ export async function GET(request: NextRequest) {
|
||||
(process.env.NODE_ENV === 'production' ? 'https' : 'http');
|
||||
const redirectUrl = `${protocol}://${host}/login?auth=success`;
|
||||
|
||||
console.log('[Plex OAuth] Setting cookies for mobile auth...');
|
||||
console.log('[Plex OAuth] Redirect URL:', redirectUrl);
|
||||
logger.debug('Setting cookies for mobile auth');
|
||||
logger.debug('Redirect URL', { redirectUrl });
|
||||
|
||||
// Prepare user data
|
||||
const userDataJson = JSON.stringify({
|
||||
@@ -260,7 +263,7 @@ export async function GET(request: NextRequest) {
|
||||
role: user.role,
|
||||
avatarUrl: user.avatarUrl,
|
||||
});
|
||||
console.log('[Plex OAuth] Setting userData cookie:', userDataJson);
|
||||
logger.debug('Setting userData cookie', { userDataJson });
|
||||
|
||||
// Prepare auth data to pass via URL hash (fallback for mobile browsers that block cookies)
|
||||
const authData = {
|
||||
@@ -331,7 +334,7 @@ export async function GET(request: NextRequest) {
|
||||
path: '/',
|
||||
});
|
||||
|
||||
console.log('[Plex OAuth] Cookies set successfully, returning HTML redirect to:', redirectUrl);
|
||||
logger.debug('Cookies set successfully, returning HTML redirect', { redirectUrl });
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -351,7 +354,7 @@ export async function GET(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to complete Plex OAuth:', error);
|
||||
logger.error('Failed to complete Plex OAuth', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'OAuthError',
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getPlexService } from '@/lib/integrations/plex.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.Plex.HomeUsers');
|
||||
|
||||
/**
|
||||
* GET /api/auth/plex/home-users
|
||||
@@ -32,7 +35,7 @@ export async function GET(request: NextRequest) {
|
||||
users,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get home users:', error);
|
||||
logger.error('Failed to get home users', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'ServerError',
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getPlexService } from '@/lib/integrations/plex.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.PlexLogin');
|
||||
|
||||
/**
|
||||
* POST /api/auth/plex/login
|
||||
@@ -33,7 +36,7 @@ export async function POST(request: NextRequest) {
|
||||
authUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to initiate Plex OAuth:', error);
|
||||
logger.error('Failed to initiate Plex OAuth', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'OAuthError',
|
||||
|
||||
@@ -8,6 +8,9 @@ import { getPlexService } from '@/lib/integrations/plex.service';
|
||||
import { getEncryptionService } from '@/lib/services/encryption.service';
|
||||
import { generateAccessToken, generateRefreshToken } from '@/lib/utils/jwt';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.PlexSwitchProfile');
|
||||
|
||||
/**
|
||||
* POST /api/auth/plex/switch-profile
|
||||
@@ -77,7 +80,7 @@ export async function POST(request: NextRequest) {
|
||||
profileUsername = profileInfo.friendlyName || `User ${userId}`;
|
||||
profileEmail = profileInfo.email || null;
|
||||
profileThumb = profileInfo.thumb || null;
|
||||
console.log('[Profile Switch] Using provided profile info:', {
|
||||
logger.debug('Using provided profile info', {
|
||||
plexId: profilePlexId,
|
||||
username: profileUsername,
|
||||
});
|
||||
@@ -86,7 +89,7 @@ export async function POST(request: NextRequest) {
|
||||
const profileUser = await plexService.getUserInfo(profileToken);
|
||||
|
||||
if (!profileUser || !profileUser.id) {
|
||||
console.error('[Profile Switch] Failed to get profile user info');
|
||||
logger.error('Failed to get profile user info');
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'ServerError',
|
||||
@@ -100,7 +103,7 @@ export async function POST(request: NextRequest) {
|
||||
profileUsername = profileUser.username || `User ${userId}`;
|
||||
profileEmail = profileUser.email || null;
|
||||
profileThumb = profileUser.thumb || null;
|
||||
console.log('[Profile Switch] Using getUserInfo data:', {
|
||||
logger.debug('Using getUserInfo data', {
|
||||
plexId: profilePlexId,
|
||||
username: profileUsername,
|
||||
});
|
||||
@@ -134,7 +137,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[Profile Switch] User authenticated:', {
|
||||
logger.info('User authenticated', {
|
||||
id: user.id,
|
||||
plexId: user.plexId,
|
||||
username: user.plexUsername,
|
||||
@@ -167,7 +170,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to switch profile:', error);
|
||||
logger.error('Failed to switch profile', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'ServerError',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { ConfigurationService } from '@/lib/services/config.service';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.Providers');
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
@@ -58,7 +61,7 @@ export async function GET() {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Auth] Failed to fetch auth providers:', error);
|
||||
logger.error('Failed to fetch auth providers', { error: error instanceof Error ? error.message : String(error) });
|
||||
// Default to Plex mode if config can't be read
|
||||
const localLoginDisabled = process.env.DISABLE_LOCAL_LOGIN === 'true';
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { verifyRefreshToken, generateAccessToken } from '@/lib/utils/jwt';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.Refresh');
|
||||
|
||||
/**
|
||||
* POST /api/auth/refresh
|
||||
@@ -68,7 +71,7 @@ export async function POST(request: NextRequest) {
|
||||
expiresIn: 3600, // 1 hour in seconds
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh token:', error);
|
||||
logger.error('Failed to refresh token', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'RefreshError',
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { LocalAuthProvider } from '@/lib/services/auth/LocalAuthProvider';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Auth.Register');
|
||||
|
||||
// Rate limiting map (in production, use Redis)
|
||||
const registrationAttempts = new Map<string, { count: number; resetAt: number }>();
|
||||
@@ -74,7 +77,7 @@ export async function POST(request: NextRequest) {
|
||||
refreshToken: result.tokens!.refreshToken,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Registration] Error:', error);
|
||||
logger.error('Registration error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Registration failed' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getEncryptionService } from '@/lib/services/encryption.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BookDateConfig');
|
||||
|
||||
// GET: Fetch global BookDate configuration (excluding API key)
|
||||
// Any authenticated user can check if BookDate is configured
|
||||
@@ -24,7 +27,7 @@ async function getConfig(req: AuthenticatedRequest) {
|
||||
|
||||
return NextResponse.json({ config: safeConfig });
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Get config error:', error);
|
||||
logger.error('Get config error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to fetch configuration' },
|
||||
{ status: 500 }
|
||||
@@ -129,7 +132,7 @@ async function saveConfig(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Save config error:', error);
|
||||
logger.error('Save config error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to save configuration' },
|
||||
{ status: 500 }
|
||||
@@ -162,7 +165,7 @@ async function deleteConfig(req: AuthenticatedRequest) {
|
||||
return NextResponse.json({ success: true });
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Delete config error:', error);
|
||||
logger.error('Delete config error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to delete configuration' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -14,6 +14,9 @@ import {
|
||||
isAlreadyRequested,
|
||||
isAlreadySwiped,
|
||||
} from '@/lib/bookdate/helpers';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BookDate.Generate');
|
||||
|
||||
async function handler(req: AuthenticatedRequest) {
|
||||
try {
|
||||
@@ -54,7 +57,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
};
|
||||
|
||||
// Build prompt and call AI (same as recommendations endpoint, but doesn't check cache)
|
||||
console.log('[BookDate] Force generating new recommendations for user:', userId);
|
||||
logger.info('Force generating new recommendations for user', { userId });
|
||||
const prompt = await buildAIPrompt(userId, userPreferences);
|
||||
const aiResponse = await callAI(config.provider, config.model, config.apiKey, prompt);
|
||||
|
||||
@@ -62,7 +65,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
throw new Error('Invalid AI response format: missing recommendations array');
|
||||
}
|
||||
|
||||
console.log(`[BookDate] AI returned ${aiResponse.recommendations.length} recommendations`);
|
||||
logger.debug('AI returned recommendations', { count: aiResponse.recommendations.length });
|
||||
|
||||
// Match to Audnexus and filter
|
||||
const batchId = `batch_${Date.now()}`;
|
||||
@@ -88,14 +91,14 @@ async function handler(req: AuthenticatedRequest) {
|
||||
const audnexusMatch = await matchToAudnexus(rec.title, rec.author);
|
||||
|
||||
if (!audnexusMatch) {
|
||||
console.warn(`[BookDate] No Audnexus match: "${rec.title}" by ${rec.author}`);
|
||||
logger.warn('No Audnexus match', { title: rec.title, author: rec.author });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check again if in library with ASIN for exact matching
|
||||
// This catches books that might have different titles (e.g., "The Tenant" vs "The Tenant (Unabridged)")
|
||||
if (await isInLibrary(userId, audnexusMatch.title, audnexusMatch.author, audnexusMatch.asin)) {
|
||||
console.log(`[BookDate] Book "${audnexusMatch.title}" (ASIN: ${audnexusMatch.asin}) is in library, skipping`);
|
||||
logger.debug('Book is in library, skipping', { title: audnexusMatch.title, asin: audnexusMatch.asin });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -122,12 +125,12 @@ async function handler(req: AuthenticatedRequest) {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[BookDate] Match error for "${rec.title}":`, error);
|
||||
logger.warn('Match error', { title: rec.title, error: error instanceof Error ? error.message : String(error) });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[BookDate] Matched ${matched.length} new recommendations`);
|
||||
logger.info('Matched new recommendations', { count: matched.length });
|
||||
|
||||
if (matched.length === 0) {
|
||||
return NextResponse.json(
|
||||
@@ -163,7 +166,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Generate error:', error);
|
||||
logger.error('Generate error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error.message || 'Failed to generate new recommendations',
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getConfigService } from '@/lib/services/config.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BookDate.Preferences');
|
||||
|
||||
/**
|
||||
* GET /api/bookdate/preferences
|
||||
@@ -54,7 +57,7 @@ async function getPreferences(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Get BookDate preferences error:', error);
|
||||
logger.error('Get BookDate preferences error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to get preferences' },
|
||||
{ status: 500 }
|
||||
@@ -135,7 +138,7 @@ async function updatePreferences(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Update BookDate preferences error:', error);
|
||||
logger.error('Update BookDate preferences error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to update preferences' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -14,6 +14,9 @@ import {
|
||||
isAlreadyRequested,
|
||||
isAlreadySwiped,
|
||||
} from '@/lib/bookdate/helpers';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BookDate.Recommendations');
|
||||
|
||||
async function handler(req: AuthenticatedRequest) {
|
||||
try {
|
||||
@@ -75,7 +78,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
};
|
||||
|
||||
// Build prompt and call AI
|
||||
console.log('[BookDate] Generating new recommendations for user:', userId);
|
||||
logger.info('Generating new recommendations for user', { userId });
|
||||
const prompt = await buildAIPrompt(userId, userPreferences);
|
||||
const aiResponse = await callAI(config.provider, config.model, config.apiKey, prompt);
|
||||
|
||||
@@ -83,7 +86,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
throw new Error('Invalid AI response format: missing recommendations array');
|
||||
}
|
||||
|
||||
console.log(`[BookDate] AI returned ${aiResponse.recommendations.length} recommendations`);
|
||||
logger.debug('AI returned recommendations', { count: aiResponse.recommendations.length });
|
||||
|
||||
// Match to Audnexus and filter
|
||||
const batchId = `batch_${Date.now()}`;
|
||||
@@ -91,19 +94,19 @@ async function handler(req: AuthenticatedRequest) {
|
||||
|
||||
for (const rec of aiResponse.recommendations) {
|
||||
if (!rec.title || !rec.author) {
|
||||
console.warn('[BookDate] Skipping recommendation with missing title or author');
|
||||
logger.warn('Skipping recommendation with missing title or author');
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if already swiped
|
||||
if (await isAlreadySwiped(userId, rec.title, rec.author)) {
|
||||
console.log(`[BookDate] Skipping already swiped: "${rec.title}"`);
|
||||
logger.debug('Skipping already swiped', { title: rec.title });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if in library
|
||||
if (await isInLibrary(userId, rec.title, rec.author)) {
|
||||
console.log(`[BookDate] Skipping already in library: "${rec.title}"`);
|
||||
logger.debug('Skipping already in library', { title: rec.title });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -112,20 +115,20 @@ async function handler(req: AuthenticatedRequest) {
|
||||
const audnexusMatch = await matchToAudnexus(rec.title, rec.author);
|
||||
|
||||
if (!audnexusMatch) {
|
||||
console.warn(`[BookDate] No Audnexus match: "${rec.title}" by ${rec.author}`);
|
||||
logger.warn('No Audnexus match', { title: rec.title, author: rec.author });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check again if in library with ASIN for exact matching
|
||||
// This catches books that might have different titles (e.g., "The Tenant" vs "The Tenant (Unabridged)")
|
||||
if (await isInLibrary(userId, audnexusMatch.title, audnexusMatch.author, audnexusMatch.asin)) {
|
||||
console.log(`[BookDate] Book "${audnexusMatch.title}" (ASIN: ${audnexusMatch.asin}) is in library, skipping`);
|
||||
logger.debug('Book is in library, skipping', { title: audnexusMatch.title, asin: audnexusMatch.asin });
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if already requested
|
||||
if (await isAlreadyRequested(userId, audnexusMatch.asin)) {
|
||||
console.log(`[BookDate] Skipping already requested: "${rec.title}"`);
|
||||
logger.debug('Skipping already requested', { title: rec.title });
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -147,12 +150,12 @@ async function handler(req: AuthenticatedRequest) {
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[BookDate] Match error for "${rec.title}":`, error);
|
||||
logger.warn('Match error', { title: rec.title, error: error instanceof Error ? error.message : String(error) });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[BookDate] Matched ${matched.length} recommendations`);
|
||||
logger.info('Matched recommendations', { count: matched.length });
|
||||
|
||||
// Save to database
|
||||
if (matched.length > 0) {
|
||||
@@ -180,7 +183,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Recommendations error:', error);
|
||||
logger.error('Recommendations error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error.message || 'Failed to generate recommendations',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BookDateSwipe');
|
||||
|
||||
async function handler(req: AuthenticatedRequest) {
|
||||
try {
|
||||
@@ -97,7 +100,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`[BookDate] Created request for "${recommendation.title}"`);
|
||||
logger.info(`Created request for "${recommendation.title}"`);
|
||||
|
||||
// Trigger search job (same as regular request creation)
|
||||
const { getJobQueueService } = await import('@/lib/services/job-queue.service');
|
||||
@@ -108,11 +111,11 @@ async function handler(req: AuthenticatedRequest) {
|
||||
author: audiobook.author,
|
||||
});
|
||||
|
||||
console.log(`[BookDate] Triggered search job for request ${newRequest.id}`);
|
||||
logger.info(`Triggered search job for request ${newRequest.id}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[BookDate] Error creating request:', error);
|
||||
logger.error('Error creating request', { error: error instanceof Error ? error.message : String(error) });
|
||||
// Don't fail the swipe if request creation fails
|
||||
}
|
||||
}
|
||||
@@ -124,7 +127,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Swipe error:', error);
|
||||
logger.error('Swipe error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to record swipe' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BookDate.Swipes');
|
||||
|
||||
// DELETE: Clear all users' swipe history (Admin only)
|
||||
async function clearSwipes(req: AuthenticatedRequest) {
|
||||
@@ -16,7 +19,7 @@ async function clearSwipes(req: AuthenticatedRequest) {
|
||||
// Also clear all cached recommendations (since swipe history affects recommendations)
|
||||
await prisma.bookDateRecommendation.deleteMany({});
|
||||
|
||||
console.log('[BookDate] Admin cleared all swipe history and recommendations');
|
||||
logger.info('Admin cleared all swipe history and recommendations');
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
@@ -24,7 +27,7 @@ async function clearSwipes(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Clear swipes error:', error);
|
||||
logger.error('Clear swipes error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to clear swipe history' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BookDate.TestConnection');
|
||||
|
||||
async function authenticatedHandler(req: AuthenticatedRequest) {
|
||||
try {
|
||||
@@ -64,7 +67,7 @@ async function authenticatedHandler(req: AuthenticatedRequest) {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('[BookDate] OpenAI API error:', errorText);
|
||||
logger.error('OpenAI API error', { error: errorText });
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid OpenAI API key or connection failed' },
|
||||
{ status: 400 }
|
||||
@@ -108,7 +111,7 @@ async function authenticatedHandler(req: AuthenticatedRequest) {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('[BookDate] Claude API error:', errorText);
|
||||
logger.error('Claude API error', { error: errorText });
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid Claude API key or connection failed' },
|
||||
{ status: 400 }
|
||||
@@ -123,7 +126,7 @@ async function authenticatedHandler(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Test connection error:', error);
|
||||
logger.error('Test connection error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Connection test failed' },
|
||||
{ status: 500 }
|
||||
@@ -179,7 +182,7 @@ async function unauthenticatedHandler(req: NextRequest) {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('[BookDate] OpenAI API error:', errorText);
|
||||
logger.error('OpenAI API error', { error: errorText });
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid OpenAI API key or connection failed' },
|
||||
{ status: 400 }
|
||||
@@ -223,7 +226,7 @@ async function unauthenticatedHandler(req: NextRequest) {
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('[BookDate] Claude API error:', errorText);
|
||||
logger.error('Claude API error', { error: errorText });
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid Claude API key or connection failed' },
|
||||
{ status: 400 }
|
||||
@@ -238,7 +241,7 @@ async function unauthenticatedHandler(req: NextRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Test connection error:', error);
|
||||
logger.error('Test connection error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Connection test failed' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.BookDate.Undo');
|
||||
|
||||
async function handler(req: AuthenticatedRequest) {
|
||||
try {
|
||||
@@ -77,7 +80,7 @@ async function handler(req: AuthenticatedRequest) {
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('[BookDate] Undo error:', error);
|
||||
logger.error('Undo error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error.message || 'Failed to undo swipe' },
|
||||
{ status: 500 }
|
||||
|
||||
+4
-1
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Thumbnails');
|
||||
|
||||
const CACHE_DIR = '/app/cache/thumbnails';
|
||||
|
||||
@@ -60,7 +63,7 @@ export async function GET(
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[ThumbnailAPI] Error serving thumbnail:', error);
|
||||
logger.error('Error serving thumbnail', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: 'Internal server error' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getConfigService } from '@/lib/services/config.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Config.Category');
|
||||
|
||||
// GET /api/config/:category - Get all config for a category
|
||||
export async function GET(
|
||||
@@ -23,7 +26,7 @@ export async function GET(
|
||||
config,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Failed to get config for category:`, error);
|
||||
logger.error('Failed to get config for category', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to get configuration',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getConfigService, ConfigUpdate } from '@/lib/services/config.service';
|
||||
import { z } from 'zod';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Config');
|
||||
|
||||
const ConfigUpdateSchema = z.object({
|
||||
updates: z.array(
|
||||
@@ -35,7 +38,7 @@ export async function PUT(request: NextRequest) {
|
||||
updated: updates.length,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to update configuration:', error);
|
||||
logger.error('Failed to update configuration', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
@@ -69,7 +72,7 @@ export async function GET() {
|
||||
config: allConfig,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get all configuration:', error);
|
||||
logger.error('Failed to get all configuration', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Failed to get configuration',
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Health');
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
@@ -17,7 +20,7 @@ export async function GET() {
|
||||
database: 'connected',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Health check failed:', error);
|
||||
logger.error('Health check failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: 'unhealthy',
|
||||
|
||||
@@ -8,25 +8,28 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getSchedulerService } from '@/lib/services/scheduler.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Init');
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
console.log('[Init] Initializing application services...');
|
||||
logger.info('Initializing application services...');
|
||||
|
||||
// Initialize scheduler service
|
||||
const schedulerService = getSchedulerService();
|
||||
await schedulerService.start();
|
||||
|
||||
console.log('[Init] Application services initialized successfully');
|
||||
logger.info('Application services initialized successfully');
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Application services initialized',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Init] Failed to initialize services:', error);
|
||||
logger.error('Failed to initialize services', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
|
||||
@@ -11,8 +11,9 @@ import { prisma } from '@/lib/db';
|
||||
import { downloadEbook } from '@/lib/services/ebook-scraper';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const DEBUG_ENABLED = process.env.LOG_LEVEL === 'debug';
|
||||
const logger = RMABLogger.create('API.FetchEbook');
|
||||
|
||||
/**
|
||||
* Sanitize path component (same logic as file-organizer)
|
||||
@@ -135,19 +136,21 @@ export async function POST(
|
||||
audiobook.audibleAsin
|
||||
);
|
||||
|
||||
if (DEBUG_ENABLED) {
|
||||
console.log(`[FetchEbook] Request: ${id}, Title: "${audiobook.title}", Author: "${audiobook.author}"`);
|
||||
console.log(`[FetchEbook] Target path: ${targetPath}`);
|
||||
console.log(`[FetchEbook] Config: format=${preferredFormat}, baseUrl=${baseUrl}, flaresolverr=${flaresolverrUrl || 'none'}`);
|
||||
}
|
||||
logger.debug('Fetch e-book request', {
|
||||
requestId: id,
|
||||
title: audiobook.title,
|
||||
author: audiobook.author,
|
||||
targetPath,
|
||||
format: preferredFormat,
|
||||
baseUrl,
|
||||
flaresolverr: flaresolverrUrl || 'none'
|
||||
});
|
||||
|
||||
// Check if target directory exists
|
||||
try {
|
||||
await fs.access(targetPath);
|
||||
} catch {
|
||||
if (DEBUG_ENABLED) {
|
||||
console.log(`[FetchEbook] Target directory not found: ${targetPath}`);
|
||||
}
|
||||
logger.debug(`Target directory not found: ${targetPath}`);
|
||||
return NextResponse.json(
|
||||
{ error: 'Audiobook directory not found. Was the audiobook properly organized?' },
|
||||
{ status: 400 }
|
||||
@@ -167,21 +170,21 @@ export async function POST(
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
console.log(`[FetchEbook] Success: ${result.filePath ? path.basename(result.filePath) : 'unknown'} for "${audiobook.title}"`);
|
||||
logger.info(`E-book downloaded: ${result.filePath ? path.basename(result.filePath) : 'unknown'} for "${audiobook.title}"`);
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: `E-book downloaded: ${result.filePath ? path.basename(result.filePath) : 'unknown'}`,
|
||||
format: result.format,
|
||||
});
|
||||
} else {
|
||||
console.log(`[FetchEbook] Failed for "${audiobook.title}": ${result.error}`);
|
||||
logger.warn(`E-book download failed for "${audiobook.title}"`, { error: result.error });
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
message: result.error || 'E-book download failed',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[FetchEbook] Unexpected error:', error instanceof Error ? error.message : error);
|
||||
logger.error('Unexpected error', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Internal server error' },
|
||||
{ status: 500 }
|
||||
|
||||
@@ -8,6 +8,9 @@ import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getProwlarrService } from '@/lib/integrations/prowlarr.service';
|
||||
import { rankTorrents } from '@/lib/utils/ranking-algorithm';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.InteractiveSearch');
|
||||
|
||||
/**
|
||||
* POST /api/requests/[id]/interactive-search
|
||||
@@ -96,9 +99,9 @@ export async function POST(
|
||||
// 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}`);
|
||||
logger.info(`Searching ${enabledIndexerIds.length} enabled indexers`, { searchQuery });
|
||||
if (customTitle) {
|
||||
console.log(`[InteractiveSearch] Using custom search title (original: "${requestRecord.audiobook.title}")`);
|
||||
logger.debug('Using custom search title', { customTitle, originalTitle: requestRecord.audiobook.title });
|
||||
}
|
||||
|
||||
const results = await prowlarr.search(searchQuery, {
|
||||
@@ -106,7 +109,7 @@ export async function POST(
|
||||
maxResults: 100, // Increased limit for broader search
|
||||
});
|
||||
|
||||
console.log(`[InteractiveSearch] Found ${results.length} raw results for request ${id}`);
|
||||
logger.debug(`Found ${results.length} raw results`, { requestId: id });
|
||||
|
||||
if (results.length === 0) {
|
||||
return NextResponse.json({
|
||||
@@ -125,42 +128,30 @@ export async function POST(
|
||||
|
||||
// No threshold filtering for interactive search - show all results
|
||||
// User can see scores and make their own decision
|
||||
console.log(`[InteractiveSearch] Ranked ${rankedResults.length} results (no threshold filter - user decides)`);
|
||||
logger.debug(`Ranked ${rankedResults.length} results (no threshold filter - user decides)`);
|
||||
|
||||
// Log top 3 results with detailed score breakdown for debugging
|
||||
const top3 = rankedResults.slice(0, 3);
|
||||
if (top3.length > 0) {
|
||||
console.log(`[InteractiveSearch] ==================== RANKING DEBUG ====================`);
|
||||
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 ${rankedResults.length} total):`);
|
||||
console.log(`[InteractiveSearch] --------------------------------------------------------`);
|
||||
logger.debug('==================== RANKING DEBUG ====================');
|
||||
logger.debug('Search parameters', { searchQuery, requestedTitle: requestRecord.audiobook.title, requestedAuthor: requestRecord.audiobook.author });
|
||||
logger.debug(`Top ${top3.length} results (out of ${rankedResults.length} total)`);
|
||||
logger.debug('--------------------------------------------------------');
|
||||
top3.forEach((result, index) => {
|
||||
console.log(`[InteractiveSearch] ${index + 1}. "${result.title}"`);
|
||||
console.log(`[InteractiveSearch] Indexer: ${result.indexer}${result.indexerId ? ` (ID: ${result.indexerId})` : ''}`);
|
||||
console.log(`[InteractiveSearch] `);
|
||||
console.log(`[InteractiveSearch] Base Score: ${result.score.toFixed(1)}/100`);
|
||||
console.log(`[InteractiveSearch] - Title/Author Match: ${result.breakdown.matchScore.toFixed(1)}/60`);
|
||||
console.log(`[InteractiveSearch] - Format Quality: ${result.breakdown.formatScore.toFixed(1)}/25 (${result.format || 'unknown'})`);
|
||||
console.log(`[InteractiveSearch] - Seeder Count: ${result.breakdown.seederScore.toFixed(1)}/15 (${result.seeders} seeders)`);
|
||||
console.log(`[InteractiveSearch] `);
|
||||
console.log(`[InteractiveSearch] Bonus Points: +${result.bonusPoints.toFixed(1)}`);
|
||||
if (result.bonusModifiers.length > 0) {
|
||||
result.bonusModifiers.forEach(mod => {
|
||||
console.log(`[InteractiveSearch] - ${mod.reason}: +${mod.points.toFixed(1)}`);
|
||||
});
|
||||
}
|
||||
console.log(`[InteractiveSearch] `);
|
||||
console.log(`[InteractiveSearch] Final Score: ${result.finalScore.toFixed(1)}`);
|
||||
if (result.breakdown.notes.length > 0) {
|
||||
console.log(`[InteractiveSearch] Notes: ${result.breakdown.notes.join(', ')}`);
|
||||
}
|
||||
if (index < top3.length - 1) {
|
||||
console.log(`[InteractiveSearch] --------------------------------------------------------`);
|
||||
}
|
||||
logger.debug(`${index + 1}. "${result.title}"`, {
|
||||
indexer: result.indexer,
|
||||
indexerId: result.indexerId,
|
||||
baseScore: `${result.score.toFixed(1)}/100`,
|
||||
matchScore: `${result.breakdown.matchScore.toFixed(1)}/60`,
|
||||
formatScore: `${result.breakdown.formatScore.toFixed(1)}/25 (${result.format || 'unknown'})`,
|
||||
seederScore: `${result.breakdown.seederScore.toFixed(1)}/15 (${result.seeders} seeders)`,
|
||||
bonusPoints: `+${result.bonusPoints.toFixed(1)}`,
|
||||
bonusModifiers: result.bonusModifiers.map(mod => `${mod.reason}: +${mod.points.toFixed(1)}`),
|
||||
finalScore: result.finalScore.toFixed(1),
|
||||
notes: result.breakdown.notes,
|
||||
});
|
||||
});
|
||||
console.log(`[InteractiveSearch] ========================================================`);
|
||||
logger.debug('========================================================');
|
||||
}
|
||||
|
||||
// Add rank position to each result
|
||||
@@ -177,7 +168,7 @@ export async function POST(
|
||||
: 'No results found',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to perform interactive search:', error);
|
||||
logger.error('Failed to perform interactive search', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'SearchError',
|
||||
|
||||
@@ -7,6 +7,9 @@ import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getJobQueueService } from '@/lib/services/job-queue.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.ManualSearch');
|
||||
|
||||
/**
|
||||
* POST /api/requests/[id]/manual-search
|
||||
@@ -89,7 +92,7 @@ export async function POST(
|
||||
message: 'Manual search initiated',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to trigger manual search:', error);
|
||||
logger.error('Failed to trigger manual search', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'SearchError',
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.RequestById');
|
||||
|
||||
/**
|
||||
* GET /api/requests/[id]
|
||||
@@ -70,7 +73,7 @@ export async function GET(
|
||||
request: requestRecord,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get request:', error);
|
||||
logger.error('Failed to get request', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'FetchError',
|
||||
@@ -304,7 +307,7 @@ export async function PATCH(
|
||||
{ status: 400 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to update request:', error);
|
||||
logger.error('Failed to update request', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'UpdateError',
|
||||
@@ -351,7 +354,7 @@ export async function DELETE(
|
||||
message: 'Request deleted successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to delete request:', error);
|
||||
logger.error('Failed to delete request', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'DeleteError',
|
||||
|
||||
@@ -8,6 +8,9 @@ import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { getJobQueueService } from '@/lib/services/job-queue.service';
|
||||
import { TorrentResult } from '@/lib/utils/ranking-algorithm';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.SelectTorrent');
|
||||
|
||||
/**
|
||||
* POST /api/requests/[id]/select-torrent
|
||||
@@ -59,7 +62,7 @@ export async function POST(
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`[SelectTorrent] User selected torrent: ${torrent.title} for request ${id}`);
|
||||
logger.info(`User selected torrent: ${torrent.title}`, { requestId: id });
|
||||
|
||||
// Trigger download job with the selected torrent
|
||||
const jobQueue = getJobQueueService();
|
||||
@@ -93,7 +96,7 @@ export async function POST(
|
||||
message: 'Torrent download initiated',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to select torrent:', error);
|
||||
logger.error('Failed to select torrent', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'DownloadError',
|
||||
|
||||
@@ -9,6 +9,9 @@ import { prisma } from '@/lib/db';
|
||||
import { getJobQueueService } from '@/lib/services/job-queue.service';
|
||||
import { findPlexMatch } from '@/lib/utils/audiobook-matcher';
|
||||
import { z } from 'zod';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Requests');
|
||||
|
||||
const CreateRequestSchema = z.object({
|
||||
audiobook: z.object({
|
||||
@@ -138,7 +141,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Delete the existing failed/warn/cancelled request
|
||||
console.log(`[Requests] Deleting existing ${existingRequest.status} request ${existingRequest.id} to allow re-request`);
|
||||
logger.debug(`Deleting existing ${existingRequest.status} request ${existingRequest.id} to allow re-request`);
|
||||
await prisma.request.delete({
|
||||
where: { id: existingRequest.id },
|
||||
});
|
||||
@@ -181,7 +184,7 @@ export async function POST(request: NextRequest) {
|
||||
request: newRequest,
|
||||
}, { status: 201 });
|
||||
} catch (error) {
|
||||
console.error('Failed to create request:', error);
|
||||
logger.error('Failed to create request', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
if (error instanceof z.ZodError) {
|
||||
return NextResponse.json(
|
||||
@@ -255,7 +258,7 @@ export async function GET(request: NextRequest) {
|
||||
count: requests.length,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get requests:', error);
|
||||
logger.error('Failed to get requests', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'FetchError',
|
||||
|
||||
@@ -9,6 +9,9 @@ import bcrypt from 'bcrypt';
|
||||
import { generateAccessToken, generateRefreshToken } from '@/lib/utils/jwt';
|
||||
import { getEncryptionService } from '@/lib/services/encryption.service';
|
||||
import { getPlexService } from '@/lib/integrations/plex.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Setup.Complete');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -172,12 +175,12 @@ export async function POST(request: NextRequest) {
|
||||
const serverInfo = await plexService.testConnection(plex.url, plex.token);
|
||||
if (serverInfo.success && serverInfo.info?.machineIdentifier) {
|
||||
machineIdentifier = serverInfo.info.machineIdentifier;
|
||||
console.log('[Setup] Fetched machineIdentifier:', machineIdentifier);
|
||||
logger.debug('Fetched machineIdentifier', { machineIdentifier });
|
||||
} else {
|
||||
console.warn('[Setup] Could not fetch machineIdentifier');
|
||||
logger.warn('Could not fetch machineIdentifier');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Setup] Error fetching machineIdentifier:', error);
|
||||
logger.error('Error fetching machineIdentifier', { error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,7 +444,7 @@ export async function POST(request: NextRequest) {
|
||||
// BookDate configuration (optional, global for all users)
|
||||
// Note: libraryScope and customPrompt are now per-user settings, not required here
|
||||
if (bookdate && bookdate.provider && bookdate.apiKey && bookdate.model) {
|
||||
console.log('[Setup] Saving global BookDate configuration');
|
||||
logger.info('Saving global BookDate configuration');
|
||||
|
||||
const encryptionService = getEncryptionService();
|
||||
const encryptedApiKey = encryptionService.encrypt(bookdate.apiKey);
|
||||
@@ -478,9 +481,9 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
|
||||
console.log('[Setup] Global BookDate configuration saved');
|
||||
logger.debug('Global BookDate configuration saved');
|
||||
} else {
|
||||
console.log('[Setup] BookDate configuration skipped (missing provider, apiKey, or model)');
|
||||
logger.debug('BookDate configuration skipped (missing provider, apiKey, or model)');
|
||||
}
|
||||
|
||||
// Mark setup as complete
|
||||
@@ -502,9 +505,9 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
|
||||
console.log('[Setup] Auto jobs enabled');
|
||||
logger.debug('Auto jobs enabled');
|
||||
|
||||
console.log('[Setup] Configuration saved successfully');
|
||||
logger.info('Configuration saved successfully');
|
||||
|
||||
// Return response with tokens if admin user was created
|
||||
if (adminUser && accessToken && refreshToken) {
|
||||
@@ -530,7 +533,7 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Setup] Failed to save configuration:', error);
|
||||
logger.error('Failed to save configuration', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Setup.Status');
|
||||
|
||||
/**
|
||||
* GET /api/setup/status
|
||||
@@ -24,7 +27,7 @@ export async function GET(request: NextRequest) {
|
||||
});
|
||||
} catch (error) {
|
||||
// If database is not ready or table doesn't exist, setup is not complete
|
||||
console.error('[Setup Status] Check failed:', error);
|
||||
logger.error('Check failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json({
|
||||
setupComplete: false,
|
||||
});
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { QBittorrentService } from '@/lib/integrations/qbittorrent.service';
|
||||
import { SABnzbdService } from '@/lib/integrations/sabnzbd.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Setup.TestDownloadClient');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -80,7 +83,7 @@ export async function POST(request: NextRequest) {
|
||||
{ status: 400 }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('[Setup] Download client test failed:', error);
|
||||
logger.error('Download client test failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { Issuer } from 'openid-client';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Setup.TestOIDC');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -65,7 +68,7 @@ export async function POST(request: NextRequest) {
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Test OIDC] Discovery failed:', error);
|
||||
logger.error('Discovery failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
|
||||
// Determine error message
|
||||
let errorMessage = 'OIDC discovery failed';
|
||||
|
||||
@@ -6,21 +6,24 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Setup.TestPaths');
|
||||
|
||||
async function testPath(dirPath: string): Promise<boolean> {
|
||||
try {
|
||||
// Try to access the path
|
||||
try {
|
||||
await fs.access(dirPath);
|
||||
console.log(`[Setup] Path exists: ${dirPath}`);
|
||||
logger.debug('Path exists', { path: dirPath });
|
||||
} catch (accessError) {
|
||||
// Path doesn't exist, try to create it
|
||||
console.log(`[Setup] Path doesn't exist, creating: ${dirPath}`);
|
||||
logger.debug('Path does not exist, creating', { path: dirPath });
|
||||
try {
|
||||
await fs.mkdir(dirPath, { recursive: true });
|
||||
console.log(`[Setup] Successfully created path: ${dirPath}`);
|
||||
logger.debug('Successfully created path', { path: dirPath });
|
||||
} catch (mkdirError) {
|
||||
console.error(`[Setup] Failed to create path ${dirPath}:`, mkdirError);
|
||||
logger.error('Failed to create path', { path: dirPath, error: mkdirError instanceof Error ? mkdirError.message : String(mkdirError) });
|
||||
// If mkdir fails, it means the parent mount doesn't exist or isn't writable
|
||||
return false;
|
||||
}
|
||||
@@ -35,7 +38,7 @@ async function testPath(dirPath: string): Promise<boolean> {
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`[Setup] Path test failed for ${dirPath}:`, error);
|
||||
logger.error('Path test failed', { path: dirPath, error: error instanceof Error ? error.message : String(error) });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -81,7 +84,7 @@ export async function POST(request: NextRequest) {
|
||||
message: 'Directories are ready and writable (created if needed)',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Setup] Path validation failed:', error);
|
||||
logger.error('Path validation failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { getPlexService } from '@/lib/integrations/plex.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Setup.TestPlex');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -49,7 +52,7 @@ export async function POST(request: NextRequest) {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Setup] Plex test failed:', error);
|
||||
logger.error('Plex test failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { ProwlarrService } from '@/lib/integrations/prowlarr.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
|
||||
const logger = RMABLogger.create('API.Setup.TestProwlarr');
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
@@ -38,7 +41,7 @@ export async function POST(request: NextRequest) {
|
||||
})),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[Setup] Prowlarr test failed:', error);
|
||||
logger.error('Prowlarr test failed', { error: error instanceof Error ? error.message : String(error) });
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
|
||||
Reference in New Issue
Block a user