-
{displayName}
-
- Manage your {displayName} instances
-
+
+
+
+
+
+
+
+ No {displayName} instances yet
+
+
+
+
+ Get started by adding your first {displayName} instance to begin managing your media library.
+
+
+
-
- Add Instance
+
+ Add {displayName} Instance
-
-
-
-
No instances configured yet. Click "Add Instance" to get started.
-
diff --git a/src/routes/arr/[type]/[id]/+page.server.ts b/src/routes/arr/[type]/[id]/+page.server.ts
new file mode 100644
index 0000000..fabc117
--- /dev/null
+++ b/src/routes/arr/[type]/[id]/+page.server.ts
@@ -0,0 +1,42 @@
+import { error } from '@sveltejs/kit';
+import type { ServerLoad } from '@sveltejs/kit';
+import { arrInstancesQueries } from '$db/queries/arrInstances.ts';
+
+// Valid arr types
+const VALID_TYPES = ['radarr', 'sonarr', 'lidarr', 'chaptarr'];
+
+export const load: ServerLoad = ({ params }) => {
+ const type = params.type;
+ const id = parseInt(params.id || '', 10);
+
+ // Validate type
+ if (!type || !VALID_TYPES.includes(type)) {
+ error(404, `Invalid arr type: ${type}`);
+ }
+
+ // Validate ID
+ if (isNaN(id)) {
+ error(404, `Invalid instance ID: ${params.id}`);
+ }
+
+ // Fetch the specific instance
+ const instance = arrInstancesQueries.getById(id);
+
+ if (!instance) {
+ error(404, `Instance not found: ${id}`);
+ }
+
+ // Verify instance type matches route type
+ if (instance.type !== type) {
+ error(404, `Instance ${id} is not a ${type} instance`);
+ }
+
+ // Fetch all instances of this type for the tabs
+ const allInstances = arrInstancesQueries.getByType(type);
+
+ return {
+ type,
+ instance,
+ allInstances
+ };
+};
diff --git a/src/routes/arr/[type]/[id]/+page.svelte b/src/routes/arr/[type]/[id]/+page.svelte
new file mode 100644
index 0000000..7f2e9d8
--- /dev/null
+++ b/src/routes/arr/[type]/[id]/+page.svelte
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
Instance content for: {data.instance.name} (ID: {data.instance.id})
+
URL: {data.instance.url}
+
Type: {data.instance.type}
+
+
+
+
+
+
+
diff --git a/src/routes/arr/[type]/[id]/edit/+page.server.ts b/src/routes/arr/[type]/[id]/edit/+page.server.ts
new file mode 100644
index 0000000..5b7380b
--- /dev/null
+++ b/src/routes/arr/[type]/[id]/edit/+page.server.ts
@@ -0,0 +1,103 @@
+import { error, redirect, fail } from '@sveltejs/kit';
+import type { ServerLoad, Actions } from '@sveltejs/kit';
+import { arrInstancesQueries } from '$db/queries/arrInstances.ts';
+import { logger } from '$logger';
+
+// Valid arr types
+const VALID_TYPES = ['radarr', 'sonarr', 'lidarr', 'chaptarr'];
+
+export const load: ServerLoad = ({ params }) => {
+ const type = params.type;
+ const id = parseInt(params.id || '', 10);
+
+ // Validate type
+ if (!type || !VALID_TYPES.includes(type)) {
+ error(404, `Invalid arr type: ${type}`);
+ }
+
+ // Validate ID
+ if (isNaN(id)) {
+ error(404, `Invalid instance ID: ${params.id}`);
+ }
+
+ // Fetch the specific instance
+ const instance = arrInstancesQueries.getById(id);
+
+ if (!instance) {
+ error(404, `Instance not found: ${id}`);
+ }
+
+ // Verify instance type matches route type
+ if (instance.type !== type) {
+ error(404, `Instance ${id} is not a ${type} instance`);
+ }
+
+ return {
+ type,
+ instance
+ };
+};
+
+export const actions: Actions = {
+ delete: async ({ params }) => {
+ const type = params.type;
+ const id = parseInt(params.id || '', 10);
+
+ // Validate type
+ if (!type || !VALID_TYPES.includes(type)) {
+ await logger.warn('Delete failed: Invalid arr type', {
+ source: 'arr/[type]/[id]/edit',
+ meta: { type }
+ });
+ return fail(400, { error: 'Invalid arr type' });
+ }
+
+ // Validate ID
+ if (isNaN(id)) {
+ await logger.warn('Delete failed: Invalid instance ID', {
+ source: 'arr/[type]/[id]/edit',
+ meta: { id: params.id }
+ });
+ return fail(400, { error: 'Invalid instance ID' });
+ }
+
+ // Fetch the instance to verify it exists
+ const instance = arrInstancesQueries.getById(id);
+
+ if (!instance) {
+ await logger.warn('Delete failed: Instance not found', {
+ source: 'arr/[type]/[id]/edit',
+ meta: { id, type }
+ });
+ return fail(404, { error: 'Instance not found' });
+ }
+
+ // Verify instance type matches route type
+ if (instance.type !== type) {
+ await logger.warn('Delete failed: Instance type mismatch', {
+ source: 'arr/[type]/[id]/edit',
+ meta: { id, expectedType: type, actualType: instance.type }
+ });
+ return fail(400, { error: 'Instance type mismatch' });
+ }
+
+ // Delete the instance
+ const deleted = arrInstancesQueries.delete(id);
+
+ if (!deleted) {
+ await logger.error('Failed to delete instance', {
+ source: 'arr/[type]/[id]/edit',
+ meta: { id, name: instance.name, type: instance.type }
+ });
+ return fail(500, { error: 'Failed to delete instance' });
+ }
+
+ await logger.info(`Deleted ${type} instance: ${instance.name}`, {
+ source: 'arr/[type]/[id]/edit',
+ meta: { id, name: instance.name, type: instance.type, url: instance.url }
+ });
+
+ // Redirect to the arr type page
+ redirect(303, `/arr/${type}`);
+ }
+};
diff --git a/src/routes/arr/[type]/[id]/edit/+page.svelte b/src/routes/arr/[type]/[id]/edit/+page.svelte
new file mode 100644
index 0000000..5d5b87a
--- /dev/null
+++ b/src/routes/arr/[type]/[id]/edit/+page.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/routes/arr/new/+page.server.ts b/src/routes/arr/new/+page.server.ts
index 32d2d9d..3319f2f 100644
--- a/src/routes/arr/new/+page.server.ts
+++ b/src/routes/arr/new/+page.server.ts
@@ -83,7 +83,6 @@ export const actions = {
source: 'arr/new',
meta: { id, name, type, url }
});
-
} catch (error) {
await logger.error('Failed to create arr instance', {
source: 'arr/new',
diff --git a/src/routes/arr/new/+page.svelte b/src/routes/arr/new/+page.svelte
index 9041115..dc4b268 100644
--- a/src/routes/arr/new/+page.svelte
+++ b/src/routes/arr/new/+page.svelte
@@ -1,229 +1,12 @@
-
-
-
Add Arr Instance
-
- Configure a new Radarr, Sonarr, Lidarr, or Chaptarr instance
-
-
-
-
-
+
diff --git a/src/routes/settings/about/+page.svelte b/src/routes/settings/about/+page.svelte
index b8abc06..c9ee3eb 100644
--- a/src/routes/settings/about/+page.svelte
+++ b/src/routes/settings/about/+page.svelte
@@ -121,184 +121,194 @@
-
-
-
- |
- Version
- |
-
-
-
+
+
+ |
+ Version
+ |
+
+
+
+ v{data.version}
+
+
+
+ |
+
+
+
+
+ {#each sections as section (section.title)}
+
+ {#each section.rows as row (row.label)}
+
+ {/each}
+
+ {/each}
+
+
+ {#if data.migration.applied.length > 0}
+
+
+ |
- v{data.version}
-
-
-
- |
-
-
-
-
- {#each sections as section (section.title)}
-
- {#each section.rows as row (row.label)}
-
- {/each}
-
- {/each}
-
-
- {#if data.migration.applied.length > 0}
-
-
- |
- Migrations
- |
-
-
- {#each data.migration.applied as migration (migration.version)}
-
-
-
- v{migration.version}
-
-
- {migration.name}
+ Migrations
+ |
+
+
+ {#each data.migration.applied as migration (migration.version)}
+
+
+
+ v{migration.version}
+
+
+ {migration.name}
+
+ {#if migration.latest}
+
+ Latest
+
+ {/if}
+
+
+ {new Date(migration.applied_at).toLocaleDateString()}
- {#if migration.latest}
-
- Latest
-
- {/if}
-
- {new Date(migration.applied_at).toLocaleDateString()}
-
-
- {/each}
-
- |
-
-
- {/if}
+ {/each}
+
+ |
+
+
+ {/if}
-
- {#if data.releases.length > 0}
-
-
-
-
- {#each data.releases as release, index (release.tag_name)}
-
-
-
- {release.tag_name}
-
- {#if index === 0}
-
+ {#if data.releases.length > 0}
+
+
+
+
+ {#each data.releases as release, index (release.tag_name)}
+
+
+
+ {new Date(release.published_at).toLocaleDateString()}
+
-
- {new Date(release.published_at).toLocaleDateString()}
-
-
- {/each}
-
- |
-
-
- {/if}
+ {/each}
+
+ |
+
+
+ {/if}
-
-
-
-
-
-
Dev Team
-
+
+
+
+
+
+
Dev Team
+
-
-
-
-
-
-
- |
- Name
- |
-
- Remark
- |
-
- Tags
- |
-
-
-
- {#each devTeam as member (member.name)}
-
- |
- {member.name}
- |
-
- {#if member.remark}
- {member.remark}
- {:else}
- Remark pending - someone should probably ask them
- {/if}
- |
-
-
- {#each member.tags as tag}
-
- {tag}
-
- {/each}
-
- |
-
- {/each}
-
-
+
+
+
+
+
+
+ |
+ Name
+ |
+
+ Remark
+ |
+
+ Tags
+ |
+
+
+
+ {#each devTeam as member (member.name)}
+
+ |
+ {member.name}
+ |
+
+ {#if member.remark}
+ {member.remark}
+ {:else}
+ Remark pending - someone should probably ask them
+ {/if}
+ |
+
+
+ {#each member.tags as tag}
+
+ {tag}
+
+ {/each}
+
+ |
+
+ {/each}
+
+
+
-
-
-
-
- This project is dedicated to Faiza, for helping me find my heart.
-
-
+
+
+
+ This project is dedicated to Faiza, for helping me find my heart.
+
+
{/if}
diff --git a/src/utils/arr/README.md b/src/utils/arr/README.md
index e39f782..9c9851a 100644
--- a/src/utils/arr/README.md
+++ b/src/utils/arr/README.md
@@ -1,6 +1,6 @@
# Arr HTTP Client Utilities
-Object-oriented HTTP client architecture for communicating with *arr
+Object-oriented HTTP client architecture for communicating with \*arr
applications (Radarr, Sonarr, Lidarr, Chaptarr).
## Architecture
@@ -64,7 +64,7 @@ new BaseHttpClient(baseUrl: string, options?: HttpClientOptions)
### BaseArrClient (`src/utils/arr/base.ts`)
-Base client for all *arr applications. Extends `BaseHttpClient`.
+Base client for all \*arr applications. Extends `BaseHttpClient`.
**Features:**
@@ -86,9 +86,9 @@ new BaseArrClient(url: string, apiKey: string)
### Future Usage (when specific methods are implemented)
```typescript
-import { createArrClient } from "$utils/arr/factory.ts";
+import { createArrClient } from '$utils/arr/factory.ts';
-const radarr = createArrClient("radarr", "http://localhost:7878", "api-key");
+const radarr = createArrClient('radarr', 'http://localhost:7878', 'api-key');
// Get movies
const movies = await radarr.getMovies();
@@ -98,8 +98,8 @@ const profiles = await radarr.getQualityProfiles();
// Add movie
await radarr.addMovie({
- title: "Inception",
- tmdbId: 27205,
- qualityProfileId: 1,
+ title: 'Inception',
+ tmdbId: 27205,
+ qualityProfileId: 1
});
```
diff --git a/src/utils/arr/factory.ts b/src/utils/arr/factory.ts
index 8348a0d..827538d 100644
--- a/src/utils/arr/factory.ts
+++ b/src/utils/arr/factory.ts
@@ -12,11 +12,7 @@ import { ChaptarrClient } from './clients/chaptarr.ts';
* @param apiKey - API key for authentication
* @returns Arr client instance
*/
-export function createArrClient(
- type: ArrType,
- url: string,
- apiKey: string
-): BaseArrClient {
+export function createArrClient(type: ArrType, url: string, apiKey: string): BaseArrClient {
switch (type) {
case 'radarr':
return new RadarrClient(url, apiKey);
diff --git a/src/utils/config/config.ts b/src/utils/config/config.ts
index 8ddccf0..ab98660 100644
--- a/src/utils/config/config.ts
+++ b/src/utils/config/config.ts
@@ -52,7 +52,7 @@ class Config {
},
get database(): string {
return `${config.basePath}/data/profilarr.db`;
- },
+ }
};
}
diff --git a/src/utils/http/client.ts b/src/utils/http/client.ts
index c5895ce..56ade9e 100644
--- a/src/utils/http/client.ts
+++ b/src/utils/http/client.ts
@@ -113,10 +113,7 @@ export class BaseHttpClient {
}
// Wrap other errors
- throw new HttpError(
- error instanceof Error ? error.message : 'Unknown error',
- 0
- );
+ throw new HttpError(error instanceof Error ? error.message : 'Unknown error', 0);
}
} catch (error) {
// If it's not an HttpError or not retryable, throw immediately