refactor(pcd): reorganise qualityProfiles to CRUD pattern, split into route based structure, remove dead langauges code and combine with general queries

This commit is contained in:
Sam Chau
2026-01-28 02:08:41 +10:30
parent 83e19f93fd
commit 81a9ecfc05
24 changed files with 471 additions and 498 deletions

View File

@@ -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
}));
}

View File

@@ -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<string, { radarr: number | null; sonarr: number | null }>;
}
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<AllCfScoresResult> {
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<string, Map<string, Map<string, number>>>();
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<string, { radarr: number | null; sonarr: number | null }> = {};
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
};
}

View File

@@ -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
*/

View File

@@ -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
*/

View File

@@ -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';

View File

@@ -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<QualityProfileLanguages> {
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'
}))
};
}

View File

@@ -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;
}

View File

@@ -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';

View File

@@ -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<QualityProfileLanguages> {
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'
}))
};
}

View File

@@ -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<QualityProfileTableRow[]> {
return results;
}
/**
* Get all quality profile names from a cache
*/
export async function names(cache: PCDCache): Promise<string[]> {
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<QualityProfileOption[]> {
const db = cache.kb;
const profiles = await db
.selectFrom('quality_profiles')
.select(['id', 'name'])
.orderBy('name')
.execute();
return profiles;
}

View File

@@ -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<string[]> {
const db = cache.kb;
const profiles = await db
.selectFrom('quality_profiles')
.select(['name'])
.orderBy('name')
.execute();
return profiles.map((p) => p.name);
}

View File

@@ -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';

View File

@@ -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<string, QualityGroup>();
const groupsMap = new Map<string, QualitiesGroup>();
for (const group of groups) {
groupsMap.set(group.name, {
name: group.name,

View File

@@ -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, "''");
}

View File

@@ -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';

View File

@@ -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<AllCfScoresResult> {
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<string, Map<string, Map<string, number>>>();
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<string, { radarr: number | null; sonarr: number | null }> = {};
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
};
}

View File

@@ -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
*/

View File

@@ -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<QualityProfileOption[]> {
const db = cache.kb;
const profiles = await db
.selectFrom('quality_profiles')
.select(['id', 'name'])
.orderBy('name')
.execute();
return profiles;
}

View File

@@ -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<string, number | null>;
}
/** 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;
}

View File

@@ -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;
}

View File

@@ -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<QualityProfilesRow, 'id' | 'name'>;
// --- 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<QualityProfilesRow, 'id' | 'name'> & {
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<string, number | null>;
}
/** 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<string, { radarr: number | null; sonarr: number | null }>;
}
/** 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[];
}

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type { QualityProfileTableRow } from '$pcd/queries/qualityProfiles/types.ts';
import type { QualityProfileTableRow } from '$shared/pcd/display.ts';
import IconCheckbox from '$ui/form/IconCheckbox.svelte';
import SyncFooter from './SyncFooter.svelte';
import { Check } from 'lucide-svelte';

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type { QualityProfileTableRow } from '$lib/server/pcd/queries/qualityProfiles';
import type { QualityProfileTableRow } from '$shared/pcd/display.ts';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { BookOpenText, Gauge, Earth } from 'lucide-svelte';

View File

@@ -1,7 +1,7 @@
<script lang="ts">
import Table from '$ui/table/Table.svelte';
import type { Column } from '$ui/table/types';
import type { QualityProfileTableRow } from '$lib/server/pcd/queries/qualityProfiles';
import type { QualityProfileTableRow } from '$shared/pcd/display.ts';
import { Tag, FileText, Layers, BookOpenText, Gauge, Earth } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';