refactor(pcd): mediaManagement now uses generated types, removed a fair bit of dead code

This commit is contained in:
Sam Chau
2026-01-28 00:31:51 +10:30
parent 745b1531cb
commit bb64b9ba9a
45 changed files with 437 additions and 672 deletions

View File

@@ -11,13 +11,29 @@
*/
import { Database } from '@jsr/db__sqlite';
import { columnTypeOverrides } from './pcd-type-overrides.ts';
// ============================================================================
// CONFIGURATION
// ============================================================================
const SCHEMA_REPO = 'Dictionarry-Hub/schema';
/**
* Manual type overrides for columns that store integers in the DB
* but need semantic string types for the UI/API layer.
*
* Columns with CHECK constraints don't need overrides - the generator
* parses those automatically. These are for Sonarr's integer enums.
*
* Runtime conversion functions are in: src/lib/shared/pcd/conversions.ts
*/
const COLUMN_TYPE_OVERRIDES: Record<string, string> = {
// Sonarr stores these as integers (0-5) but we want semantic strings in TS
'sonarr_naming.colon_replacement_format':
"'delete' | 'dash' | 'spaceDash' | 'spaceDashSpace' | 'smart' | 'custom'",
'sonarr_naming.multi_episode_style':
"'extend' | 'duplicate' | 'repeat' | 'scene' | 'range' | 'prefixedRange'"
};
const DEFAULT_VERSION = '1.0.0'; // Schema versions are branch names (e.g., 1.0.0, 1.1.0)
const SCHEMA_PATH = 'ops/0.schema.sql';
const OUTPUT_DIR = './src/lib/shared/pcd';
@@ -268,7 +284,7 @@ function getSemanticType(
): string {
// 1. Check for manual type override (for columns that store numbers but need string types)
const overrideKey = `${tableName}.${column.name}`;
const override = columnTypeOverrides[overrideKey];
const override = COLUMN_TYPE_OVERRIDES[overrideKey];
if (override) {
return nullable ? `(${override}) | null` : override;
}

View File

@@ -1,59 +0,0 @@
/**
* PCD Type Overrides
*
* Manual type overrides for columns that store numeric values in the DB
* but need semantic string types (because that's what the API expects).
*
* Format: 'table_name.column_name': 'TypeScript type'
*
* Note: Columns with CHECK constraints in the schema don't need overrides -
* the generator parses those automatically.
*/
export const columnTypeOverrides: Record<string, string> = {
// Sonarr stores these as integers (0-5) but API expects semantic strings
'sonarr_naming.colon_replacement_format':
"'delete' | 'dash' | 'spaceDash' | 'spaceDashSpace' | 'smart' | 'custom'",
'sonarr_naming.multi_episode_style':
"'extend' | 'duplicate' | 'repeat' | 'scene' | 'range' | 'prefixedRange'"
};
/**
* DB value mappings for converting between numeric DB values and semantic strings.
* Used by query read/write functions.
*/
export const colonReplacementFromDb: Record<number, string> = {
0: 'delete',
1: 'dash',
2: 'spaceDash',
3: 'spaceDashSpace',
4: 'smart',
5: 'custom'
};
export const colonReplacementToDb: Record<string, number> = {
delete: 0,
dash: 1,
spaceDash: 2,
spaceDashSpace: 3,
smart: 4,
custom: 5
};
export const multiEpisodeStyleFromDb: Record<number, string> = {
0: 'extend',
1: 'duplicate',
2: 'repeat',
3: 'scene',
4: 'range',
5: 'prefixedRange'
};
export const multiEpisodeStyleToDb: Record<string, number> = {
extend: 0,
duplicate: 1,
repeat: 2,
scene: 3,
range: 4,
prefixedRange: 5
};

View File

@@ -1,118 +0,0 @@
/**
* Combined getters for media management configs
* Provides backward compatibility for the syncer which expects combined objects
*
* NOTE: This returns the first available config for each type.
* The syncer will need to be updated later to support selecting specific named configs.
*/
import type { PCDCache } from '../../cache.ts';
import type { MediaSettings, RadarrNaming, SonarrNaming } from '$lib/shared/mediaManagement.ts';
import { getRadarrByName as getRadarrMediaSettings, getSonarrByName as getSonarrMediaSettings } from './media-settings/read.ts';
import { getRadarrByName as getRadarrNaming, getSonarrByName as getSonarrNaming } from './naming/read.ts';
import { getRadarrByName as getRadarrQualityDefs, getSonarrByName as getSonarrQualityDefs } from './quality-definitions/read.ts';
import { list as listMediaSettings } from './media-settings/read.ts';
import { list as listNaming } from './naming/read.ts';
import { list as listQualityDefs } from './quality-definitions/read.ts';
import type { QualityDefinitionEntry } from './quality-definitions/types.ts';
export interface QualityDefinition {
quality_name: string;
min_size: number;
max_size: number | null;
preferred_size: number | null;
}
export interface RadarrCombined {
mediaSettings: MediaSettings | null;
naming: RadarrNaming | null;
qualityDefinitions: QualityDefinition[];
}
export interface SonarrCombined {
mediaSettings: MediaSettings | null;
naming: SonarrNaming | null;
qualityDefinitions: QualityDefinition[];
}
/**
* Get all Radarr media management configs (returns first available of each type)
*/
export async function getRadarr(cache: PCDCache): Promise<RadarrCombined> {
// Get the first available media settings config
const mediaSettingsList = await listMediaSettings(cache);
const radarrMediaSettings = mediaSettingsList.find(c => c.arr_type === 'radarr');
const mediaSettings = radarrMediaSettings
? await getRadarrMediaSettings(cache, radarrMediaSettings.name)
: null;
// Get the first available naming config
const namingList = await listNaming(cache);
const radarrNaming = namingList.find(c => c.arr_type === 'radarr');
const naming = radarrNaming
? await getRadarrNaming(cache, radarrNaming.name)
: null;
// Get quality definitions (not yet refactored to multi-config)
const qualityDefinitions = await getQualityDefinitions(cache, 'radarr');
return {
mediaSettings,
naming,
qualityDefinitions
};
}
/**
* Get all Sonarr media management configs (returns first available of each type)
*/
export async function getSonarr(cache: PCDCache): Promise<SonarrCombined> {
// Get the first available media settings config
const mediaSettingsList = await listMediaSettings(cache);
const sonarrMediaSettings = mediaSettingsList.find(c => c.arr_type === 'sonarr');
const mediaSettings = sonarrMediaSettings
? await getSonarrMediaSettings(cache, sonarrMediaSettings.name)
: null;
// Get the first available naming config
const namingList = await listNaming(cache);
const sonarrNaming = namingList.find(c => c.arr_type === 'sonarr');
const naming = sonarrNaming
? await getSonarrNaming(cache, sonarrNaming.name)
: null;
// Get quality definitions (not yet refactored to multi-config)
const qualityDefinitions = await getQualityDefinitions(cache, 'sonarr');
return {
mediaSettings,
naming,
qualityDefinitions
};
}
/**
* Get quality definitions for an arr type (returns first available config)
*/
async function getQualityDefinitions(cache: PCDCache, arrType: 'radarr' | 'sonarr'): Promise<QualityDefinition[]> {
const qualityDefsList = await listQualityDefs(cache);
const config = qualityDefsList.find(c => c.arr_type === arrType);
if (!config) {
return [];
}
const getByName = arrType === 'radarr' ? getRadarrQualityDefs : getSonarrQualityDefs;
const fullConfig = await getByName(cache, config.name);
if (!fullConfig) {
return [];
}
return fullConfig.entries.map((entry: QualityDefinitionEntry) => ({
quality_name: entry.quality_name,
min_size: entry.min_size,
max_size: entry.max_size,
preferred_size: entry.preferred_size
}));
}

View File

@@ -4,11 +4,11 @@
import type { PCDCache } from '../../../cache.ts';
import { writeOperation, type OperationLayer } from '../../../writer.ts';
import type { PropersRepacks } from '$lib/shared/mediaManagement.ts';
import type { RadarrMediaSettingsRow } from '$shared/pcd/display.ts';
export interface CreateMediaSettingsInput {
name: string;
propersRepacks: PropersRepacks;
propersRepacks: RadarrMediaSettingsRow['propers_repacks'];
enableMediaInfo: boolean;
}

View File

@@ -2,8 +2,14 @@
* Media settings queries index
*/
export * from './types.ts';
export * from './read.ts';
export * from './create.ts';
export * from './update.ts';
export * from './remove.ts';
// Read
export { list, getRadarrByName, getSonarrByName } from './read.ts';
// Create
export { createRadarrMediaSettings, createSonarrMediaSettings } from './create.ts';
// Update
export { updateRadarrMediaSettings, updateSonarrMediaSettings } from './update.ts';
// Delete
export { removeRadarrMediaSettings, removeSonarrMediaSettings } from './delete.ts';

View File

@@ -3,8 +3,7 @@
*/
import type { PCDCache } from '../../../cache.ts';
import type { MediaSettings, PropersRepacks } from '$lib/shared/mediaManagement.ts';
import type { MediaSettingsListItem } from './types.ts';
import type { RadarrMediaSettingsRow, SonarrMediaSettingsRow, MediaSettingsListItem } from '$shared/pcd/display.ts';
export async function list(cache: PCDCache): Promise<MediaSettingsListItem[]> {
const db = cache.kb;
@@ -42,12 +41,12 @@ export async function list(cache: PCDCache): Promise<MediaSettingsListItem[]> {
export async function getRadarrByName(
cache: PCDCache,
name: string
): Promise<MediaSettings | null> {
): Promise<RadarrMediaSettingsRow | null> {
const db = cache.kb;
const row = await db
.selectFrom('radarr_media_settings')
.select(['name', 'propers_repacks', 'enable_media_info'])
.selectAll()
.where('name', '=', name)
.executeTakeFirst();
@@ -55,20 +54,22 @@ export async function getRadarrByName(
return {
name: row.name!,
propers_repacks: row.propers_repacks as PropersRepacks,
enable_media_info: row.enable_media_info === 1
propers_repacks: row.propers_repacks as RadarrMediaSettingsRow['propers_repacks'],
enable_media_info: row.enable_media_info === 1,
created_at: row.created_at,
updated_at: row.updated_at
};
}
export async function getSonarrByName(
cache: PCDCache,
name: string
): Promise<MediaSettings | null> {
): Promise<SonarrMediaSettingsRow | null> {
const db = cache.kb;
const row = await db
.selectFrom('sonarr_media_settings')
.select(['name', 'propers_repacks', 'enable_media_info'])
.selectAll()
.where('name', '=', name)
.executeTakeFirst();
@@ -76,7 +77,9 @@ export async function getSonarrByName(
return {
name: row.name!,
propers_repacks: row.propers_repacks as PropersRepacks,
enable_media_info: row.enable_media_info === 1
propers_repacks: row.propers_repacks as SonarrMediaSettingsRow['propers_repacks'],
enable_media_info: row.enable_media_info === 1,
created_at: row.created_at,
updated_at: row.updated_at
};
}

View File

@@ -1,13 +0,0 @@
/**
* Media settings query-specific types
*/
export type ArrType = 'radarr' | 'sonarr';
export interface MediaSettingsListItem {
name: string;
arr_type: ArrType;
propers_repacks: string;
enable_media_info: boolean;
updated_at: string;
}

View File

@@ -4,11 +4,11 @@
import type { PCDCache } from '../../../cache.ts';
import { writeOperation, type OperationLayer } from '../../../writer.ts';
import type { PropersRepacks } from '$lib/shared/mediaManagement.ts';
import type { RadarrMediaSettingsRow } from '$shared/pcd/display.ts';
export interface UpdateMediaSettingsInput {
name: string;
propersRepacks: PropersRepacks;
propersRepacks: RadarrMediaSettingsRow['propers_repacks'];
enableMediaInfo: boolean;
}

View File

@@ -4,8 +4,8 @@
import type { PCDCache } from '../../../cache.ts';
import { writeOperation, type OperationLayer } from '../../../writer.ts';
import type { RadarrColonReplacementFormat, ColonReplacementFormat, MultiEpisodeStyle } from '$lib/shared/mediaManagement.ts';
import { colonReplacementToDb, multiEpisodeStyleToDb } from '$lib/shared/mediaManagement.ts';
import type { RadarrNamingRow, SonarrNamingRow } from '$shared/pcd/display.ts';
import { colonReplacementToDb, multiEpisodeStyleToDb } from '$shared/pcd/conversions.ts';
export interface CreateRadarrNamingInput {
name: string;
@@ -13,7 +13,7 @@ export interface CreateRadarrNamingInput {
movieFormat: string;
movieFolderFormat: string;
replaceIllegalCharacters: boolean;
colonReplacementFormat: RadarrColonReplacementFormat;
colonReplacementFormat: RadarrNamingRow['colon_replacement_format'];
}
export interface CreateRadarrNamingOptions {
@@ -72,9 +72,9 @@ export interface CreateSonarrNamingInput {
seriesFolderFormat: string;
seasonFolderFormat: string;
replaceIllegalCharacters: boolean;
colonReplacementFormat: ColonReplacementFormat;
colonReplacementFormat: SonarrNamingRow['colon_replacement_format'];
customColonReplacementFormat: string | null;
multiEpisodeStyle: MultiEpisodeStyle;
multiEpisodeStyle: SonarrNamingRow['multi_episode_style'];
}
export interface CreateSonarrNamingOptions {

View File

@@ -2,8 +2,14 @@
* Naming queries index
*/
export * from './types.ts';
export * from './read.ts';
export * from './create.ts';
export * from './update.ts';
export * from './remove.ts';
// Read
export { list, getRadarrByName, getSonarrByName } from './read.ts';
// Create
export { createRadarrNaming, createSonarrNaming } from './create.ts';
// Update
export { updateRadarrNaming, updateSonarrNaming } from './update.ts';
// Delete
export { removeRadarrNaming, removeSonarrNaming } from './delete.ts';

View File

@@ -3,13 +3,8 @@
*/
import type { PCDCache } from '../../../cache.ts';
import type {
RadarrNaming,
SonarrNaming,
RadarrColonReplacementFormat
} from '$lib/shared/mediaManagement.ts';
import { colonReplacementFromDb, multiEpisodeStyleFromDb } from '$lib/shared/mediaManagement.ts';
import type { NamingListItem } from './types.ts';
import type { RadarrNamingRow, SonarrNamingRow, NamingListItem } from '$shared/pcd/display.ts';
import { colonReplacementFromDb, multiEpisodeStyleFromDb } from '$shared/pcd/conversions.ts';
// Note: name is PRIMARY KEY so never null, but Kysely types it as nullable
// because the generator doesn't detect non-INTEGER primary keys
@@ -48,19 +43,12 @@ export async function list(cache: PCDCache): Promise<NamingListItem[]> {
export async function getRadarrByName(
cache: PCDCache,
name: string
): Promise<RadarrNaming | null> {
): Promise<RadarrNamingRow | null> {
const db = cache.kb;
const row = await db
.selectFrom('radarr_naming')
.select([
'name',
'rename',
'movie_format',
'movie_folder_format',
'replace_illegal_characters',
'colon_replacement_format'
])
.selectAll()
.where('name', '=', name)
.executeTakeFirst();
@@ -72,31 +60,21 @@ export async function getRadarrByName(
movie_format: row.movie_format,
movie_folder_format: row.movie_folder_format,
replace_illegal_characters: row.replace_illegal_characters === 1,
colon_replacement_format: row.colon_replacement_format as RadarrColonReplacementFormat
colon_replacement_format: row.colon_replacement_format as RadarrNamingRow['colon_replacement_format'],
created_at: row.created_at,
updated_at: row.updated_at
};
}
export async function getSonarrByName(
cache: PCDCache,
name: string
): Promise<SonarrNaming | null> {
): Promise<SonarrNamingRow | null> {
const db = cache.kb;
const row = await db
.selectFrom('sonarr_naming')
.select([
'name',
'rename',
'standard_episode_format',
'daily_episode_format',
'anime_episode_format',
'series_folder_format',
'season_folder_format',
'replace_illegal_characters',
'colon_replacement_format',
'custom_colon_replacement_format',
'multi_episode_style'
])
.selectAll()
.where('name', '=', name)
.executeTakeFirst();
@@ -113,6 +91,8 @@ export async function getSonarrByName(
replace_illegal_characters: row.replace_illegal_characters === 1,
colon_replacement_format: colonReplacementFromDb(row.colon_replacement_format),
custom_colon_replacement_format: row.custom_colon_replacement_format,
multi_episode_style: multiEpisodeStyleFromDb(row.multi_episode_style)
multi_episode_style: multiEpisodeStyleFromDb(row.multi_episode_style),
created_at: row.created_at,
updated_at: row.updated_at
};
}

View File

@@ -1,12 +0,0 @@
/**
* Naming query-specific types
*/
export type ArrType = 'radarr' | 'sonarr';
export interface NamingListItem {
name: string;
arr_type: ArrType;
rename: boolean;
updated_at: string;
}

View File

@@ -4,8 +4,8 @@
import type { PCDCache } from '../../../cache.ts';
import { writeOperation, type OperationLayer } from '../../../writer.ts';
import type { ColonReplacementFormat, MultiEpisodeStyle, RadarrColonReplacementFormat } from '$lib/shared/mediaManagement.ts';
import { colonReplacementToDb, multiEpisodeStyleToDb } from '$lib/shared/mediaManagement.ts';
import type { RadarrNamingRow, SonarrNamingRow } from '$shared/pcd/display.ts';
import { colonReplacementToDb, multiEpisodeStyleToDb } from '$shared/pcd/conversions.ts';
export interface UpdateRadarrNamingInput {
name: string;
@@ -13,7 +13,7 @@ export interface UpdateRadarrNamingInput {
movieFormat: string;
movieFolderFormat: string;
replaceIllegalCharacters: boolean;
colonReplacementFormat: RadarrColonReplacementFormat;
colonReplacementFormat: RadarrNamingRow['colon_replacement_format'];
}
export interface UpdateRadarrNamingOptions {
@@ -76,9 +76,9 @@ export interface UpdateSonarrNamingInput {
seriesFolderFormat: string;
seasonFolderFormat: string;
replaceIllegalCharacters: boolean;
colonReplacementFormat: ColonReplacementFormat;
colonReplacementFormat: SonarrNamingRow['colon_replacement_format'];
customColonReplacementFormat: string | null;
multiEpisodeStyle: MultiEpisodeStyle;
multiEpisodeStyle: SonarrNamingRow['multi_episode_style'];
}
export interface UpdateSonarrNamingOptions {

View File

@@ -4,7 +4,7 @@
import type { PCDCache } from '../../../cache.ts';
import { writeOperation, type OperationLayer } from '../../../writer.ts';
import type { QualityDefinitionEntry } from './types.ts';
import type { QualityDefinitionEntry } from '$shared/pcd/display.ts';
export interface CreateQualityDefinitionsInput {
name: string;

View File

@@ -2,8 +2,14 @@
* Quality definitions queries
*/
export * from './types.ts';
export * from './read.ts';
export * from './create.ts';
export * from './update.ts';
export * from './remove.ts';
// Read
export { list, getRadarrByName, getSonarrByName, getAvailableQualities } from './read.ts';
// Create
export { createRadarrQualityDefinitions, createSonarrQualityDefinitions } from './create.ts';
// Update
export { updateRadarrQualityDefinitions, updateSonarrQualityDefinitions } from './update.ts';
// Delete
export { removeRadarrQualityDefinitions, removeSonarrQualityDefinitions } from './delete.ts';

View File

@@ -3,7 +3,12 @@
*/
import type { PCDCache } from '../../../cache.ts';
import type { QualityDefinitionListItem, QualityDefinitionsConfig, QualityDefinitionEntry, ArrType } from './types.ts';
import type { ArrType } from '$shared/pcd/types.ts';
import type {
QualityDefinitionListItem,
QualityDefinitionsConfig,
QualityDefinitionEntry
} from '$shared/pcd/display.ts';
/**
* Get available qualities for an arr type from quality_api_mappings

View File

@@ -1,24 +0,0 @@
/**
* Quality definitions types
*/
export type ArrType = 'radarr' | 'sonarr';
export interface QualityDefinitionListItem {
name: string;
arr_type: ArrType;
quality_count: number;
updated_at: string;
}
export interface QualityDefinitionEntry {
quality_name: string;
min_size: number;
max_size: number;
preferred_size: number;
}
export interface QualityDefinitionsConfig {
name: string;
entries: QualityDefinitionEntry[];
}

View File

@@ -4,7 +4,7 @@
import type { PCDCache } from '../../../cache.ts';
import { writeOperation, type OperationLayer } from '../../../writer.ts';
import type { QualityDefinitionEntry } from './types.ts';
import type { QualityDefinitionEntry } from '$shared/pcd/display.ts';
export interface UpdateQualityDefinitionsInput {
name: string;

View File

@@ -39,4 +39,4 @@ export { updateGeneral } from './updateGeneral.ts';
export { updateScoring } from './updateScoring.ts';
export { updateQualities } from './updateQualities.ts';
export { updateLanguages } from './updateLanguages.ts';
export { remove } from './remove.ts';
export { remove } from './delete.ts';

View File

@@ -17,12 +17,11 @@
import { BaseSyncer, type SyncResult } from '../base.ts';
import { arrSyncQueries } from '$db/queries/arrSync.ts';
import { getCache, type PCDCache } from '$pcd/cache.ts';
import type { QualityDefinition } from '$pcd/queries/mediaManagement/combined.ts';
import { getRadarrByName as getRadarrMediaSettings, getSonarrByName as getSonarrMediaSettings } from '$pcd/queries/mediaManagement/media-settings/read.ts';
import { getRadarrByName as getRadarrNaming, getSonarrByName as getSonarrNaming } from '$pcd/queries/mediaManagement/naming/read.ts';
import { getRadarrByName as getRadarrQualityDefs, getSonarrByName as getSonarrQualityDefs } from '$pcd/queries/mediaManagement/quality-definitions/read.ts';
import type { MediaSettings, RadarrNaming, SonarrNaming } from '$lib/shared/mediaManagement.ts';
import { colonReplacementToDb, multiEpisodeStyleToDb } from '$lib/shared/mediaManagement.ts';
import type { RadarrMediaSettingsRow, SonarrMediaSettingsRow, RadarrNamingRow, SonarrNamingRow } from '$shared/pcd/display.ts';
import { colonReplacementToDb, multiEpisodeStyleToDb } from '$shared/pcd/conversions.ts';
import type {
ArrType,
ArrPropersAndRepacks,
@@ -150,7 +149,7 @@ export class MediaManagementSyncer extends BaseSyncer {
}
// Fetch from PCD by config name
let mediaSettings: MediaSettings | null = null;
let mediaSettings: RadarrMediaSettingsRow | SonarrMediaSettingsRow | null = null;
if (this.instanceType === 'radarr') {
mediaSettings = await getRadarrMediaSettings(cache, configName);
} else if (this.instanceType === 'sonarr') {
@@ -337,14 +336,7 @@ export class MediaManagementSyncer extends BaseSyncer {
return false;
}
const pcdDefinitions: QualityDefinition[] = qualityDefsConfig.entries.map(entry => ({
quality_name: entry.quality_name,
min_size: entry.min_size,
max_size: entry.max_size,
preferred_size: entry.preferred_size
}));
if (pcdDefinitions.length === 0) {
if (qualityDefsConfig.entries.length === 0) {
await logger.debug(`Quality definitions config "${configName}" has no entries`, {
source: 'Sync:QualityDefinitions',
meta: { instanceId: this.instanceId, configName }
@@ -368,13 +360,13 @@ export class MediaManagementSyncer extends BaseSyncer {
// Update ARR definitions with PCD values
let updatedCount = 0;
for (const pcdDef of pcdDefinitions) {
for (const entry of qualityDefsConfig.entries) {
// Get the API name for this quality
const apiName = apiMappings.get(pcdDef.quality_name.toLowerCase());
const apiName = apiMappings.get(entry.quality_name.toLowerCase());
if (!apiName) {
await logger.debug(`No API mapping found for quality "${pcdDef.quality_name}"`, {
await logger.debug(`No API mapping found for quality "${entry.quality_name}"`, {
source: 'Sync:QualityDefinitions',
meta: { instanceId: this.instanceId, qualityName: pcdDef.quality_name }
meta: { instanceId: this.instanceId, qualityName: entry.quality_name }
});
continue;
}
@@ -390,10 +382,10 @@ export class MediaManagementSyncer extends BaseSyncer {
}
// Update the definition
// PCD uses 0 for "unlimited", Radarr API uses null
arrDef.minSize = pcdDef.min_size;
arrDef.maxSize = pcdDef.max_size === 0 ? null : pcdDef.max_size;
arrDef.preferredSize = pcdDef.preferred_size === 0 ? null : pcdDef.preferred_size;
// PCD stores 0 for "unlimited", arr API expects null
arrDef.minSize = entry.min_size;
arrDef.maxSize = entry.max_size === 0 ? null : entry.max_size;
arrDef.preferredSize = entry.preferred_size === 0 ? null : entry.preferred_size;
updatedCount++;
}

View File

@@ -1,289 +0,0 @@
/**
* Shared media management types and options
* Used by both UI and sync engine
*/
// ============================================================================
// PROPERS AND REPACKS
// ============================================================================
export type PropersRepacks = 'doNotPrefer' | 'preferAndUpgrade' | 'doNotUpgradeAutomatically';
// ============================================================================
// MEDIA SETTINGS
// ============================================================================
export interface MediaSettings {
name: string;
propers_repacks: PropersRepacks;
enable_media_info: boolean;
}
export const PROPERS_REPACKS_OPTIONS: {
value: PropersRepacks;
label: string;
description: string;
}[] = [
{
value: 'doNotPrefer',
label: 'Do Not Prefer',
description: 'Propers and repacks are not preferred over existing files'
},
{
value: 'preferAndUpgrade',
label: 'Prefer and Upgrade',
description: 'Automatically upgrade to propers and repacks when available'
},
{
value: 'doNotUpgradeAutomatically',
label: 'Do Not Upgrade Automatically',
description: 'Prefer propers/repacks but do not automatically upgrade'
}
];
/**
* Get the display label for a propers_repacks value
*/
export function getPropersRepacksLabel(value: PropersRepacks): string {
const option = PROPERS_REPACKS_OPTIONS.find((o) => o.value === value);
return option?.label ?? value;
}
// ============================================================================
// SONARR NAMING
// ============================================================================
export type ColonReplacementFormat =
| 'delete'
| 'dash'
| 'spaceDash'
| 'spaceDashSpace'
| 'smart'
| 'custom';
export const COLON_REPLACEMENT_OPTIONS: {
value: ColonReplacementFormat;
label: string;
}[] = [
{ value: 'delete', label: 'Delete' },
{ value: 'dash', label: 'Replace with Dash' },
{ value: 'spaceDash', label: 'Replace with Space Dash' },
{ value: 'spaceDashSpace', label: 'Replace with Space Dash Space' },
{ value: 'smart', label: 'Smart Replace' },
{ value: 'custom', label: 'Custom' }
];
export function getColonReplacementLabel(value: ColonReplacementFormat): string {
const option = COLON_REPLACEMENT_OPTIONS.find((o) => o.value === value);
return option?.label ?? value;
}
// Database stores as numbers: 0=delete, 1=dash, 2=spaceDash, 3=spaceDashSpace, 4=smart, 5=custom
const COLON_REPLACEMENT_NUM_MAP: Record<number, ColonReplacementFormat> = {
0: 'delete',
1: 'dash',
2: 'spaceDash',
3: 'spaceDashSpace',
4: 'smart',
5: 'custom'
};
const COLON_REPLACEMENT_STR_MAP: Record<ColonReplacementFormat, number> = {
delete: 0,
dash: 1,
spaceDash: 2,
spaceDashSpace: 3,
smart: 4,
custom: 5
};
export function colonReplacementFromDb(value: number): ColonReplacementFormat {
return COLON_REPLACEMENT_NUM_MAP[value] ?? 'delete';
}
export function colonReplacementToDb(value: ColonReplacementFormat): number {
return COLON_REPLACEMENT_STR_MAP[value] ?? 0;
}
export type MultiEpisodeStyle =
| 'extend'
| 'duplicate'
| 'repeat'
| 'scene'
| 'range'
| 'prefixedRange';
export const MULTI_EPISODE_STYLE_OPTIONS: {
value: MultiEpisodeStyle;
label: string;
}[] = [
{ value: 'extend', label: 'Extend' },
{ value: 'duplicate', label: 'Duplicate' },
{ value: 'repeat', label: 'Repeat' },
{ value: 'scene', label: 'Scene' },
{ value: 'range', label: 'Range' },
{ value: 'prefixedRange', label: 'Prefixed Range' }
];
export function getMultiEpisodeStyleLabel(value: MultiEpisodeStyle): string {
const option = MULTI_EPISODE_STYLE_OPTIONS.find((o) => o.value === value);
return option?.label ?? value;
}
// Database stores as numbers: 0=extend, 1=duplicate, 2=repeat, 3=scene, 4=range, 5=prefixedRange
const MULTI_EPISODE_NUM_MAP: Record<number, MultiEpisodeStyle> = {
0: 'extend',
1: 'duplicate',
2: 'repeat',
3: 'scene',
4: 'range',
5: 'prefixedRange'
};
const MULTI_EPISODE_STR_MAP: Record<MultiEpisodeStyle, number> = {
extend: 0,
duplicate: 1,
repeat: 2,
scene: 3,
range: 4,
prefixedRange: 5
};
export function multiEpisodeStyleFromDb(value: number): MultiEpisodeStyle {
return MULTI_EPISODE_NUM_MAP[value] ?? 'extend';
}
export function multiEpisodeStyleToDb(value: MultiEpisodeStyle): number {
return MULTI_EPISODE_STR_MAP[value] ?? 0;
}
// Radarr colon replacement (no custom option)
export type RadarrColonReplacementFormat =
| 'delete'
| 'dash'
| 'spaceDash'
| 'spaceDashSpace'
| 'smart';
export const RADARR_COLON_REPLACEMENT_OPTIONS: {
value: RadarrColonReplacementFormat;
label: string;
}[] = [
{ value: 'delete', label: 'Delete' },
{ value: 'dash', label: 'Replace with Dash' },
{ value: 'spaceDash', label: 'Replace with Space Dash' },
{ value: 'spaceDashSpace', label: 'Replace with Space Dash Space' },
{ value: 'smart', label: 'Smart Replace' }
];
export function getRadarrColonReplacementLabel(value: RadarrColonReplacementFormat): string {
const option = RADARR_COLON_REPLACEMENT_OPTIONS.find((o) => o.value === value);
return option?.label ?? value;
}
export function radarrColonReplacementFromDb(value: number): RadarrColonReplacementFormat {
const map: Record<number, RadarrColonReplacementFormat> = {
0: 'delete',
1: 'dash',
2: 'spaceDash',
3: 'spaceDashSpace',
4: 'smart'
};
return map[value] ?? 'delete';
}
export function radarrColonReplacementToDb(value: RadarrColonReplacementFormat): number {
const map: Record<RadarrColonReplacementFormat, number> = {
delete: 0,
dash: 1,
spaceDash: 2,
spaceDashSpace: 3,
smart: 4
};
return map[value] ?? 0;
}
export interface RadarrNaming {
name: string;
rename: boolean;
movie_format: string;
movie_folder_format: string;
replace_illegal_characters: boolean;
colon_replacement_format: RadarrColonReplacementFormat;
}
export interface SonarrNaming {
name: string;
rename: boolean;
replace_illegal_characters: boolean;
colon_replacement_format: ColonReplacementFormat;
custom_colon_replacement_format: string | null;
standard_episode_format: string;
daily_episode_format: string;
anime_episode_format: string;
series_folder_format: string;
season_folder_format: string;
multi_episode_style: MultiEpisodeStyle;
}
// ============================================================================
// QUALITY DEFINITION RESOLUTION GROUPS
// ============================================================================
export type ResolutionGroup = 'SD' | '720p' | '1080p' | '2160p' | 'Prereleases' | 'Other';
export const RESOLUTION_GROUP_ORDER: ResolutionGroup[] = [
'2160p',
'1080p',
'720p',
'SD',
'Prereleases',
'Other'
];
export const RESOLUTION_GROUP_LABELS: Record<ResolutionGroup, string> = {
'2160p': '4K Ultra HD (2160p)',
'1080p': 'Full HD (1080p)',
'720p': 'HD (720p)',
SD: 'Standard Definition (SD)',
Prereleases: 'Prereleases',
Other: 'Other'
};
// Qualities that belong to Prereleases group
const PRERELEASE_QUALITIES = ['cam', 'dvdscr', 'regional', 'telecine', 'telesync', 'workprint'];
// Qualities that belong to Other group
const OTHER_QUALITIES = ['raw-hd', 'unknown'];
/**
* Determine the resolution group from a quality name
* Parses names like "Bluray-1080p", "WEBDL-720p", "HDTV-2160p", etc.
*/
export function getResolutionGroup(qualityName: string): ResolutionGroup {
const name = qualityName.toLowerCase();
// Check for prereleases first
if (PRERELEASE_QUALITIES.some((q) => name === q || name.includes(q))) {
return 'Prereleases';
}
// Check for other/unknown
if (OTHER_QUALITIES.some((q) => name === q || name.includes(q))) {
return 'Other';
}
// Check by resolution
if (name.includes('2160') || name.includes('4k') || name.includes('uhd')) {
return '2160p';
}
if (name.includes('1080')) {
return '1080p';
}
if (name.includes('720')) {
return '720p';
}
// Everything else is SD (480p, SDTV, DVD, etc.)
return 'SD';
}

View File

@@ -0,0 +1,187 @@
/**
* PCD Value Conversions
*
* Runtime conversion functions for columns that store integers in the DB
* but need semantic string values for the API/UI.
*
* Note: These are only needed for Sonarr's quirky integer-based enums.
* Radarr stores strings directly.
*/
// ============================================================================
// SONARR COLON REPLACEMENT FORMAT
// ============================================================================
// DB stores: 0, 1, 2, 3, 4, 5
// API expects: 'delete', 'dash', 'spaceDash', 'spaceDashSpace', 'smart', 'custom'
export type SonarrColonReplacementFormat =
| 'delete'
| 'dash'
| 'spaceDash'
| 'spaceDashSpace'
| 'smart'
| 'custom';
const COLON_REPLACEMENT_FROM_DB: Record<number, SonarrColonReplacementFormat> = {
0: 'delete',
1: 'dash',
2: 'spaceDash',
3: 'spaceDashSpace',
4: 'smart',
5: 'custom'
};
const COLON_REPLACEMENT_TO_DB: Record<SonarrColonReplacementFormat, number> = {
delete: 0,
dash: 1,
spaceDash: 2,
spaceDashSpace: 3,
smart: 4,
custom: 5
};
export function colonReplacementFromDb(value: number): SonarrColonReplacementFormat {
return COLON_REPLACEMENT_FROM_DB[value] ?? 'delete';
}
export function colonReplacementToDb(value: SonarrColonReplacementFormat): number {
return COLON_REPLACEMENT_TO_DB[value] ?? 0;
}
// UI options for Sonarr colon replacement
export const SONARR_COLON_REPLACEMENT_OPTIONS: {
value: SonarrColonReplacementFormat;
label: string;
}[] = [
{ value: 'delete', label: 'Delete' },
{ value: 'dash', label: 'Replace with Dash' },
{ value: 'spaceDash', label: 'Replace with Space Dash' },
{ value: 'spaceDashSpace', label: 'Replace with Space Dash Space' },
{ value: 'smart', label: 'Smart Replace' },
{ value: 'custom', label: 'Custom' }
];
export function getColonReplacementLabel(value: SonarrColonReplacementFormat): string {
const option = SONARR_COLON_REPLACEMENT_OPTIONS.find((o) => o.value === value);
return option?.label ?? value;
}
// ============================================================================
// SONARR MULTI-EPISODE STYLE
// ============================================================================
// DB stores: 0, 1, 2, 3, 4, 5
// API expects: 'extend', 'duplicate', 'repeat', 'scene', 'range', 'prefixedRange'
export type MultiEpisodeStyle =
| 'extend'
| 'duplicate'
| 'repeat'
| 'scene'
| 'range'
| 'prefixedRange';
const MULTI_EPISODE_FROM_DB: Record<number, MultiEpisodeStyle> = {
0: 'extend',
1: 'duplicate',
2: 'repeat',
3: 'scene',
4: 'range',
5: 'prefixedRange'
};
const MULTI_EPISODE_TO_DB: Record<MultiEpisodeStyle, number> = {
extend: 0,
duplicate: 1,
repeat: 2,
scene: 3,
range: 4,
prefixedRange: 5
};
export function multiEpisodeStyleFromDb(value: number): MultiEpisodeStyle {
return MULTI_EPISODE_FROM_DB[value] ?? 'extend';
}
export function multiEpisodeStyleToDb(value: MultiEpisodeStyle): number {
return MULTI_EPISODE_TO_DB[value] ?? 0;
}
// UI options for multi-episode style
export const MULTI_EPISODE_STYLE_OPTIONS: {
value: MultiEpisodeStyle;
label: string;
}[] = [
{ value: 'extend', label: 'Extend' },
{ value: 'duplicate', label: 'Duplicate' },
{ value: 'repeat', label: 'Repeat' },
{ value: 'scene', label: 'Scene' },
{ value: 'range', label: 'Range' },
{ value: 'prefixedRange', label: 'Prefixed Range' }
];
export function getMultiEpisodeStyleLabel(value: MultiEpisodeStyle): string {
const option = MULTI_EPISODE_STYLE_OPTIONS.find((o) => o.value === value);
return option?.label ?? value;
}
// ============================================================================
// RADARR COLON REPLACEMENT FORMAT
// ============================================================================
// Radarr stores as strings directly in the DB, but we still need UI options
export type RadarrColonReplacementFormat =
| 'delete'
| 'dash'
| 'spaceDash'
| 'spaceDashSpace'
| 'smart';
export const RADARR_COLON_REPLACEMENT_OPTIONS: {
value: RadarrColonReplacementFormat;
label: string;
}[] = [
{ value: 'delete', label: 'Delete' },
{ value: 'dash', label: 'Replace with Dash' },
{ value: 'spaceDash', label: 'Replace with Space Dash' },
{ value: 'spaceDashSpace', label: 'Replace with Space Dash Space' },
{ value: 'smart', label: 'Smart Replace' }
];
export function getRadarrColonReplacementLabel(value: RadarrColonReplacementFormat): string {
const option = RADARR_COLON_REPLACEMENT_OPTIONS.find((o) => o.value === value);
return option?.label ?? value;
}
// ============================================================================
// PROPERS AND REPACKS
// ============================================================================
// Both Radarr and Sonarr store as strings - no conversion needed, just UI options
export type PropersRepacks = 'doNotPrefer' | 'preferAndUpgrade' | 'doNotUpgradeAutomatically';
export const PROPERS_REPACKS_OPTIONS: {
value: PropersRepacks;
label: string;
description: string;
}[] = [
{
value: 'doNotPrefer',
label: 'Do Not Prefer',
description: 'Propers and repacks are not preferred over existing files'
},
{
value: 'preferAndUpgrade',
label: 'Prefer and Upgrade',
description: 'Automatically upgrade to propers and repacks when available'
},
{
value: 'doNotUpgradeAutomatically',
label: 'Do Not Upgrade Automatically',
description: 'Prefer propers/repacks but do not automatically upgrade'
}
];
export function getPropersRepacksLabel(value: PropersRepacks): string {
const option = PROPERS_REPACKS_OPTIONS.find((o) => o.value === value);
return option?.label ?? value;
}

View File

@@ -37,3 +37,54 @@ export type { DelayProfilesRow } from './types.ts';
/** Preferred protocol options - extracted for use in mutations */
export type PreferredProtocol = DelayProfilesRow['preferred_protocol'];
// ============================================================================
// MEDIA MANAGEMENT
// ============================================================================
import type { ArrType } from './types.ts';
// Naming
export type { RadarrNamingRow, SonarrNamingRow } from './types.ts';
export interface NamingListItem {
name: string;
arr_type: Exclude<ArrType, 'all'>;
rename: boolean;
updated_at: string;
}
// Media Settings
export type { RadarrMediaSettingsRow, SonarrMediaSettingsRow } from './types.ts';
export interface MediaSettingsListItem {
name: string;
arr_type: Exclude<ArrType, 'all'>;
propers_repacks: string;
enable_media_info: boolean;
updated_at: string;
}
// Quality Definitions
export type { RadarrQualityDefinitionsRow, SonarrQualityDefinitionsRow } from './types.ts';
export interface QualityDefinitionListItem {
name: string;
arr_type: Exclude<ArrType, 'all'>;
quality_count: number;
updated_at: string;
}
/** Single quality entry (Row without the config name) */
export interface QualityDefinitionEntry {
quality_name: string;
min_size: number;
max_size: number;
preferred_size: number;
}
/** Aggregate config with all its entries */
export interface QualityDefinitionsConfig {
name: string;
entries: QualityDefinitionEntry[];
}

View File

@@ -3,10 +3,10 @@
*
* AUTO-GENERATED - DO NOT EDIT MANUALLY
*
* Generated from: https://github.com/Dictionarry-Hub/schema/blob/local/ops/0.schema.sql
* Generated at: 2026-01-27T12:38:46.562Z
* Generated from: https://github.com/Dictionarry-Hub/schema/blob/1.0.0/ops/0.schema.sql
* Generated at: 2026-01-27T13:04:08.653Z
*
* To regenerate: deno task generate:pcd-types --version=local
* To regenerate: deno task generate:pcd-types --version=1.0.0
*/
import type { Generated } from 'kysely';
@@ -226,7 +226,7 @@ export interface DelayProfilesTable {
// MEDIA MANAGEMENT
export interface RadarrNamingTable {
name: string | null;
name: string;
rename: Generated<number>;
movie_format: string;
movie_folder_format: string;
@@ -237,7 +237,7 @@ export interface RadarrNamingTable {
}
export interface SonarrNamingTable {
name: string | null;
name: string;
rename: Generated<number>;
standard_episode_format: string;
daily_episode_format: string;
@@ -253,7 +253,7 @@ export interface SonarrNamingTable {
}
export interface RadarrMediaSettingsTable {
name: string | null;
name: string;
propers_repacks: Generated<string>;
enable_media_info: Generated<number>;
created_at: Generated<string>;
@@ -261,7 +261,7 @@ export interface RadarrMediaSettingsTable {
}
export interface SonarrMediaSettingsTable {
name: string | null;
name: string;
propers_repacks: Generated<string>;
enable_media_info: Generated<number>;
created_at: Generated<string>;
@@ -574,7 +574,7 @@ export interface DelayProfilesRow {
// MEDIA MANAGEMENT
export interface RadarrNamingRow {
name: string | null;
name: string;
rename: boolean;
movie_format: string;
movie_folder_format: string;
@@ -585,7 +585,7 @@ export interface RadarrNamingRow {
}
export interface SonarrNamingRow {
name: string | null;
name: string;
rename: boolean;
standard_episode_format: string;
daily_episode_format: string;
@@ -601,7 +601,7 @@ export interface SonarrNamingRow {
}
export interface RadarrMediaSettingsRow {
name: string | null;
name: string;
propers_repacks: 'doNotPrefer' | 'preferAndUpgrade' | 'doNotUpgradeAutomatically';
enable_media_info: boolean;
created_at: string;
@@ -609,7 +609,7 @@ export interface RadarrMediaSettingsRow {
}
export interface SonarrMediaSettingsRow {
name: string | null;
name: string;
propers_repacks: 'doNotPrefer' | 'preferAndUpgrade' | 'doNotUpgradeAutomatically';
enable_media_info: boolean;
created_at: string;

View File

@@ -8,11 +8,11 @@
import { alertStore } from '$alerts/store';
import { Check, Save, Trash2 } from 'lucide-svelte';
import { current, isDirty, initEdit, initCreate, update } from '$lib/client/stores/dirty';
import type { MediaSettings, PropersRepacks } from '$lib/shared/mediaManagement.ts';
import { PROPERS_REPACKS_OPTIONS } from '$lib/shared/mediaManagement.ts';
import type { ArrType } from '$pcd/queries/mediaManagement/media-settings/types.ts';
import type { RadarrMediaSettingsRow } from '$shared/pcd/display.ts';
import type { ArrType } from '$shared/pcd/types.ts';
import { PROPERS_REPACKS_OPTIONS, type PropersRepacks } from '$shared/pcd/conversions.ts';
interface MediaSettingsFormData {
interface RadarrMediaSettingsRowFormData {
name: string;
propersRepacks: PropersRepacks;
enableMediaInfo: boolean;
@@ -24,15 +24,15 @@
export let databaseName: string;
export let canWriteToBase: boolean = false;
export let actionUrl: string = '';
export let initialData: MediaSettings | null;
export let initialData: RadarrMediaSettingsRow | null;
const defaults: MediaSettingsFormData = {
const defaults: RadarrMediaSettingsRowFormData = {
name: '',
propersRepacks: 'doNotPrefer',
enableMediaInfo: true
};
function mapToFormData(data: MediaSettings | null): MediaSettingsFormData {
function mapToFormData(data: RadarrMediaSettingsRow | null): RadarrMediaSettingsRowFormData {
if (!data) return defaults;
return {
name: data.name,
@@ -47,7 +47,7 @@
initEdit(mapToFormData(initialData));
}
$: formData = $current as MediaSettingsFormData;
$: formData = $current as RadarrMediaSettingsRowFormData;
let saving = false;
let deleting = false;

View File

@@ -3,8 +3,8 @@ import type { PageServerLoad, Actions } from './$types';
import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import type { ArrType } from '$pcd/queries/mediaManagement/media-settings/types.ts';
import type { PropersRepacks } from '$lib/shared/mediaManagement.ts';
import type { ArrType } from '$shared/pcd/types.ts';
import type { PropersRepacks } from '$shared/pcd/conversions.ts';
import { createRadarrMediaSettings, createSonarrMediaSettings } from '$pcd/queries/mediaManagement/media-settings/index.ts';
export const load: PageServerLoad = async ({ parent }) => {

View File

@@ -4,7 +4,7 @@
import RadarrIcon from '$lib/client/assets/Radarr.svg';
import SonarrIcon from '$lib/client/assets/Sonarr.svg';
import type { PageData } from './$types';
import type { ArrType } from '$pcd/queries/mediaManagement/media-settings/types.ts';
import type { ArrType } from '$shared/pcd/types.ts';
export let data: PageData;

View File

@@ -4,7 +4,7 @@ import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import { getRadarrByName, updateRadarrMediaSettings, removeRadarrMediaSettings } from '$pcd/queries/mediaManagement/media-settings/index.ts';
import type { PropersRepacks } from '$lib/shared/mediaManagement.ts';
import type { PropersRepacks } from '$shared/pcd/conversions.ts';
export const load: PageServerLoad = async ({ params, parent }) => {
const { databaseId, name } = params;

View File

@@ -4,7 +4,7 @@ import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import { getSonarrByName, updateSonarrMediaSettings, removeSonarrMediaSettings } from '$pcd/queries/mediaManagement/media-settings/index.ts';
import type { PropersRepacks } from '$lib/shared/mediaManagement.ts';
import type { PropersRepacks } from '$shared/pcd/conversions.ts';
export const load: PageServerLoad = async ({ params, parent }) => {
const { databaseId, name } = params;

View File

@@ -4,7 +4,7 @@
import type { Column } from '$ui/table/types';
import { goto } from '$app/navigation';
import { Tag, Info, RefreshCw } from 'lucide-svelte';
import type { MediaSettingsListItem } from '$pcd/queries/mediaManagement/media-settings/types.ts';
import type { MediaSettingsListItem } from '$shared/pcd/display.ts';
import radarrLogo from '$lib/client/assets/Radarr.svg';
import sonarrLogo from '$lib/client/assets/Sonarr.svg';

View File

@@ -8,8 +8,8 @@
import { alertStore } from '$alerts/store';
import { Check, Save, Trash2 } from 'lucide-svelte';
import { current, isDirty, initEdit, initCreate, update } from '$lib/client/stores/dirty';
import type { RadarrNaming, RadarrColonReplacementFormat } from '$lib/shared/mediaManagement.ts';
import { RADARR_COLON_REPLACEMENT_OPTIONS } from '$lib/shared/mediaManagement.ts';
import type { RadarrNamingRow } from '$shared/pcd/display.ts';
import { RADARR_COLON_REPLACEMENT_OPTIONS, type RadarrColonReplacementFormat } from '$shared/pcd/conversions.ts';
interface RadarrNamingFormData {
name: string;
@@ -25,7 +25,7 @@
export let databaseName: string;
export let canWriteToBase: boolean = false;
export let actionUrl: string = '';
export let initialData: RadarrNaming | null;
export let initialData: RadarrNamingRow | null;
const defaults: RadarrNamingFormData = {
name: '',
@@ -36,7 +36,7 @@
colonReplacementFormat: 'delete'
};
function mapToFormData(data: RadarrNaming | null): RadarrNamingFormData {
function mapToFormData(data: RadarrNamingRow | null): RadarrNamingFormData {
if (!data) return defaults;
return {
name: data.name,

View File

@@ -8,8 +8,8 @@
import { alertStore } from '$alerts/store';
import { Check, Save, Trash2 } from 'lucide-svelte';
import { current, isDirty, initEdit, initCreate, update } from '$lib/client/stores/dirty';
import type { SonarrNaming, ColonReplacementFormat, MultiEpisodeStyle } from '$lib/shared/mediaManagement.ts';
import { COLON_REPLACEMENT_OPTIONS, MULTI_EPISODE_STYLE_OPTIONS } from '$lib/shared/mediaManagement.ts';
import type { SonarrNamingRow } from '$shared/pcd/display.ts';
import { SONARR_COLON_REPLACEMENT_OPTIONS, MULTI_EPISODE_STYLE_OPTIONS, type SonarrColonReplacementFormat, type MultiEpisodeStyle } from '$shared/pcd/conversions.ts';
interface SonarrNamingFormData {
name: string;
@@ -20,7 +20,7 @@
seriesFolderFormat: string;
seasonFolderFormat: string;
replaceIllegalCharacters: boolean;
colonReplacementFormat: ColonReplacementFormat;
colonReplacementFormat: SonarrColonReplacementFormat;
customColonReplacementFormat: string;
multiEpisodeStyle: MultiEpisodeStyle;
[key: string]: unknown;
@@ -30,7 +30,7 @@
export let databaseName: string;
export let canWriteToBase: boolean = false;
export let actionUrl: string = '';
export let initialData: SonarrNaming | null;
export let initialData: SonarrNamingRow | null;
const defaults: SonarrNamingFormData = {
name: '',
@@ -46,7 +46,7 @@
multiEpisodeStyle: 'extend'
};
function mapToFormData(data: SonarrNaming | null): SonarrNamingFormData {
function mapToFormData(data: SonarrNamingRow | null): SonarrNamingFormData {
if (!data) return defaults;
return {
name: data.name,
@@ -345,7 +345,7 @@
Colon Replacement
</span>
<div class="mt-2 grid gap-2">
{#each COLON_REPLACEMENT_OPTIONS as option}
{#each SONARR_COLON_REPLACEMENT_OPTIONS as option}
<button
type="button"
onclick={() => update('colonReplacementFormat', option.value)}

View File

@@ -3,8 +3,7 @@ import type { PageServerLoad, Actions } from './$types';
import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import type { ArrType } from '$pcd/queries/mediaManagement/naming/types.ts';
import type { RadarrColonReplacementFormat, ColonReplacementFormat, MultiEpisodeStyle } from '$lib/shared/mediaManagement.ts';
import type { RadarrNamingRow, SonarrNamingRow } from '$shared/pcd/display.ts';
import { createRadarrNaming, createSonarrNaming } from '$pcd/queries/mediaManagement/naming/index.ts';
export const load: PageServerLoad = async ({ parent }) => {
@@ -33,7 +32,7 @@ export const actions: Actions = {
}
const formData = await request.formData();
const arrType = formData.get('arrType') as ArrType;
const arrType = formData.get('arrType') as 'radarr' | 'sonarr';
const name = formData.get('name') as string;
const layer = (formData.get('layer') as OperationLayer) || 'user';
@@ -56,7 +55,7 @@ export const actions: Actions = {
const replaceIllegalCharacters = formData.get('replaceIllegalCharacters') === 'true';
const colonReplacementFormat = formData.get(
'colonReplacementFormat'
) as RadarrColonReplacementFormat;
) as RadarrNamingRow['colon_replacement_format'];
const result = await createRadarrNaming({
databaseId: currentDatabaseId,
@@ -85,9 +84,9 @@ export const actions: Actions = {
const replaceIllegalCharacters = formData.get('replaceIllegalCharacters') === 'true';
const colonReplacementFormat = formData.get(
'colonReplacementFormat'
) as ColonReplacementFormat;
) as SonarrNamingRow['colon_replacement_format'];
const customColonReplacementFormat = formData.get('customColonReplacementFormat') as string;
const multiEpisodeStyle = formData.get('multiEpisodeStyle') as MultiEpisodeStyle;
const multiEpisodeStyle = formData.get('multiEpisodeStyle') as SonarrNamingRow['multi_episode_style'];
const result = await createSonarrNaming({
databaseId: currentDatabaseId,

View File

@@ -5,13 +5,13 @@
import RadarrIcon from '$lib/client/assets/Radarr.svg';
import SonarrIcon from '$lib/client/assets/Sonarr.svg';
import type { PageData } from './$types';
import type { ArrType } from '$pcd/queries/mediaManagement/naming/types.ts';
import type { ArrType } from '$shared/pcd/types.ts';
export let data: PageData;
let selectedArrType: ArrType | null = null;
let selectedArrType: Exclude<ArrType, 'all'> | null = null;
const arrTypeOptions: { value: ArrType; label: string; description: string; icon: string }[] = [
const arrTypeOptions: { value: Exclude<ArrType, 'all'>; label: string; description: string; icon: string }[] = [
{
value: 'radarr',
label: 'Radarr',

View File

@@ -4,7 +4,7 @@ import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import { getRadarrByName, updateRadarrNaming, removeRadarrNaming } from '$pcd/queries/mediaManagement/naming/index.ts';
import type { RadarrColonReplacementFormat } from '$lib/shared/mediaManagement.ts';
import type { RadarrNamingRow } from '$shared/pcd/display.ts';
export const load: PageServerLoad = async ({ params, parent }) => {
const { databaseId, name } = params;
@@ -80,7 +80,7 @@ export const actions: Actions = {
const replaceIllegalCharacters = formData.get('replaceIllegalCharacters') === 'true';
const colonReplacementFormat = formData.get(
'colonReplacementFormat'
) as RadarrColonReplacementFormat;
) as RadarrNamingRow['colon_replacement_format'];
const result = await updateRadarrNaming({
databaseId: currentDatabaseId,

View File

@@ -4,7 +4,7 @@ import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import { getSonarrByName, updateSonarrNaming, removeSonarrNaming } from '$pcd/queries/mediaManagement/naming/index.ts';
import type { ColonReplacementFormat, MultiEpisodeStyle } from '$lib/shared/mediaManagement.ts';
import type { SonarrNamingRow } from '$shared/pcd/display.ts';
export const load: PageServerLoad = async ({ params, parent }) => {
const { databaseId, name } = params;
@@ -81,9 +81,9 @@ export const actions: Actions = {
const seriesFolderFormat = formData.get('seriesFolderFormat') as string;
const seasonFolderFormat = formData.get('seasonFolderFormat') as string;
const replaceIllegalCharacters = formData.get('replaceIllegalCharacters') === 'true';
const colonReplacementFormat = formData.get('colonReplacementFormat') as ColonReplacementFormat;
const colonReplacementFormat = formData.get('colonReplacementFormat') as SonarrNamingRow['colon_replacement_format'];
const customColonReplacementFormat = formData.get('customColonReplacementFormat') as string;
const multiEpisodeStyle = formData.get('multiEpisodeStyle') as MultiEpisodeStyle;
const multiEpisodeStyle = formData.get('multiEpisodeStyle') as SonarrNamingRow['multi_episode_style'];
const result = await updateSonarrNaming({
databaseId: currentDatabaseId,

View File

@@ -4,7 +4,7 @@
import type { Column } from '$ui/table/types';
import { goto } from '$app/navigation';
import { Tag, ToggleRight } from 'lucide-svelte';
import type { NamingListItem } from '$pcd/queries/mediaManagement/naming/types.ts';
import type { NamingListItem } from '$shared/pcd/display.ts';
import radarrLogo from '$lib/client/assets/Radarr.svg';
import sonarrLogo from '$lib/client/assets/Sonarr.svg';

View File

@@ -14,13 +14,42 @@
import Dropdown from '$ui/dropdown/Dropdown.svelte';
import DropdownItem from '$ui/dropdown/DropdownItem.svelte';
import { isDirty, initEdit, initCreate, update } from '$lib/client/stores/dirty';
import type { ArrType, QualityDefinitionsConfig, QualityDefinitionEntry } from '$pcd/queries/mediaManagement/quality-definitions/types.ts';
import {
type ResolutionGroup,
RESOLUTION_GROUP_ORDER,
RESOLUTION_GROUP_LABELS,
getResolutionGroup
} from '$lib/shared/mediaManagement.ts';
import type { ArrType } from '$shared/pcd/types.ts';
import type { QualityDefinitionsConfig, QualityDefinitionEntry } from '$shared/pcd/display.ts';
// Resolution grouping for quality definitions UI
type ResolutionGroup = 'SD' | '720p' | '1080p' | '2160p' | 'Prereleases' | 'Other';
const RESOLUTION_GROUP_ORDER: ResolutionGroup[] = [
'2160p',
'1080p',
'720p',
'SD',
'Prereleases',
'Other'
];
const RESOLUTION_GROUP_LABELS: Record<ResolutionGroup, string> = {
'2160p': '4K Ultra HD (2160p)',
'1080p': 'Full HD (1080p)',
'720p': 'HD (720p)',
SD: 'Standard Definition (SD)',
Prereleases: 'Prereleases',
Other: 'Other'
};
const PRERELEASE_QUALITIES = ['cam', 'dvdscr', 'regional', 'telecine', 'telesync', 'workprint'];
const OTHER_QUALITIES = ['raw-hd', 'unknown'];
function getResolutionGroup(qualityName: string): ResolutionGroup {
const name = qualityName.toLowerCase();
if (PRERELEASE_QUALITIES.some((q) => name === q || name.includes(q))) return 'Prereleases';
if (OTHER_QUALITIES.some((q) => name === q || name.includes(q))) return 'Other';
if (name.includes('2160') || name.includes('4k') || name.includes('uhd')) return '2160p';
if (name.includes('1080')) return '1080p';
if (name.includes('720')) return '720p';
return 'SD';
}
export let mode: 'create' | 'edit';
export let arrType: ArrType;

View File

@@ -3,7 +3,7 @@ import type { PageServerLoad, Actions } from './$types';
import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import type { ArrType } from '$pcd/queries/mediaManagement/quality-definitions/types.ts';
import type { ArrType } from '$shared/pcd/types.ts';
import { getAvailableQualities } from '$pcd/queries/mediaManagement/quality-definitions/read.ts';
import { createRadarrQualityDefinitions, createSonarrQualityDefinitions } from '$pcd/queries/mediaManagement/quality-definitions/index.ts';

View File

@@ -4,7 +4,7 @@
import RadarrIcon from '$lib/client/assets/Radarr.svg';
import SonarrIcon from '$lib/client/assets/Sonarr.svg';
import type { PageData } from './$types';
import type { ArrType } from '$pcd/queries/mediaManagement/quality-definitions/types.ts';
import type { ArrType } from '$shared/pcd/types.ts';
export let data: PageData;

View File

@@ -3,7 +3,7 @@
import type { Column } from '$ui/table/types';
import { goto } from '$app/navigation';
import { Tag } from 'lucide-svelte';
import type { QualityDefinitionListItem } from '$pcd/queries/mediaManagement/quality-definitions/types.ts';
import type { QualityDefinitionListItem } from '$shared/pcd/display.ts';
import radarrLogo from '$lib/client/assets/Radarr.svg';
import sonarrLogo from '$lib/client/assets/Sonarr.svg';