Skip to content

Commit b503493

Browse files
oxon1umclaude
andcommitted
Add debug mode setting with toggle in admin settings
- Add debugMode field to database schema - Add debugMode to runtime config and settings - Add toggle in admin settings page - Make all debug logging conditional on debugMode flag - Pass debug flag to LidarrClient Run: docker compose exec app npx prisma db push Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bca2cdc commit b503493

File tree

6 files changed

+61
-20
lines changed

6 files changed

+61
-20
lines changed

app/api/search/artist/[artistId]/route.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,20 @@ export async function GET(req: NextRequest, { params }: { params: Promise<{ arti
1818
return jsonError("Lidarr is not configured", 500);
1919
}
2020

21-
const lidarr = new LidarrClient(config.lidarrUrl, config.lidarrApiKey);
21+
const lidarr = new LidarrClient(config.lidarrUrl, config.lidarrApiKey, config.debugMode);
2222

2323
// Debug: log what we're looking up
24-
console.log("[artist-detail] Looking up artistId:", artistId);
24+
if (config.debugMode) console.log("[artist-detail] Looking up artistId:", artistId);
2525

2626
// Get artist details from lookup
2727
const artist = await lidarr.getArtistByForeignId(artistId);
28-
console.log("[artist-detail] Artist result:", artist);
28+
if (config.debugMode) console.log("[artist-detail] Artist result:", artist);
2929

3030
// Get albums (from lookup or from existing library)
3131
const albums = await lidarr.getAlbumsByArtistForeignId(artistId);
32-
console.log("[artist-detail] Albums result:", JSON.stringify(albums, null, 2));
32+
if (config.debugMode) console.log("[artist-detail] Albums result:", JSON.stringify(albums, null, 2));
3333
const existingAlbums = await lidarr.getExistingArtistAlbums(artistId);
34-
console.log("[artist-detail] Existing albums:", JSON.stringify(existingAlbums, null, 2));
34+
if (config.debugMode) console.log("[artist-detail] Existing albums:", JSON.stringify(existingAlbums, null, 2));
3535

3636
// Mark which albums are already in the library
3737
const existingAlbumIds = new Set(existingAlbums.map((a) => a.foreignAlbumId));

app/api/settings/route.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const settingsSchema = z.object({
1818
lidarrMetadataProfileId: z.number().int().positive().nullable().optional(),
1919
lidarrMonitorMode: z.string().min(1).nullable().optional(),
2020
requestAutoApprove: z.boolean().optional(),
21+
debugMode: z.boolean().optional(),
2122
testJellyfin: z.boolean().optional(),
2223
testLidarr: z.boolean().optional()
2324
});
@@ -93,6 +94,9 @@ export async function PUT(req: NextRequest) {
9394
if (Object.prototype.hasOwnProperty.call(payload, "requestAutoApprove")) {
9495
updateInput.requestAutoApprove = payload.requestAutoApprove;
9596
}
97+
if (Object.prototype.hasOwnProperty.call(payload, "debugMode")) {
98+
updateInput.debugMode = payload.debugMode;
99+
}
96100

97101
if (
98102
Object.prototype.hasOwnProperty.call(payload, "lidarrApiKey") &&

components/admin-settings-form.tsx

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type SettingsPayload = {
1717
lidarrMetadataProfileId: number | null;
1818
lidarrMonitorMode: string;
1919
requestAutoApprove: boolean;
20+
debugMode: boolean;
2021
};
2122

2223
type PasswordPayload = {
@@ -35,7 +36,8 @@ const defaultState: SettingsPayload = {
3536
lidarrQualityProfileId: null,
3637
lidarrMetadataProfileId: null,
3738
lidarrMonitorMode: "all",
38-
requestAutoApprove: true
39+
requestAutoApprove: true,
40+
debugMode: false
3941
};
4042

4143
const defaultPasswordState: PasswordPayload = {
@@ -85,7 +87,8 @@ export function AdminSettingsForm() {
8587
lidarrQualityProfileId: state.lidarrQualityProfileId,
8688
lidarrMetadataProfileId: state.lidarrMetadataProfileId,
8789
lidarrMonitorMode: state.lidarrMonitorMode?.trim() ? state.lidarrMonitorMode.trim() : "all",
88-
requestAutoApprove: state.requestAutoApprove
90+
requestAutoApprove: state.requestAutoApprove,
91+
debugMode: state.debugMode
8992
});
9093

9194
const sendUpdate = async (extra?: { testJellyfin?: boolean; testLidarr?: boolean }) => {
@@ -401,6 +404,31 @@ export function AdminSettingsForm() {
401404
</span>
402405
</button>
403406

407+
{/* Debug Mode Toggle */}
408+
<button
409+
type="button"
410+
aria-pressed={state.debugMode}
411+
onClick={() =>
412+
setState((prev) => ({ ...prev, debugMode: !prev.debugMode }))
413+
}
414+
className="flex w-full items-center gap-4 rounded-2xl border border-white/[0.08] bg-panel-2/30 p-5 text-left text-sm text-muted transition hover:border-white/[0.15] focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[rgba(86,177,255,0.15)]"
415+
>
416+
<span
417+
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border transition-all ${
418+
state.debugMode
419+
? "border-accent/60 bg-accent/20 text-accent"
420+
: "border-white/[0.15] bg-transparent text-transparent"
421+
}`}
422+
aria-hidden
423+
>
424+
<IconCheck className="h-4 w-4" />
425+
</span>
426+
<span className="leading-none">
427+
<span className="font-medium text-text">Debug mode</span>
428+
<span className="ml-2 text-xs text-muted">(Enable verbose logging)</span>
429+
</span>
430+
</button>
431+
404432
{/* Password Section */}
405433
{canManagePassword ? (
406434
<section className="space-y-4 rounded-2xl border border-white/[0.08] bg-panel-2/30 p-5">

lib/lidarr/client.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ const normalizeSong = (raw: unknown): LidarrSongSearchResult | null => {
179179
export class LidarrClient {
180180
constructor(
181181
private readonly baseUrl: string,
182-
private readonly apiKey: string
182+
private readonly apiKey: string,
183+
private readonly debug: boolean = false
183184
) {}
184185

185186
private async request<T>(path: string, init?: RequestInit): Promise<T> {
@@ -274,18 +275,18 @@ export class LidarrClient {
274275
async getArtistByForeignId(foreignArtistId: string): Promise<LidarrArtist | null> {
275276
// First try: check if artist is already in the library
276277
const existingArtist = await this.getExistingArtistByForeignId(foreignArtistId);
277-
console.log("[lidarr] getArtistByForeignId - existing artist:", existingArtist ? { id: existingArtist.id, name: existingArtist.artistName, imagesCount: existingArtist.images?.length } : null);
278+
if (this.debug) console.log("[lidarr] getArtistByForeignId - existing artist:", existingArtist ? { id: existingArtist.id, name: existingArtist.artistName, imagesCount: existingArtist.images?.length } : null);
278279
if (existingArtist) return existingArtist;
279280

280281
// Second try: search by term using the foreignArtistId
281282
const encoded = encodeURIComponent(foreignArtistId);
282283
const searchResults = await this.tryRequest<LidarrArtist[]>(`/api/v1/artist/lookup?term=${encoded}`);
283-
console.log("[lidarr] getArtistByForeignId - search results:", searchResults?.length ?? 0, "artists");
284+
if (this.debug) console.log("[lidarr] getArtistByForeignId - search results:", searchResults?.length ?? 0, "artists");
284285

285286
// Filter results to find matching artist by foreignArtistId
286287
if (searchResults && searchResults.length > 0) {
287288
const match = searchResults.find((a) => a.foreignArtistId === foreignArtistId);
288-
console.log("[lidarr] getArtistByForeignId - matched artist:", match ? { name: match.artistName, imagesCount: match.images?.length } : null);
289+
if (this.debug) console.log("[lidarr] getArtistByForeignId - matched artist:", match ? { name: match.artistName, imagesCount: match.images?.length } : null);
289290
if (match) return match;
290291
// Return first result if no exact match
291292
return searchResults[0];
@@ -298,34 +299,34 @@ export class LidarrClient {
298299
// First try: search by term
299300
const encoded = encodeURIComponent(foreignArtistId);
300301
let albums = await this.tryRequest<LidarrArtistAlbum[]>(`/api/v1/album/lookup?term=${encoded}`);
301-
console.log("[lidarr] getAlbumsByArtistForeignId - search results:", albums?.length ?? 0, "albums");
302+
if (this.debug) console.log("[lidarr] getAlbumsByArtistForeignId - search results:", albums?.length ?? 0, "albums");
302303

303304
// Filter to only albums matching the foreignArtistId
304305
if (albums && albums.length > 0) {
305306
const matching = albums.filter((a) => a.foreignArtistId === foreignArtistId);
306-
console.log("[lidarr] getAlbumsByArtistForeignId - filtered by foreignArtistId:", matching.length, "albums");
307+
if (this.debug) console.log("[lidarr] getAlbumsByArtistForeignId - filtered by foreignArtistId:", matching.length, "albums");
307308
if (matching.length > 0) return matching;
308309
}
309310

310311
if (!albums || albums.length === 0) {
311-
console.log("[lidarr] getAlbumsByArtistForeignId - no albums from search, trying fallback");
312+
if (this.debug) console.log("[lidarr] getAlbumsByArtistForeignId - no albums from search, trying fallback");
312313
// Fallback: search by artist name in existing albums
313314
const artist = await this.getArtistByForeignId(foreignArtistId);
314315
if (!artist) {
315-
console.log("[lidarr] getAlbumsByArtistForeignId - no artist found for fallback");
316+
if (this.debug) console.log("[lidarr] getAlbumsByArtistForeignId - no artist found for fallback");
316317
return [];
317318
}
318319

319-
console.log("[lidarr] getAlbumsByArtistForeignId - fallback artist:", artist.artistName);
320+
if (this.debug) console.log("[lidarr] getAlbumsByArtistForeignId - fallback artist:", artist.artistName);
320321
const allAlbums = await this.tryRequest<LidarrArtistAlbum[]>("/api/v1/album");
321-
console.log("[lidarr] getAlbumsByArtistForeignId - all albums in library:", allAlbums?.length ?? 0);
322+
if (this.debug) console.log("[lidarr] getAlbumsByArtistForeignId - all albums in library:", allAlbums?.length ?? 0);
322323

323324
if (!allAlbums) return [];
324325

325326
const filtered = allAlbums.filter(
326327
(album) => album.artistName?.toLowerCase() === artist.artistName.toLowerCase()
327328
);
328-
console.log("[lidarr] getAlbumsByArtistForeignId - fallback filtered:", filtered.length, "albums");
329+
if (this.debug) console.log("[lidarr] getAlbumsByArtistForeignId - fallback filtered:", filtered.length, "albums");
329330
return filtered;
330331
}
331332

lib/settings/store.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type RuntimeConfig = {
1515
lidarrMetadataProfileId: number | null;
1616
lidarrMonitorMode: string;
1717
requestAutoApprove: boolean;
18+
debugMode: boolean;
1819
};
1920

2021
const fromAppConfig = async (config: AppConfig): Promise<RuntimeConfig> => {
@@ -37,7 +38,8 @@ const fromAppConfig = async (config: AppConfig): Promise<RuntimeConfig> => {
3738
lidarrQualityProfileId: config.lidarrQualityProfileId,
3839
lidarrMetadataProfileId: config.lidarrMetadataProfileId,
3940
lidarrMonitorMode: config.lidarrMonitorMode ?? "all",
40-
requestAutoApprove: config.requestAutoApprove
41+
requestAutoApprove: config.requestAutoApprove,
42+
debugMode: config.debugMode ?? false
4143
};
4244
};
4345

@@ -55,7 +57,8 @@ export const getRuntimeConfig = async (): Promise<RuntimeConfig> => {
5557
lidarrQualityProfileId: env.lidarrQualityProfileId ?? base.lidarrQualityProfileId,
5658
lidarrMetadataProfileId: env.lidarrMetadataProfileId ?? base.lidarrMetadataProfileId,
5759
lidarrMonitorMode: env.lidarrMonitorMode ?? base.lidarrMonitorMode,
58-
requestAutoApprove: env.requestAutoApprove ?? base.requestAutoApprove
60+
requestAutoApprove: env.requestAutoApprove ?? base.requestAutoApprove,
61+
debugMode: base.debugMode
5962
};
6063
};
6164

@@ -70,6 +73,7 @@ export type UpdateConfigInput = {
7073
lidarrMetadataProfileId?: number | null;
7174
lidarrMonitorMode?: string | null;
7275
requestAutoApprove?: boolean;
76+
debugMode?: boolean;
7377
};
7478

7579
export const updateConfig = async (input: UpdateConfigInput): Promise<RuntimeConfig> => {
@@ -111,6 +115,9 @@ export const updateConfig = async (input: UpdateConfigInput): Promise<RuntimeCon
111115
if (Object.prototype.hasOwnProperty.call(input, "requestAutoApprove")) {
112116
updateData.requestAutoApprove = input.requestAutoApprove;
113117
}
118+
if (Object.prototype.hasOwnProperty.call(input, "debugMode")) {
119+
updateData.debugMode = input.debugMode;
120+
}
114121

115122
await prisma.appConfig.update({
116123
where: { id: 1 },

prisma/schema.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ model AppConfig {
9898
lidarrMetadataProfileId Int?
9999
lidarrMonitorMode String? @default("all")
100100
requestAutoApprove Boolean @default(true)
101+
debugMode Boolean @default(false)
101102
createdAt DateTime @default(now())
102103
updatedAt DateTime @updatedAt
103104
}

0 commit comments

Comments
 (0)