diff --git a/src/lib/server/pcd/queries/languages.ts b/src/lib/server/pcd/queries/languages.ts deleted file mode 100644 index fe67d72..0000000 --- a/src/lib/server/pcd/queries/languages.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Language queries for PCD cache - */ - -import type { PCDCache } from '../cache.ts'; - -/** - * Available language from the database - */ -export interface Language { - id: number; - name: string; -} - -/** - * Get all available languages - */ -export function list(cache: PCDCache): Language[] { - const languages = cache.query<{ - id: number; - name: string; - }>(` - SELECT - id, - name - FROM languages - ORDER BY name - `); - - return languages.map((lang) => ({ - id: lang.id, - name: lang.name - })); -} diff --git a/src/lib/server/pcd/queries/qualityProfiles/allCfScores.ts b/src/lib/server/pcd/queries/qualityProfiles/allCfScores.ts deleted file mode 100644 index 057ff98..0000000 --- a/src/lib/server/pcd/queries/qualityProfiles/allCfScores.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Get all custom format scores for all quality profiles - * Used by entity testing to calculate scores client-side - */ - -import type { PCDCache } from '../../cache.ts'; - -export interface ProfileCfScores { - profileName: string; - /** Map of custom format name to score (by arr type) */ - scores: Record; -} - -export interface AllCfScoresResult { - /** All custom formats with their names */ - customFormats: Array<{ name: string }>; - /** CF scores per profile */ - profiles: ProfileCfScores[]; -} - -/** - * Get all custom format scores for all quality profiles - */ -export async function allCfScores(cache: PCDCache): Promise { - const db = cache.kb; - - // Get all custom formats - const customFormats = await db - .selectFrom('custom_formats') - .select(['name']) - .orderBy('name') - .execute(); - - // Get all quality profiles - const profiles = await db.selectFrom('quality_profiles').select(['name']).execute(); - - // Get all CF scores for all profiles - const allScores = await db - .selectFrom('quality_profile_custom_formats') - .select(['quality_profile_name', 'custom_format_name', 'arr_type', 'score']) - .execute(); - - // Build scores map: profileName -> cfName -> arrType -> score - const scoresMap = new Map>>(); - - for (const score of allScores) { - if (!scoresMap.has(score.quality_profile_name)) { - scoresMap.set(score.quality_profile_name, new Map()); - } - const profileScores = scoresMap.get(score.quality_profile_name)!; - - if (!profileScores.has(score.custom_format_name)) { - profileScores.set(score.custom_format_name, new Map()); - } - profileScores.get(score.custom_format_name)!.set(score.arr_type, score.score); - } - - // Build result - const profilesResult: ProfileCfScores[] = profiles.map((profile) => { - const profileScores = scoresMap.get(profile.name); - const scores: Record = {}; - - for (const cf of customFormats) { - const cfScores = profileScores?.get(cf.name); - const allScore = cfScores?.get('all') ?? null; - - scores[cf.name] = { - radarr: cfScores?.get('radarr') ?? allScore, - sonarr: cfScores?.get('sonarr') ?? allScore - }; - } - - return { - profileName: profile.name, - scores - }; - }); - - return { - customFormats, - profiles: profilesResult - }; -} diff --git a/src/lib/server/pcd/queries/qualityProfiles/create.ts b/src/lib/server/pcd/queries/qualityProfiles/create.ts index eb6beda..5de6ee6 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/create.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/create.ts @@ -2,23 +2,31 @@ * Create a quality profile operation */ -import type { PCDCache } from '../../cache.ts'; -import { writeOperation, type OperationLayer } from '../../writer.ts'; +import type { PCDCache } from '$pcd/cache.ts'; +import { writeOperation, type OperationLayer } from '$pcd/writer.ts'; -export interface CreateQualityProfileInput { +// ============================================================================ +// Input types +// ============================================================================ + +interface CreateQualityProfileInput { name: string; description: string | null; tags: string[]; language: string | null; } -export interface CreateQualityProfileOptions { +interface CreateQualityProfileOptions { databaseId: number; cache: PCDCache; layer: OperationLayer; input: CreateQualityProfileInput; } +// ============================================================================ +// Mutations +// ============================================================================ + /** * Escape a string for SQL */ diff --git a/src/lib/server/pcd/queries/qualityProfiles/delete.ts b/src/lib/server/pcd/queries/qualityProfiles/delete.ts index 54f069c..f1b38de 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/delete.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/delete.ts @@ -2,10 +2,14 @@ * Delete a quality profile operation */ -import type { PCDCache } from '../../cache.ts'; -import { writeOperation, type OperationLayer } from '../../writer.ts'; +import type { PCDCache } from '$pcd/cache.ts'; +import { writeOperation, type OperationLayer } from '$pcd/writer.ts'; -export interface RemoveQualityProfileOptions { +// ============================================================================ +// Input types +// ============================================================================ + +interface RemoveQualityProfileOptions { databaseId: number; cache: PCDCache; layer: OperationLayer; @@ -13,6 +17,10 @@ export interface RemoveQualityProfileOptions { profileName: string; } +// ============================================================================ +// Mutations +// ============================================================================ + /** * Delete a quality profile by writing an operation to the specified layer */ diff --git a/src/lib/server/pcd/queries/qualityProfiles/general/index.ts b/src/lib/server/pcd/queries/qualityProfiles/general/index.ts new file mode 100644 index 0000000..e2736a5 --- /dev/null +++ b/src/lib/server/pcd/queries/qualityProfiles/general/index.ts @@ -0,0 +1,11 @@ +/** + * Quality profile general queries and mutations + * + * Types: import from '$shared/pcd/display.ts' + */ + +// Queries +export { general, languages } from './read.ts'; + +// Mutations +export { updateGeneral, updateLanguages } from './update.ts'; diff --git a/src/lib/server/pcd/queries/qualityProfiles/general.ts b/src/lib/server/pcd/queries/qualityProfiles/general/read.ts similarity index 62% rename from src/lib/server/pcd/queries/qualityProfiles/general.ts rename to src/lib/server/pcd/queries/qualityProfiles/general/read.ts index e640055..b489098 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/general.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/general/read.ts @@ -2,8 +2,8 @@ * Quality profile general queries */ -import type { PCDCache } from '../../cache.ts'; -import type { QualityProfileGeneral } from './types.ts'; +import type { PCDCache } from '$pcd/cache.ts'; +import type { QualityProfileGeneral, QualityProfileLanguages } from '$shared/pcd/display.ts'; /** * Get general information for a single quality profile @@ -50,3 +50,28 @@ export async function general( language: languageRow?.language_name ?? null }; } + +/** + * Get languages for a quality profile + */ +export async function languages( + cache: PCDCache, + profileName: string +): Promise { + const db = cache.kb; + + const profileLanguages = await db + .selectFrom('quality_profile_languages as qpl') + .innerJoin('languages as l', 'qpl.language_name', 'l.name') + .select(['l.name as language_name', 'qpl.type']) + .where('qpl.quality_profile_name', '=', profileName) + .orderBy('l.name') + .execute(); + + return { + languages: profileLanguages.map((lang) => ({ + name: lang.language_name, + type: lang.type as 'must' | 'only' | 'not' | 'simple' + })) + }; +} diff --git a/src/lib/server/pcd/queries/qualityProfiles/updateGeneral.ts b/src/lib/server/pcd/queries/qualityProfiles/general/update.ts similarity index 63% rename from src/lib/server/pcd/queries/qualityProfiles/updateGeneral.ts rename to src/lib/server/pcd/queries/qualityProfiles/general/update.ts index 7304957..b18eba5 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/updateGeneral.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/general/update.ts @@ -1,29 +1,48 @@ /** - * Update quality profile general information + * Update quality profile general information and languages */ -import type { PCDCache } from '../../cache.ts'; -import { writeOperation, type OperationLayer } from '../../writer.ts'; -import type { QualityProfileGeneral } from './types.ts'; +import type { PCDCache } from '$pcd/cache.ts'; +import { writeOperation, type OperationLayer } from '$pcd/writer.ts'; +import type { QualityProfileGeneral } from '$shared/pcd/display.ts'; import { logger } from '$logger/logger.ts'; -export interface UpdateGeneralInput { +// ============================================================================ +// Input types +// ============================================================================ + +interface UpdateGeneralInput { name: string; description: string; tags: string[]; - language: string | null; // Language name, null means no language set + language: string | null; } -export interface UpdateGeneralOptions { +interface UpdateGeneralOptions { databaseId: number; cache: PCDCache; layer: OperationLayer; - /** The current quality profile data (for value guards) */ current: QualityProfileGeneral; - /** The new values */ input: UpdateGeneralInput; } +interface UpdateLanguagesInput { + languageName: string | null; + type: 'must' | 'only' | 'not' | 'simple'; +} + +interface UpdateLanguagesOptions { + databaseId: number; + cache: PCDCache; + layer: OperationLayer; + profileName: string; + input: UpdateLanguagesInput; +} + +// ============================================================================ +// Mutations +// ============================================================================ + /** * Escape a string for SQL */ @@ -156,3 +175,54 @@ export async function updateGeneral(options: UpdateGeneralOptions) { return result; } + +/** + * Update quality profile language configuration + */ +export async function updateLanguages(options: UpdateLanguagesOptions) { + const { databaseId, cache, layer, profileName, input } = options; + const db = cache.kb; + + const queries = []; + + // 1. Delete existing languages for this profile + const deleteLanguages = db + .deleteFrom('quality_profile_languages') + .where('quality_profile_name', '=', profileName) + .compile(); + queries.push(deleteLanguages); + + // 2. Insert new language if one is selected + if (input.languageName !== null) { + const insertLanguage = { + sql: `INSERT INTO quality_profile_languages (quality_profile_name, language_name, type) VALUES ('${profileName.replace(/'/g, "''")}', '${input.languageName.replace(/'/g, "''")}', '${input.type}')`, + parameters: [], + query: {} as never + }; + queries.push(insertLanguage); + } + + await logger.info(`Save quality profile languages "${profileName}"`, { + source: 'QualityProfile', + meta: { + profileName, + languageName: input.languageName, + type: input.type + } + }); + + // Write the operation + const result = await writeOperation({ + databaseId, + layer, + description: `update-quality-profile-languages-${profileName}`, + queries, + metadata: { + operation: 'update', + entity: 'quality_profile', + name: profileName + } + }); + + return result; +} diff --git a/src/lib/server/pcd/queries/qualityProfiles/index.ts b/src/lib/server/pcd/queries/qualityProfiles/index.ts index f19dfe9..4dafe3f 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/index.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/index.ts @@ -1,42 +1,24 @@ /** - * Quality Profile queries + * Quality Profile queries and mutations + * + * Types: import from '$shared/pcd/display.ts' */ -// Export all types -export type { - QualityItem, - ProfileLanguage, - CustomFormatCounts, - QualityProfileGeneral, - QualityProfileLanguage, - QualityProfileLanguages, - QualitySingle, - QualityGroup, - QualityProfileQualities, - QualityProfileTableRow -} from './types.ts'; +// List queries +export { list, names, select } from './list.ts'; -export type { - QualityMember, - OrderedItem, - QualityGroup as QualitiesGroup, - QualitiesPageData -} from './qualities.ts'; +// General queries and mutations +export { general, languages } from './general/index.ts'; +export { updateGeneral, updateLanguages } from './general/index.ts'; -// Export query functions -export { list } from './list.ts'; -export { names } from './names.ts'; -export { select } from './select.ts'; -export type { QualityProfileOption } from './select.ts'; -export { general } from './general.ts'; -export { languages } from './languages.ts'; -export { qualities } from './qualities.ts'; -export { scoring } from './scoring.ts'; -export { allCfScores } from './allCfScores.ts'; -export type { ProfileCfScores, AllCfScoresResult } from './allCfScores.ts'; +// Scoring queries and mutations +export { scoring, allCfScores } from './scoring/index.ts'; +export { updateScoring } from './scoring/index.ts'; + +// Qualities queries and mutations +export { qualities } from './qualities/index.ts'; +export { updateQualities } from './qualities/index.ts'; + +// Create and delete export { create } from './create.ts'; -export { updateGeneral } from './updateGeneral.ts'; -export { updateScoring } from './updateScoring.ts'; -export { updateQualities } from './updateQualities.ts'; -export { updateLanguages } from './updateLanguages.ts'; export { remove } from './delete.ts'; diff --git a/src/lib/server/pcd/queries/qualityProfiles/languages.ts b/src/lib/server/pcd/queries/qualityProfiles/languages.ts deleted file mode 100644 index ba1e45d..0000000 --- a/src/lib/server/pcd/queries/qualityProfiles/languages.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Quality profile languages queries - */ - -import type { PCDCache } from '../../cache.ts'; -import type { QualityProfileLanguages } from './types.ts'; - -/** - * Get languages for a quality profile - */ -export async function languages( - cache: PCDCache, - profileName: string -): Promise { - const db = cache.kb; - - const profileLanguages = await db - .selectFrom('quality_profile_languages as qpl') - .innerJoin('languages as l', 'qpl.language_name', 'l.name') - .select(['l.name as language_name', 'qpl.type']) - .where('qpl.quality_profile_name', '=', profileName) - .orderBy('l.name') - .execute(); - - return { - languages: profileLanguages.map((lang) => ({ - name: lang.language_name, - type: lang.type as 'must' | 'only' | 'not' | 'simple' - })) - }; -} diff --git a/src/lib/server/pcd/queries/qualityProfiles/list.ts b/src/lib/server/pcd/queries/qualityProfiles/list.ts index 8cf1b51..0ace514 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/list.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/list.ts @@ -2,16 +2,16 @@ * Quality profile list queries */ -import type { PCDCache } from '../../cache.ts'; -import type { Tag } from '$shared/pcd/display.ts'; +import type { PCDCache } from '$pcd/cache.ts'; import type { + Tag, QualityProfileTableRow, QualityItem, ProfileLanguage, - CustomFormatCounts -} from './types.ts'; + CustomFormatCounts, + QualityProfileOption +} from '$shared/pcd/display.ts'; import { parseMarkdown } from '$utils/markdown/markdown.ts'; -import { logger } from '$logger/logger.ts'; /** * Get quality profiles with full data for table/card views @@ -167,3 +167,33 @@ export async function list(cache: PCDCache): Promise { return results; } + +/** + * Get all quality profile names from a cache + */ +export async function names(cache: PCDCache): Promise { + const db = cache.kb; + + const profiles = await db + .selectFrom('quality_profiles') + .select(['name']) + .orderBy('name') + .execute(); + + return profiles.map((p) => p.name); +} + +/** + * Get quality profile options for select/dropdown components + */ +export async function select(cache: PCDCache): Promise { + const db = cache.kb; + + const profiles = await db + .selectFrom('quality_profiles') + .select(['id', 'name']) + .orderBy('name') + .execute(); + + return profiles; +} diff --git a/src/lib/server/pcd/queries/qualityProfiles/names.ts b/src/lib/server/pcd/queries/qualityProfiles/names.ts deleted file mode 100644 index 2891b54..0000000 --- a/src/lib/server/pcd/queries/qualityProfiles/names.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Quality profile name queries - */ - -import type { PCDCache } from '../../cache.ts'; - -/** - * Get all quality profile names from a cache - */ -export async function names(cache: PCDCache): Promise { - const db = cache.kb; - - const profiles = await db - .selectFrom('quality_profiles') - .select(['name']) - .orderBy('name') - .execute(); - - return profiles.map((p) => p.name); -} diff --git a/src/lib/server/pcd/queries/qualityProfiles/qualities/index.ts b/src/lib/server/pcd/queries/qualityProfiles/qualities/index.ts new file mode 100644 index 0000000..730e293 --- /dev/null +++ b/src/lib/server/pcd/queries/qualityProfiles/qualities/index.ts @@ -0,0 +1,11 @@ +/** + * Quality profile qualities queries and mutations + * + * Types: import from '$shared/pcd/display.ts' + */ + +// Queries +export { qualities } from './read.ts'; + +// Mutations +export { updateQualities } from './update.ts'; diff --git a/src/lib/server/pcd/queries/qualityProfiles/qualities.ts b/src/lib/server/pcd/queries/qualityProfiles/qualities/read.ts similarity index 85% rename from src/lib/server/pcd/queries/qualityProfiles/qualities.ts rename to src/lib/server/pcd/queries/qualityProfiles/qualities/read.ts index 026e1d1..bf4c701 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/qualities.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/qualities/read.ts @@ -2,32 +2,8 @@ * Quality profile qualities queries */ -import type { PCDCache } from '../../cache.ts'; - -export interface QualityMember { - name: string; -} - -export interface OrderedItem { - type: 'quality' | 'group'; - name: string; // quality_name or quality_group_name - position: number; - enabled: boolean; - upgradeUntil: boolean; - members?: QualityMember[]; -} - -export interface QualityGroup { - name: string; - members: QualityMember[]; -} - -export interface QualitiesPageData { - orderedItems: OrderedItem[]; - availableQualities: QualityMember[]; - allQualities: QualityMember[]; - groups: QualityGroup[]; -} +import type { PCDCache } from '$pcd/cache.ts'; +import type { QualitiesPageData, OrderedItem, QualitiesGroup } from '$shared/pcd/display.ts'; /** * Get quality profile qualities data @@ -58,7 +34,7 @@ export async function qualities( .execute(); // Build groups with members - const groupsMap = new Map(); + const groupsMap = new Map(); for (const group of groups) { groupsMap.set(group.name, { name: group.name, diff --git a/src/lib/server/pcd/queries/qualityProfiles/updateQualities.ts b/src/lib/server/pcd/queries/qualityProfiles/qualities/update.ts similarity index 85% rename from src/lib/server/pcd/queries/qualityProfiles/updateQualities.ts rename to src/lib/server/pcd/queries/qualityProfiles/qualities/update.ts index 50cb1d6..7c7793b 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/updateQualities.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/qualities/update.ts @@ -2,28 +2,20 @@ * Update quality profile qualities */ -import type { PCDCache } from '../../cache.ts'; -import { writeOperation, type OperationLayer } from '../../writer.ts'; +import type { PCDCache } from '$pcd/cache.ts'; +import { writeOperation, type OperationLayer } from '$pcd/writer.ts'; +import type { OrderedItem } from '$shared/pcd/display.ts'; import { logger } from '$logger/logger.ts'; -export interface QualityMember { - name: string; -} +// ============================================================================ +// Input types +// ============================================================================ -export interface OrderedItem { - type: 'quality' | 'group'; - name: string; // quality_name or quality_group_name - position: number; - enabled: boolean; - upgradeUntil: boolean; - members?: QualityMember[]; -} - -export interface UpdateQualitiesInput { +interface UpdateQualitiesInput { orderedItems: OrderedItem[]; } -export interface UpdateQualitiesOptions { +interface UpdateQualitiesOptions { databaseId: number; cache: PCDCache; layer: OperationLayer; @@ -31,6 +23,10 @@ export interface UpdateQualitiesOptions { input: UpdateQualitiesInput; } +// ============================================================================ +// Mutations +// ============================================================================ + function esc(str: string): string { return str.replace(/'/g, "''"); } diff --git a/src/lib/server/pcd/queries/qualityProfiles/scoring/index.ts b/src/lib/server/pcd/queries/qualityProfiles/scoring/index.ts new file mode 100644 index 0000000..6bbf908 --- /dev/null +++ b/src/lib/server/pcd/queries/qualityProfiles/scoring/index.ts @@ -0,0 +1,11 @@ +/** + * Quality profile scoring queries and mutations + * + * Types: import from '$shared/pcd/display.ts' + */ + +// Queries +export { scoring, allCfScores } from './read.ts'; + +// Mutations +export { updateScoring } from './update.ts'; diff --git a/src/lib/server/pcd/queries/qualityProfiles/scoring.ts b/src/lib/server/pcd/queries/qualityProfiles/scoring/read.ts similarity index 60% rename from src/lib/server/pcd/queries/qualityProfiles/scoring.ts rename to src/lib/server/pcd/queries/qualityProfiles/scoring/read.ts index b943ef3..51e9efb 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/scoring.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/scoring/read.ts @@ -2,8 +2,8 @@ * Quality profile scoring queries */ -import type { PCDCache } from '../../cache.ts'; -import type { QualityProfileScoring } from './types.ts'; +import type { PCDCache } from '$pcd/cache.ts'; +import type { QualityProfileScoring, ProfileCfScores, AllCfScoresResult } from '$shared/pcd/display.ts'; /** * Get quality profile scoring data @@ -102,3 +102,68 @@ export async function scoring( return result; } + +/** + * Get all custom format scores for all quality profiles + * Used by entity testing to calculate scores client-side + */ +export async function allCfScores(cache: PCDCache): Promise { + const db = cache.kb; + + // Get all custom formats + const customFormats = await db + .selectFrom('custom_formats') + .select(['name']) + .orderBy('name') + .execute(); + + // Get all quality profiles + const profiles = await db.selectFrom('quality_profiles').select(['name']).execute(); + + // Get all CF scores for all profiles + const allScores = await db + .selectFrom('quality_profile_custom_formats') + .select(['quality_profile_name', 'custom_format_name', 'arr_type', 'score']) + .execute(); + + // Build scores map: profileName -> cfName -> arrType -> score + const scoresMap = new Map>>(); + + for (const score of allScores) { + if (!scoresMap.has(score.quality_profile_name)) { + scoresMap.set(score.quality_profile_name, new Map()); + } + const profileScores = scoresMap.get(score.quality_profile_name)!; + + if (!profileScores.has(score.custom_format_name)) { + profileScores.set(score.custom_format_name, new Map()); + } + profileScores.get(score.custom_format_name)!.set(score.arr_type, score.score); + } + + // Build result + const profilesResult: ProfileCfScores[] = profiles.map((profile) => { + const profileScores = scoresMap.get(profile.name); + const scores: Record = {}; + + for (const cf of customFormats) { + const cfScores = profileScores?.get(cf.name); + const allScore = cfScores?.get('all') ?? null; + + scores[cf.name] = { + radarr: cfScores?.get('radarr') ?? allScore, + sonarr: cfScores?.get('sonarr') ?? allScore + }; + } + + return { + profileName: profile.name, + scores + }; + }); + + return { + customFormats, + profiles: profilesResult + }; +} diff --git a/src/lib/server/pcd/queries/qualityProfiles/updateScoring.ts b/src/lib/server/pcd/queries/qualityProfiles/scoring/update.ts similarity index 84% rename from src/lib/server/pcd/queries/qualityProfiles/updateScoring.ts rename to src/lib/server/pcd/queries/qualityProfiles/scoring/update.ts index 33b29ab..c8055f3 100644 --- a/src/lib/server/pcd/queries/qualityProfiles/updateScoring.ts +++ b/src/lib/server/pcd/queries/qualityProfiles/scoring/update.ts @@ -2,24 +2,28 @@ * Update quality profile scoring settings */ -import type { PCDCache } from '../../cache.ts'; -import { writeOperation, type OperationLayer } from '../../writer.ts'; +import type { PCDCache } from '$pcd/cache.ts'; +import { writeOperation, type OperationLayer } from '$pcd/writer.ts'; import { logger } from '$logger/logger.ts'; -export interface CustomFormatScore { +// ============================================================================ +// Input types +// ============================================================================ + +interface CustomFormatScore { customFormatName: string; arrType: string; score: number | null; } -export interface UpdateScoringInput { +interface UpdateScoringInput { minimumScore: number; upgradeUntilScore: number; upgradeScoreIncrement: number; customFormatScores: CustomFormatScore[]; } -export interface UpdateScoringOptions { +interface UpdateScoringOptions { databaseId: number; cache: PCDCache; layer: OperationLayer; @@ -27,6 +31,10 @@ export interface UpdateScoringOptions { input: UpdateScoringInput; } +// ============================================================================ +// Mutations +// ============================================================================ + /** * Update quality profile scoring settings */ diff --git a/src/lib/server/pcd/queries/qualityProfiles/select.ts b/src/lib/server/pcd/queries/qualityProfiles/select.ts deleted file mode 100644 index c1e6748..0000000 --- a/src/lib/server/pcd/queries/qualityProfiles/select.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Quality profile select options query - */ - -import type { PCDCache } from '../../cache.ts'; - -export interface QualityProfileOption { - id: number; - name: string; -} - -/** - * Get quality profile options for select/dropdown components - */ -export async function select(cache: PCDCache): Promise { - const db = cache.kb; - - const profiles = await db - .selectFrom('quality_profiles') - .select(['id', 'name']) - .orderBy('name') - .execute(); - - return profiles; -} diff --git a/src/lib/server/pcd/queries/qualityProfiles/types.ts b/src/lib/server/pcd/queries/qualityProfiles/types.ts deleted file mode 100644 index e80504c..0000000 --- a/src/lib/server/pcd/queries/qualityProfiles/types.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Quality Profile query-specific types - */ - -import type { Tag } from '$shared/pcd/display.ts'; - -// ============================================================================ -// INTERNAL TYPES (used within queries) -// ============================================================================ - -/** Quality/group item in the hierarchy */ -export interface QualityItem { - position: number; - type: 'quality' | 'group'; - name: string; - is_upgrade_until: boolean; -} - -/** Language configuration */ -export interface ProfileLanguage { - name: string; - type: 'must' | 'only' | 'not' | 'simple'; -} - -/** Custom format counts by arr type */ -export interface CustomFormatCounts { - all: number; - radarr: number; - sonarr: number; - total: number; -} - -// ============================================================================ -// QUERY RESULT TYPES -// ============================================================================ - -/** Quality profile general information */ -export interface QualityProfileGeneral { - id: number; - name: string; - description: string; // Raw markdown - tags: Tag[]; - language: string | null; // Language name, null means "Any" -} - -/** Language configuration for a quality profile */ -export interface QualityProfileLanguage { - name: string; - type: 'must' | 'only' | 'not' | 'simple'; -} - -/** Quality profile languages information */ -export interface QualityProfileLanguages { - languages: QualityProfileLanguage[]; -} - -/** Single quality item */ -export interface QualitySingle { - name: string; - position: number; - enabled: boolean; - isUpgradeUntil: boolean; -} - -/** Quality group with members */ -export interface QualityGroup { - name: string; - position: number; - enabled: boolean; - isUpgradeUntil: boolean; - members: { - name: string; - }[]; -} - -/** Quality profile qualities information */ -export interface QualityProfileQualities { - singles: QualitySingle[]; - groups: QualityGroup[]; -} - -/** Custom format scoring entry */ -export interface CustomFormatScoring { - name: string; - tags: string[]; - scores: Record; -} - -/** Quality profile scoring data for the scoring page */ -export interface QualityProfileScoring { - databaseId: number; - arrTypes: string[]; - customFormats: CustomFormatScoring[]; - minimum_custom_format_score: number; - upgrade_until_score: number; - upgrade_score_increment: number; -} - -/** Quality profile data for table view with all relationships */ -export interface QualityProfileTableRow { - // Basic info - id: number; - name: string; - description: string; // Parsed HTML from markdown - - // Tags - tags: Tag[]; - - // Upgrade settings - upgrades_allowed: boolean; - minimum_custom_format_score: number; - upgrade_until_score?: number; // Only if upgrades_allowed - upgrade_score_increment?: number; // Only if upgrades_allowed - - // Custom format counts by arr type - custom_formats: CustomFormatCounts; - - // Quality hierarchy (in order) - qualities: QualityItem[]; - - // Single language configuration - language?: ProfileLanguage; -} diff --git a/src/lib/server/pcd/queries/qualityProfiles/updateLanguages.ts b/src/lib/server/pcd/queries/qualityProfiles/updateLanguages.ts deleted file mode 100644 index 4d2fcbc..0000000 --- a/src/lib/server/pcd/queries/qualityProfiles/updateLanguages.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Update quality profile languages - */ - -import type { PCDCache } from '../../cache.ts'; -import { writeOperation, type OperationLayer } from '../../writer.ts'; -import { logger } from '$logger/logger.ts'; - -export interface UpdateLanguagesInput { - languageName: string | null; - type: 'must' | 'only' | 'not' | 'simple'; -} - -export interface UpdateLanguagesOptions { - databaseId: number; - cache: PCDCache; - layer: OperationLayer; - profileName: string; - input: UpdateLanguagesInput; -} - -/** - * Update quality profile language configuration - */ -export async function updateLanguages(options: UpdateLanguagesOptions) { - const { databaseId, cache, layer, profileName, input } = options; - const db = cache.kb; - - const queries = []; - - // 1. Delete existing languages for this profile - const deleteLanguages = db - .deleteFrom('quality_profile_languages') - .where('quality_profile_name', '=', profileName) - .compile(); - queries.push(deleteLanguages); - - // 2. Insert new language if one is selected - if (input.languageName !== null) { - const insertLanguage = { - sql: `INSERT INTO quality_profile_languages (quality_profile_name, language_name, type) VALUES ('${profileName.replace(/'/g, "''")}', '${input.languageName.replace(/'/g, "''")}', '${input.type}')`, - parameters: [], - query: {} as never - }; - queries.push(insertLanguage); - } - - await logger.info(`Save quality profile languages "${profileName}"`, { - source: 'QualityProfile', - meta: { - profileName, - languageName: input.languageName, - type: input.type - } - }); - - // Write the operation - const result = await writeOperation({ - databaseId, - layer, - description: `update-quality-profile-languages-${profileName}`, - queries, - metadata: { - operation: 'update', - entity: 'quality_profile', - name: profileName - } - }); - - return result; -} diff --git a/src/lib/shared/pcd/display.ts b/src/lib/shared/pcd/display.ts index 52f18de..9f63336 100644 --- a/src/lib/shared/pcd/display.ts +++ b/src/lib/shared/pcd/display.ts @@ -204,3 +204,162 @@ export interface CustomFormatWithConditions { name: string; conditions: ConditionData[]; } + +// ============================================================================ +// QUALITY PROFILES +// ============================================================================ + +import type { QualityProfilesRow } from './types.ts'; + +// --- Select/Dropdown --- + +/** Quality profile option for select/dropdown */ +export type QualityProfileOption = Pick; + +// --- List/Table View Helpers --- + +/** Quality/group item in the hierarchy */ +export interface QualityItem { + position: number; + type: 'quality' | 'group'; + name: string; + is_upgrade_until: boolean; +} + +/** Language configuration */ +export interface ProfileLanguage { + name: string; + type: 'must' | 'only' | 'not' | 'simple'; +} + +/** Custom format counts by arr type */ +export interface CustomFormatCounts { + all: number; + radarr: number; + sonarr: number; + total: number; +} + +/** Quality profile data for table/card views (with JOINed data) */ +export type QualityProfileTableRow = Omit< + QualityProfilesRow, + 'description' | 'upgrade_until_score' | 'upgrade_score_increment' | 'created_at' | 'updated_at' +> & { + description: string; // Parsed HTML from markdown (non-nullable) + tags: Tag[]; + upgrade_until_score?: number; // Only if upgrades_allowed + upgrade_score_increment?: number; // Only if upgrades_allowed + custom_formats: CustomFormatCounts; + qualities: QualityItem[]; + language?: ProfileLanguage; +}; + +// --- General Tab --- + +/** Quality profile general information */ +export type QualityProfileGeneral = Pick & { + description: string; // Raw markdown (non-nullable for form) + tags: Tag[]; + language: string | null; // Language name, null means "Any" +}; + +/** Language configuration for a quality profile */ +export interface QualityProfileLanguage { + name: string; + type: 'must' | 'only' | 'not' | 'simple'; +} + +/** Quality profile languages information */ +export interface QualityProfileLanguages { + languages: QualityProfileLanguage[]; +} + +// --- Qualities Tab --- + +/** Single quality item for display */ +export interface QualitySingle { + name: string; + position: number; + enabled: boolean; + isUpgradeUntil: boolean; +} + +/** Quality group with members for display */ +export interface QualityGroup { + name: string; + position: number; + enabled: boolean; + isUpgradeUntil: boolean; + members: { name: string }[]; +} + +/** Quality profile qualities information */ +export interface QualityProfileQualities { + singles: QualitySingle[]; + groups: QualityGroup[]; +} + +/** Simple quality member (name only) */ +export interface QualityMember { + name: string; +} + +/** Ordered quality/group item for qualities page */ +export interface OrderedItem { + type: 'quality' | 'group'; + name: string; + position: number; + enabled: boolean; + upgradeUntil: boolean; + members?: QualityMember[]; +} + +/** Group with members (for qualities page) */ +export interface QualitiesGroup { + name: string; + members: QualityMember[]; +} + +/** Qualities page data */ +export interface QualitiesPageData { + orderedItems: OrderedItem[]; + availableQualities: QualityMember[]; + allQualities: QualityMember[]; + groups: QualitiesGroup[]; +} + +// --- Scoring Tab --- + +/** Custom format scoring entry */ +export interface CustomFormatScoring { + name: string; + tags: string[]; + scores: Record; +} + +/** Quality profile scoring data for the scoring page */ +export interface QualityProfileScoring { + databaseId: number; + arrTypes: string[]; + customFormats: CustomFormatScoring[]; + minimum_custom_format_score: number; + upgrade_until_score: number; + upgrade_score_increment: number; +} + +// --- Entity Testing --- + +/** CF scores for a single profile */ +export interface ProfileCfScores { + profileName: string; + /** Map of custom format name to score (by arr type) */ + scores: Record; +} + +/** All CF scores result for entity testing */ +export interface AllCfScoresResult { + /** All custom formats with their names */ + customFormats: Array<{ name: string }>; + /** CF scores per profile */ + profiles: ProfileCfScores[]; +} diff --git a/src/routes/arr/[id]/sync/components/QualityProfiles.svelte b/src/routes/arr/[id]/sync/components/QualityProfiles.svelte index 6c44833..2c82bab 100644 --- a/src/routes/arr/[id]/sync/components/QualityProfiles.svelte +++ b/src/routes/arr/[id]/sync/components/QualityProfiles.svelte @@ -1,5 +1,5 @@