mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 12:50: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:
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user