mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-29 05:50:51 +01:00
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:
@@ -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
|
||||
}));
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
11
src/lib/server/pcd/queries/qualityProfiles/general/index.ts
Normal file
11
src/lib/server/pcd/queries/qualityProfiles/general/index.ts
Normal 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';
|
||||
@@ -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'
|
||||
}))
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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'
|
||||
}))
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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,
|
||||
@@ -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, "''");
|
||||
}
|
||||
11
src/lib/server/pcd/queries/qualityProfiles/scoring/index.ts
Normal file
11
src/lib/server/pcd/queries/qualityProfiles/scoring/index.ts
Normal 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';
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
*/
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user