mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 10:51:02 +01:00
feat: apply default delay profile to arrs when adding a new one
This commit is contained in:
@@ -31,6 +31,7 @@ import { migration as migration026 } from './migrations/026_create_upgrade_runs.
|
||||
import { migration as migration027 } from './migrations/027_create_rename_runs.ts';
|
||||
import { migration as migration028 } from './migrations/028_simplify_delay_profile_sync.ts';
|
||||
import { migration as migration029 } from './migrations/029_add_database_id_foreign_keys.ts';
|
||||
import { migration as migration030 } from './migrations/030_create_general_settings.ts';
|
||||
|
||||
export interface Migration {
|
||||
version: number;
|
||||
@@ -274,7 +275,8 @@ export function loadMigrations(): Migration[] {
|
||||
migration026,
|
||||
migration027,
|
||||
migration028,
|
||||
migration029
|
||||
migration029,
|
||||
migration030
|
||||
];
|
||||
|
||||
// Sort by version number
|
||||
|
||||
31
src/lib/server/db/migrations/030_create_general_settings.ts
Normal file
31
src/lib/server/db/migrations/030_create_general_settings.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { Migration } from '../migrations.ts';
|
||||
|
||||
/**
|
||||
* Create general_settings table for app-wide settings
|
||||
* Initial setting: apply_default_delay_profiles (ON by default)
|
||||
*/
|
||||
export const migration: Migration = {
|
||||
version: 30,
|
||||
name: 'Create general_settings table',
|
||||
|
||||
up: `
|
||||
-- General settings table (singleton pattern)
|
||||
CREATE TABLE IF NOT EXISTS general_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
|
||||
-- Default delay profile settings
|
||||
apply_default_delay_profiles INTEGER NOT NULL DEFAULT 1, -- 1=apply defaults when adding arr, 0=don't
|
||||
|
||||
-- Metadata
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Insert default row
|
||||
INSERT INTO general_settings (id) VALUES (1);
|
||||
`,
|
||||
|
||||
down: `
|
||||
DROP TABLE IF EXISTS general_settings;
|
||||
`
|
||||
};
|
||||
64
src/lib/server/db/queries/generalSettings.ts
Normal file
64
src/lib/server/db/queries/generalSettings.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { db } from '../db.ts';
|
||||
|
||||
/**
|
||||
* Types for general_settings table
|
||||
*/
|
||||
export interface GeneralSettings {
|
||||
id: number;
|
||||
apply_default_delay_profiles: number; // 1=true, 0=false
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface UpdateGeneralSettingsInput {
|
||||
applyDefaultDelayProfiles?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* All queries for general_settings table
|
||||
* Singleton pattern - only one settings record exists
|
||||
*/
|
||||
export const generalSettingsQueries = {
|
||||
/**
|
||||
* Get the general settings (singleton)
|
||||
*/
|
||||
get(): GeneralSettings | undefined {
|
||||
return db.queryFirst<GeneralSettings>('SELECT * FROM general_settings WHERE id = 1');
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if default delay profiles should be applied when adding arr
|
||||
*/
|
||||
shouldApplyDefaultDelayProfiles(): boolean {
|
||||
const settings = this.get();
|
||||
return settings?.apply_default_delay_profiles === 1;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update general settings
|
||||
*/
|
||||
update(input: UpdateGeneralSettingsInput): boolean {
|
||||
const updates: string[] = [];
|
||||
const params: (string | number)[] = [];
|
||||
|
||||
if (input.applyDefaultDelayProfiles !== undefined) {
|
||||
updates.push('apply_default_delay_profiles = ?');
|
||||
params.push(input.applyDefaultDelayProfiles ? 1 : 0);
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Add updated_at
|
||||
updates.push('updated_at = CURRENT_TIMESTAMP');
|
||||
params.push(1); // id is always 1
|
||||
|
||||
const affected = db.execute(
|
||||
`UPDATE general_settings SET ${updates.join(', ')} WHERE id = ?`,
|
||||
...params
|
||||
);
|
||||
|
||||
return affected > 0;
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
-- Profilarr Database Schema
|
||||
-- This file documents the current database schema after all migrations
|
||||
-- DO NOT execute this file directly - use migrations instead
|
||||
-- Last updated: 2026-01-21
|
||||
-- Last updated: 2026-01-22
|
||||
|
||||
-- ==============================================================================
|
||||
-- TABLE: migrations
|
||||
@@ -603,3 +603,20 @@ CREATE TABLE rename_runs (
|
||||
CREATE INDEX idx_rename_runs_instance ON rename_runs(instance_id);
|
||||
CREATE INDEX idx_rename_runs_started_at ON rename_runs(started_at DESC);
|
||||
CREATE INDEX idx_rename_runs_status ON rename_runs(status);
|
||||
|
||||
-- ==============================================================================
|
||||
-- TABLE: general_settings
|
||||
-- Purpose: Store general app-wide settings (singleton pattern with id=1)
|
||||
-- Migration: 030_create_general_settings.ts
|
||||
-- ==============================================================================
|
||||
|
||||
CREATE TABLE general_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
|
||||
-- Default delay profile settings
|
||||
apply_default_delay_profiles INTEGER NOT NULL DEFAULT 1, -- 1=apply defaults when adding arr, 0=don't
|
||||
|
||||
-- Metadata
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
64
src/lib/server/utils/arr/defaults.ts
Normal file
64
src/lib/server/utils/arr/defaults.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Default delay profiles for Radarr and Sonarr
|
||||
*
|
||||
* These are applied when a new arr instance is added (if enabled in general settings).
|
||||
* Values can be updated based on community feedback.
|
||||
*
|
||||
* Protocol configuration (maps to UI options):
|
||||
* - Prefer Usenet: enableUsenet=true, enableTorrent=true, preferredProtocol='usenet'
|
||||
* - Prefer Torrent: enableUsenet=true, enableTorrent=true, preferredProtocol='torrent'
|
||||
* - Only Usenet: enableUsenet=true, enableTorrent=false, preferredProtocol='usenet'
|
||||
* - Only Torrent: enableUsenet=false, enableTorrent=true, preferredProtocol='torrent'
|
||||
*
|
||||
* TODO: Get final values from Seraphys
|
||||
*/
|
||||
|
||||
import type { ArrDelayProfile } from './types.ts';
|
||||
|
||||
/**
|
||||
* Default delay profile for Radarr
|
||||
* Applied to the default profile (id=1) when adding a new Radarr instance
|
||||
*/
|
||||
export const RADARR_DEFAULT_DELAY_PROFILE: Omit<ArrDelayProfile, 'id' | 'order'> = {
|
||||
enableUsenet: true,
|
||||
enableTorrent: true,
|
||||
preferredProtocol: 'torrent',
|
||||
usenetDelay: 600,
|
||||
torrentDelay: 600,
|
||||
bypassIfHighestQuality: false,
|
||||
bypassIfAboveCustomFormatScore: false,
|
||||
minimumCustomFormatScore: 0,
|
||||
tags: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Default delay profile for Sonarr
|
||||
* Applied to the default profile (id=1) when adding a new Sonarr instance
|
||||
*/
|
||||
export const SONARR_DEFAULT_DELAY_PROFILE: Omit<ArrDelayProfile, 'id' | 'order'> = {
|
||||
enableUsenet: true,
|
||||
enableTorrent: true,
|
||||
preferredProtocol: 'torrent',
|
||||
usenetDelay: 600,
|
||||
torrentDelay: 600,
|
||||
bypassIfHighestQuality: false,
|
||||
bypassIfAboveCustomFormatScore: false,
|
||||
minimumCustomFormatScore: 0,
|
||||
tags: []
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the default delay profile for an arr type
|
||||
*/
|
||||
export function getDefaultDelayProfile(
|
||||
arrType: 'radarr' | 'sonarr'
|
||||
): Omit<ArrDelayProfile, 'id' | 'order'> {
|
||||
switch (arrType) {
|
||||
case 'radarr':
|
||||
return RADARR_DEFAULT_DELAY_PROFILE;
|
||||
case 'sonarr':
|
||||
return SONARR_DEFAULT_DELAY_PROFILE;
|
||||
default:
|
||||
throw new Error(`No default delay profile for arr type: ${arrType}`);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,10 @@
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import type { Actions } from '@sveltejs/kit';
|
||||
import { arrInstancesQueries } from '$db/queries/arrInstances.ts';
|
||||
import { generalSettingsQueries } from '$db/queries/generalSettings.ts';
|
||||
import { createArrClient } from '$arr/factory.ts';
|
||||
import { getDefaultDelayProfile } from '$arr/defaults.ts';
|
||||
import type { ArrType } from '$arr/types.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
|
||||
const VALID_TYPES = ['radarr', 'sonarr'];
|
||||
@@ -86,6 +90,35 @@ export const actions = {
|
||||
source: 'arr/new',
|
||||
meta: { id, name, type, url }
|
||||
});
|
||||
|
||||
// Apply default delay profile if enabled
|
||||
if (
|
||||
(type === 'radarr' || type === 'sonarr') &&
|
||||
generalSettingsQueries.shouldApplyDefaultDelayProfiles()
|
||||
) {
|
||||
try {
|
||||
const client = createArrClient(type as ArrType, url, apiKey);
|
||||
const defaultProfile = getDefaultDelayProfile(type);
|
||||
|
||||
// Update the default delay profile (id=1)
|
||||
await client.updateDelayProfile(1, {
|
||||
...defaultProfile,
|
||||
id: 1,
|
||||
order: 2147483647 // Default profile order
|
||||
});
|
||||
|
||||
await logger.info(`Applied default delay profile to ${name}`, {
|
||||
source: 'arr/new',
|
||||
meta: { id, type, profile: defaultProfile }
|
||||
});
|
||||
} catch (error) {
|
||||
// Log but don't fail - the instance was created successfully
|
||||
await logger.warn(`Failed to apply default delay profile to ${name}`, {
|
||||
source: 'arr/new',
|
||||
meta: { id, type, error: error instanceof Error ? error.message : error }
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
await logger.error('Failed to create arr instance', {
|
||||
source: 'arr/new',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { logSettingsQueries } from '$db/queries/logSettings.ts';
|
||||
import { backupSettingsQueries } from '$db/queries/backupSettings.ts';
|
||||
import { aiSettingsQueries } from '$db/queries/aiSettings.ts';
|
||||
import { tmdbSettingsQueries } from '$db/queries/tmdbSettings.ts';
|
||||
import { generalSettingsQueries } from '$db/queries/generalSettings.ts';
|
||||
import { logSettings } from '$logger/settings.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
|
||||
@@ -12,6 +13,7 @@ export const load = () => {
|
||||
const backupSetting = backupSettingsQueries.get();
|
||||
const aiSetting = aiSettingsQueries.get();
|
||||
const tmdbSetting = tmdbSettingsQueries.get();
|
||||
const generalSetting = generalSettingsQueries.get();
|
||||
|
||||
if (!logSetting) {
|
||||
throw new Error('Log settings not found in database');
|
||||
@@ -29,6 +31,10 @@ export const load = () => {
|
||||
throw new Error('TMDB settings not found in database');
|
||||
}
|
||||
|
||||
if (!generalSetting) {
|
||||
throw new Error('General settings not found in database');
|
||||
}
|
||||
|
||||
return {
|
||||
logSettings: {
|
||||
retention_days: logSetting.retention_days,
|
||||
@@ -52,6 +58,9 @@ export const load = () => {
|
||||
},
|
||||
tmdbSettings: {
|
||||
api_key: tmdbSetting.api_key
|
||||
},
|
||||
generalSettings: {
|
||||
apply_default_delay_profiles: generalSetting.apply_default_delay_profiles === 1
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -243,6 +252,32 @@ export const actions: Actions = {
|
||||
source: 'settings/general'
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
},
|
||||
|
||||
updateArrDefaults: async ({ request }: RequestEvent) => {
|
||||
const formData = await request.formData();
|
||||
|
||||
// Parse form data
|
||||
const applyDefaultDelayProfiles = formData.get('apply_default_delay_profiles') === 'on';
|
||||
|
||||
// Update settings
|
||||
const updated = generalSettingsQueries.update({
|
||||
applyDefaultDelayProfiles
|
||||
});
|
||||
|
||||
if (!updated) {
|
||||
await logger.error('Failed to update arr default settings', {
|
||||
source: 'settings/general'
|
||||
});
|
||||
return fail(500, { error: 'Failed to update settings' });
|
||||
}
|
||||
|
||||
await logger.info('Arr default settings updated', {
|
||||
source: 'settings/general',
|
||||
meta: { applyDefaultDelayProfiles }
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import AISettings from './components/AISettings.svelte';
|
||||
import TMDBSettings from './components/TMDBSettings.svelte';
|
||||
import UISettings from './components/UISettings.svelte';
|
||||
import ArrDefaultsSettings from './components/ArrDefaultsSettings.svelte';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
@@ -21,6 +22,9 @@
|
||||
<!-- UI Preferences -->
|
||||
<UISettings />
|
||||
|
||||
<!-- Arr Instance Defaults -->
|
||||
<ArrDefaultsSettings settings={data.generalSettings} />
|
||||
|
||||
<!-- Backup Configuration -->
|
||||
<BackupSettings settings={data.backupSettings} />
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { alertStore } from '$alerts/store';
|
||||
import { Save, Check } from 'lucide-svelte';
|
||||
import IconCheckbox from '$ui/form/IconCheckbox.svelte';
|
||||
import type { GeneralSettings } from './types';
|
||||
|
||||
export let settings: GeneralSettings;
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="rounded-lg border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="border-b border-neutral-200 px-6 py-4 dark:border-neutral-800">
|
||||
<h2 class="text-xl font-semibold text-neutral-900 dark:text-neutral-50">
|
||||
Arr Instance Defaults
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-neutral-600 dark:text-neutral-400">
|
||||
Configure default settings applied when adding new Radarr/Sonarr instances
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<form
|
||||
method="POST"
|
||||
action="?/updateArrDefaults"
|
||||
class="p-6"
|
||||
use:enhance={() => {
|
||||
return async ({ result, update }) => {
|
||||
if (result.type === 'failure' && result.data) {
|
||||
alertStore.add('error', (result.data as { error?: string }).error || 'Failed to save');
|
||||
} else if (result.type === 'success') {
|
||||
alertStore.add('success', 'Arr default settings saved successfully!');
|
||||
}
|
||||
await update();
|
||||
};
|
||||
}}
|
||||
>
|
||||
<div class="space-y-6">
|
||||
<!-- Delay Profile Defaults -->
|
||||
<div class="space-y-3">
|
||||
<h3 class="text-sm font-semibold text-neutral-900 dark:text-neutral-50">Delay Profiles</h3>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center gap-3">
|
||||
<IconCheckbox
|
||||
icon={Check}
|
||||
checked={settings.apply_default_delay_profiles}
|
||||
on:click={() =>
|
||||
(settings.apply_default_delay_profiles = !settings.apply_default_delay_profiles)}
|
||||
/>
|
||||
<input
|
||||
type="hidden"
|
||||
name="apply_default_delay_profiles"
|
||||
value={settings.apply_default_delay_profiles ? 'on' : ''}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="flex-1 text-left"
|
||||
on:click={() =>
|
||||
(settings.apply_default_delay_profiles = !settings.apply_default_delay_profiles)}
|
||||
>
|
||||
<span class="text-sm font-medium text-neutral-900 dark:text-neutral-50">
|
||||
Apply Default Delay Profile
|
||||
</span>
|
||||
<p class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||
Automatically configure the default delay profile when adding new arr instances
|
||||
</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex justify-end gap-3 border-t border-neutral-200 pt-6 dark:border-neutral-800">
|
||||
<button
|
||||
type="submit"
|
||||
class="flex items-center gap-2 rounded-lg bg-accent-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-accent-700 dark:bg-accent-500 dark:hover:bg-accent-600"
|
||||
>
|
||||
<Save size={16} />
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -28,3 +28,7 @@ export interface AISettings {
|
||||
export interface TMDBSettings {
|
||||
api_key: string;
|
||||
}
|
||||
|
||||
export interface GeneralSettings {
|
||||
apply_default_delay_profiles: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user