mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-29 05:50:51 +01:00
refactor(pcd): reorganise customFormats to CRUD pattern, cleanup duped types in evaluator, split into similar route based file structure
This commit is contained in:
@@ -1,229 +0,0 @@
|
||||
/**
|
||||
* Get all custom format conditions for batch evaluation
|
||||
* Used by entity testing to evaluate releases against all CFs at once
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import type { ConditionData } from './conditions.ts';
|
||||
|
||||
export interface CustomFormatWithConditions {
|
||||
name: string;
|
||||
conditions: ConditionData[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all custom formats with their conditions for batch evaluation
|
||||
* Optimized to fetch all data in minimal queries
|
||||
*/
|
||||
export async function getAllConditionsForEvaluation(
|
||||
cache: PCDCache
|
||||
): Promise<CustomFormatWithConditions[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
// Get all custom formats
|
||||
const formats = await db
|
||||
.selectFrom('custom_formats')
|
||||
.select(['id', 'name'])
|
||||
.orderBy('name')
|
||||
.execute();
|
||||
|
||||
if (formats.length === 0) return [];
|
||||
|
||||
// Get all conditions for all formats
|
||||
const conditions = await db
|
||||
.selectFrom('custom_format_conditions')
|
||||
.select(['custom_format_name', 'name', 'type', 'arr_type', 'negate', 'required'])
|
||||
.execute();
|
||||
|
||||
if (conditions.length === 0) {
|
||||
return formats.map((f) => ({ name: f.name, conditions: [] }));
|
||||
}
|
||||
|
||||
// Build composite keys for condition lookups
|
||||
const conditionKeys = conditions.map((c) => `${c.custom_format_name}|${c.name}`);
|
||||
|
||||
// Get all related data in parallel
|
||||
const [
|
||||
patterns,
|
||||
languages,
|
||||
sources,
|
||||
resolutions,
|
||||
qualityModifiers,
|
||||
releaseTypes,
|
||||
indexerFlags,
|
||||
sizes,
|
||||
years
|
||||
] = await Promise.all([
|
||||
// Patterns with regex
|
||||
db
|
||||
.selectFrom('condition_patterns as cp')
|
||||
.innerJoin('regular_expressions as re', 're.name', 'cp.regular_expression_name')
|
||||
.select(['cp.custom_format_name', 'cp.condition_name', 're.name', 're.pattern'])
|
||||
.execute(),
|
||||
|
||||
// Languages
|
||||
db
|
||||
.selectFrom('condition_languages as cl')
|
||||
.innerJoin('languages as l', 'l.name', 'cl.language_name')
|
||||
.select(['cl.custom_format_name', 'cl.condition_name', 'l.name', 'cl.except_language'])
|
||||
.execute(),
|
||||
|
||||
// Sources
|
||||
db
|
||||
.selectFrom('condition_sources')
|
||||
.select(['custom_format_name', 'condition_name', 'source'])
|
||||
.execute(),
|
||||
|
||||
// Resolutions
|
||||
db
|
||||
.selectFrom('condition_resolutions')
|
||||
.select(['custom_format_name', 'condition_name', 'resolution'])
|
||||
.execute(),
|
||||
|
||||
// Quality modifiers
|
||||
db
|
||||
.selectFrom('condition_quality_modifiers')
|
||||
.select(['custom_format_name', 'condition_name', 'quality_modifier'])
|
||||
.execute(),
|
||||
|
||||
// Release types
|
||||
db
|
||||
.selectFrom('condition_release_types')
|
||||
.select(['custom_format_name', 'condition_name', 'release_type'])
|
||||
.execute(),
|
||||
|
||||
// Indexer flags
|
||||
db
|
||||
.selectFrom('condition_indexer_flags')
|
||||
.select(['custom_format_name', 'condition_name', 'flag'])
|
||||
.execute(),
|
||||
|
||||
// Sizes
|
||||
db
|
||||
.selectFrom('condition_sizes')
|
||||
.select(['custom_format_name', 'condition_name', 'min_bytes', 'max_bytes'])
|
||||
.execute(),
|
||||
|
||||
// Years
|
||||
db
|
||||
.selectFrom('condition_years')
|
||||
.select(['custom_format_name', 'condition_name', 'min_year', 'max_year'])
|
||||
.execute()
|
||||
]);
|
||||
|
||||
// Build lookup maps using composite key (custom_format_name|condition_name)
|
||||
const patternsMap = new Map<string, { name: string; pattern: string }[]>();
|
||||
for (const p of patterns) {
|
||||
const key = `${p.custom_format_name}|${p.condition_name}`;
|
||||
if (!patternsMap.has(key)) {
|
||||
patternsMap.set(key, []);
|
||||
}
|
||||
patternsMap.get(key)!.push({ name: p.name, pattern: p.pattern });
|
||||
}
|
||||
|
||||
const languagesMap = new Map<string, { name: string; except: boolean }[]>();
|
||||
for (const l of languages) {
|
||||
const key = `${l.custom_format_name}|${l.condition_name}`;
|
||||
if (!languagesMap.has(key)) {
|
||||
languagesMap.set(key, []);
|
||||
}
|
||||
languagesMap.get(key)!.push({
|
||||
name: l.name,
|
||||
except: l.except_language === 1
|
||||
});
|
||||
}
|
||||
|
||||
const sourcesMap = new Map<string, string[]>();
|
||||
for (const s of sources) {
|
||||
const key = `${s.custom_format_name}|${s.condition_name}`;
|
||||
if (!sourcesMap.has(key)) {
|
||||
sourcesMap.set(key, []);
|
||||
}
|
||||
sourcesMap.get(key)!.push(s.source);
|
||||
}
|
||||
|
||||
const resolutionsMap = new Map<string, string[]>();
|
||||
for (const r of resolutions) {
|
||||
const key = `${r.custom_format_name}|${r.condition_name}`;
|
||||
if (!resolutionsMap.has(key)) {
|
||||
resolutionsMap.set(key, []);
|
||||
}
|
||||
resolutionsMap.get(key)!.push(r.resolution);
|
||||
}
|
||||
|
||||
const qualityModifiersMap = new Map<string, string[]>();
|
||||
for (const q of qualityModifiers) {
|
||||
const key = `${q.custom_format_name}|${q.condition_name}`;
|
||||
if (!qualityModifiersMap.has(key)) {
|
||||
qualityModifiersMap.set(key, []);
|
||||
}
|
||||
qualityModifiersMap.get(key)!.push(q.quality_modifier);
|
||||
}
|
||||
|
||||
const releaseTypesMap = new Map<string, string[]>();
|
||||
for (const r of releaseTypes) {
|
||||
const key = `${r.custom_format_name}|${r.condition_name}`;
|
||||
if (!releaseTypesMap.has(key)) {
|
||||
releaseTypesMap.set(key, []);
|
||||
}
|
||||
releaseTypesMap.get(key)!.push(r.release_type);
|
||||
}
|
||||
|
||||
const indexerFlagsMap = new Map<string, string[]>();
|
||||
for (const f of indexerFlags) {
|
||||
const key = `${f.custom_format_name}|${f.condition_name}`;
|
||||
if (!indexerFlagsMap.has(key)) {
|
||||
indexerFlagsMap.set(key, []);
|
||||
}
|
||||
indexerFlagsMap.get(key)!.push(f.flag);
|
||||
}
|
||||
|
||||
const sizesMap = new Map<string, { minBytes: number | null; maxBytes: number | null }>();
|
||||
for (const s of sizes) {
|
||||
const key = `${s.custom_format_name}|${s.condition_name}`;
|
||||
sizesMap.set(key, {
|
||||
minBytes: s.min_bytes,
|
||||
maxBytes: s.max_bytes
|
||||
});
|
||||
}
|
||||
|
||||
const yearsMap = new Map<string, { minYear: number | null; maxYear: number | null }>();
|
||||
for (const y of years) {
|
||||
const key = `${y.custom_format_name}|${y.condition_name}`;
|
||||
yearsMap.set(key, {
|
||||
minYear: y.min_year,
|
||||
maxYear: y.max_year
|
||||
});
|
||||
}
|
||||
|
||||
// Build conditions by format
|
||||
const conditionsByFormat = new Map<string, ConditionData[]>();
|
||||
for (const c of conditions) {
|
||||
if (!conditionsByFormat.has(c.custom_format_name)) {
|
||||
conditionsByFormat.set(c.custom_format_name, []);
|
||||
}
|
||||
const key = `${c.custom_format_name}|${c.name}`;
|
||||
conditionsByFormat.get(c.custom_format_name)!.push({
|
||||
name: c.name,
|
||||
type: c.type,
|
||||
arrType: c.arr_type as 'all' | 'radarr' | 'sonarr',
|
||||
negate: c.negate === 1,
|
||||
required: c.required === 1,
|
||||
patterns: patternsMap.get(key),
|
||||
languages: languagesMap.get(key),
|
||||
sources: sourcesMap.get(key),
|
||||
resolutions: resolutionsMap.get(key),
|
||||
qualityModifiers: qualityModifiersMap.get(key),
|
||||
releaseTypes: releaseTypesMap.get(key),
|
||||
indexerFlags: indexerFlagsMap.get(key),
|
||||
size: sizesMap.get(key),
|
||||
years: yearsMap.get(key)
|
||||
});
|
||||
}
|
||||
|
||||
// Build final result
|
||||
return formats.map((f) => ({
|
||||
name: f.name,
|
||||
conditions: conditionsByFormat.get(f.name) || []
|
||||
}));
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/**
|
||||
* Custom format condition queries for test evaluation
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
|
||||
/** Full condition data for evaluation */
|
||||
export interface ConditionData {
|
||||
name: string;
|
||||
type: string;
|
||||
arrType: 'all' | 'radarr' | 'sonarr';
|
||||
negate: boolean;
|
||||
required: boolean;
|
||||
// Type-specific data
|
||||
patterns?: { name: string; pattern: string }[];
|
||||
languages?: { name: string; except: boolean }[];
|
||||
sources?: string[];
|
||||
resolutions?: string[];
|
||||
qualityModifiers?: string[];
|
||||
releaseTypes?: string[];
|
||||
indexerFlags?: string[];
|
||||
size?: { minBytes: number | null; maxBytes: number | null };
|
||||
years?: { minYear: number | null; maxYear: number | null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conditions for a custom format with full data for evaluation
|
||||
*/
|
||||
export async function getConditionsForEvaluation(
|
||||
cache: PCDCache,
|
||||
formatName: string
|
||||
): Promise<ConditionData[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
// Get base conditions
|
||||
const conditions = await db
|
||||
.selectFrom('custom_format_conditions')
|
||||
.select(['custom_format_name', 'name', 'type', 'arr_type', 'negate', 'required'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.execute();
|
||||
|
||||
if (conditions.length === 0) return [];
|
||||
|
||||
const conditionNames = conditions.map((c) => c.name);
|
||||
|
||||
// Get all related data in parallel
|
||||
const [
|
||||
patterns,
|
||||
languages,
|
||||
sources,
|
||||
resolutions,
|
||||
qualityModifiers,
|
||||
releaseTypes,
|
||||
indexerFlags,
|
||||
sizes,
|
||||
years
|
||||
] = await Promise.all([
|
||||
// Patterns with regex
|
||||
db
|
||||
.selectFrom('condition_patterns as cp')
|
||||
.innerJoin('regular_expressions as re', 're.name', 'cp.regular_expression_name')
|
||||
.select(['cp.condition_name', 're.name', 're.pattern'])
|
||||
.where('cp.custom_format_name', '=', formatName)
|
||||
.where('cp.condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Languages
|
||||
db
|
||||
.selectFrom('condition_languages as cl')
|
||||
.innerJoin('languages as l', 'l.name', 'cl.language_name')
|
||||
.select(['cl.condition_name', 'l.name', 'cl.except_language'])
|
||||
.where('cl.custom_format_name', '=', formatName)
|
||||
.where('cl.condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Sources
|
||||
db
|
||||
.selectFrom('condition_sources')
|
||||
.select(['condition_name', 'source'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Resolutions
|
||||
db
|
||||
.selectFrom('condition_resolutions')
|
||||
.select(['condition_name', 'resolution'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Quality modifiers
|
||||
db
|
||||
.selectFrom('condition_quality_modifiers')
|
||||
.select(['condition_name', 'quality_modifier'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Release types
|
||||
db
|
||||
.selectFrom('condition_release_types')
|
||||
.select(['condition_name', 'release_type'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Indexer flags
|
||||
db
|
||||
.selectFrom('condition_indexer_flags')
|
||||
.select(['condition_name', 'flag'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Sizes
|
||||
db
|
||||
.selectFrom('condition_sizes')
|
||||
.select(['condition_name', 'min_bytes', 'max_bytes'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Years
|
||||
db
|
||||
.selectFrom('condition_years')
|
||||
.select(['condition_name', 'min_year', 'max_year'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute()
|
||||
]);
|
||||
|
||||
// Build lookup maps using condition_name as key
|
||||
const patternsMap = new Map<string, { name: string; pattern: string }[]>();
|
||||
for (const p of patterns) {
|
||||
if (!patternsMap.has(p.condition_name)) {
|
||||
patternsMap.set(p.condition_name, []);
|
||||
}
|
||||
patternsMap.get(p.condition_name)!.push({ name: p.name, pattern: p.pattern });
|
||||
}
|
||||
|
||||
const languagesMap = new Map<string, { name: string; except: boolean }[]>();
|
||||
for (const l of languages) {
|
||||
if (!languagesMap.has(l.condition_name)) {
|
||||
languagesMap.set(l.condition_name, []);
|
||||
}
|
||||
languagesMap.get(l.condition_name)!.push({
|
||||
name: l.name,
|
||||
except: l.except_language === 1
|
||||
});
|
||||
}
|
||||
|
||||
const sourcesMap = new Map<string, string[]>();
|
||||
for (const s of sources) {
|
||||
if (!sourcesMap.has(s.condition_name)) {
|
||||
sourcesMap.set(s.condition_name, []);
|
||||
}
|
||||
sourcesMap.get(s.condition_name)!.push(s.source);
|
||||
}
|
||||
|
||||
const resolutionsMap = new Map<string, string[]>();
|
||||
for (const r of resolutions) {
|
||||
if (!resolutionsMap.has(r.condition_name)) {
|
||||
resolutionsMap.set(r.condition_name, []);
|
||||
}
|
||||
resolutionsMap.get(r.condition_name)!.push(r.resolution);
|
||||
}
|
||||
|
||||
const qualityModifiersMap = new Map<string, string[]>();
|
||||
for (const q of qualityModifiers) {
|
||||
if (!qualityModifiersMap.has(q.condition_name)) {
|
||||
qualityModifiersMap.set(q.condition_name, []);
|
||||
}
|
||||
qualityModifiersMap.get(q.condition_name)!.push(q.quality_modifier);
|
||||
}
|
||||
|
||||
const releaseTypesMap = new Map<string, string[]>();
|
||||
for (const r of releaseTypes) {
|
||||
if (!releaseTypesMap.has(r.condition_name)) {
|
||||
releaseTypesMap.set(r.condition_name, []);
|
||||
}
|
||||
releaseTypesMap.get(r.condition_name)!.push(r.release_type);
|
||||
}
|
||||
|
||||
const indexerFlagsMap = new Map<string, string[]>();
|
||||
for (const f of indexerFlags) {
|
||||
if (!indexerFlagsMap.has(f.condition_name)) {
|
||||
indexerFlagsMap.set(f.condition_name, []);
|
||||
}
|
||||
indexerFlagsMap.get(f.condition_name)!.push(f.flag);
|
||||
}
|
||||
|
||||
const sizesMap = new Map<string, { minBytes: number | null; maxBytes: number | null }>();
|
||||
for (const s of sizes) {
|
||||
sizesMap.set(s.condition_name, {
|
||||
minBytes: s.min_bytes,
|
||||
maxBytes: s.max_bytes
|
||||
});
|
||||
}
|
||||
|
||||
const yearsMap = new Map<string, { minYear: number | null; maxYear: number | null }>();
|
||||
for (const y of years) {
|
||||
yearsMap.set(y.condition_name, {
|
||||
minYear: y.min_year,
|
||||
maxYear: y.max_year
|
||||
});
|
||||
}
|
||||
|
||||
// Build final result
|
||||
return conditions.map((c) => ({
|
||||
name: c.name,
|
||||
type: c.type,
|
||||
arrType: c.arr_type as 'all' | 'radarr' | 'sonarr',
|
||||
negate: c.negate === 1,
|
||||
required: c.required === 1,
|
||||
patterns: patternsMap.get(c.name),
|
||||
languages: languagesMap.get(c.name),
|
||||
sources: sourcesMap.get(c.name),
|
||||
resolutions: resolutionsMap.get(c.name),
|
||||
qualityModifiers: qualityModifiersMap.get(c.name),
|
||||
releaseTypes: releaseTypesMap.get(c.name),
|
||||
indexerFlags: indexerFlagsMap.get(c.name),
|
||||
size: sizesMap.get(c.name),
|
||||
years: yearsMap.get(c.name)
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Custom format condition queries and mutations
|
||||
*/
|
||||
|
||||
// Read
|
||||
export { getConditionsForEvaluation, getAllConditionsForEvaluation, listConditions } from './read.ts';
|
||||
|
||||
// Update
|
||||
export { updateConditions } from './update.ts';
|
||||
449
src/lib/server/pcd/queries/customFormats/conditions/read.ts
Normal file
449
src/lib/server/pcd/queries/customFormats/conditions/read.ts
Normal file
@@ -0,0 +1,449 @@
|
||||
/**
|
||||
* Custom format condition read queries for test evaluation
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '$pcd/cache.ts';
|
||||
import type { ConditionData, ConditionListItem, CustomFormatWithConditions } from '$shared/pcd/display.ts';
|
||||
|
||||
/**
|
||||
* Get all conditions for a custom format with full data for evaluation
|
||||
*/
|
||||
export async function getConditionsForEvaluation(
|
||||
cache: PCDCache,
|
||||
formatName: string
|
||||
): Promise<ConditionData[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
// Get base conditions
|
||||
const conditions = await db
|
||||
.selectFrom('custom_format_conditions')
|
||||
.select(['custom_format_name', 'name', 'type', 'arr_type', 'negate', 'required'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.execute();
|
||||
|
||||
if (conditions.length === 0) return [];
|
||||
|
||||
const conditionNames = conditions.map((c) => c.name);
|
||||
|
||||
// Get all related data in parallel
|
||||
const [
|
||||
patterns,
|
||||
languages,
|
||||
sources,
|
||||
resolutions,
|
||||
qualityModifiers,
|
||||
releaseTypes,
|
||||
indexerFlags,
|
||||
sizes,
|
||||
years
|
||||
] = await Promise.all([
|
||||
// Patterns with regex
|
||||
db
|
||||
.selectFrom('condition_patterns as cp')
|
||||
.innerJoin('regular_expressions as re', 're.name', 'cp.regular_expression_name')
|
||||
.select(['cp.condition_name', 're.name', 're.pattern'])
|
||||
.where('cp.custom_format_name', '=', formatName)
|
||||
.where('cp.condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Languages
|
||||
db
|
||||
.selectFrom('condition_languages as cl')
|
||||
.innerJoin('languages as l', 'l.name', 'cl.language_name')
|
||||
.select(['cl.condition_name', 'l.name', 'cl.except_language'])
|
||||
.where('cl.custom_format_name', '=', formatName)
|
||||
.where('cl.condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Sources
|
||||
db
|
||||
.selectFrom('condition_sources')
|
||||
.select(['condition_name', 'source'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Resolutions
|
||||
db
|
||||
.selectFrom('condition_resolutions')
|
||||
.select(['condition_name', 'resolution'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Quality modifiers
|
||||
db
|
||||
.selectFrom('condition_quality_modifiers')
|
||||
.select(['condition_name', 'quality_modifier'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Release types
|
||||
db
|
||||
.selectFrom('condition_release_types')
|
||||
.select(['condition_name', 'release_type'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Indexer flags
|
||||
db
|
||||
.selectFrom('condition_indexer_flags')
|
||||
.select(['condition_name', 'flag'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Sizes
|
||||
db
|
||||
.selectFrom('condition_sizes')
|
||||
.select(['condition_name', 'min_bytes', 'max_bytes'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute(),
|
||||
|
||||
// Years
|
||||
db
|
||||
.selectFrom('condition_years')
|
||||
.select(['condition_name', 'min_year', 'max_year'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.where('condition_name', 'in', conditionNames)
|
||||
.execute()
|
||||
]);
|
||||
|
||||
// Build lookup maps using condition_name as key
|
||||
const patternsMap = new Map<string, { name: string; pattern: string }[]>();
|
||||
for (const p of patterns) {
|
||||
if (!patternsMap.has(p.condition_name)) {
|
||||
patternsMap.set(p.condition_name, []);
|
||||
}
|
||||
patternsMap.get(p.condition_name)!.push({ name: p.name, pattern: p.pattern });
|
||||
}
|
||||
|
||||
const languagesMap = new Map<string, { name: string; except: boolean }[]>();
|
||||
for (const l of languages) {
|
||||
if (!languagesMap.has(l.condition_name)) {
|
||||
languagesMap.set(l.condition_name, []);
|
||||
}
|
||||
languagesMap.get(l.condition_name)!.push({
|
||||
name: l.name,
|
||||
except: l.except_language === 1
|
||||
});
|
||||
}
|
||||
|
||||
const sourcesMap = new Map<string, string[]>();
|
||||
for (const s of sources) {
|
||||
if (!sourcesMap.has(s.condition_name)) {
|
||||
sourcesMap.set(s.condition_name, []);
|
||||
}
|
||||
sourcesMap.get(s.condition_name)!.push(s.source);
|
||||
}
|
||||
|
||||
const resolutionsMap = new Map<string, string[]>();
|
||||
for (const r of resolutions) {
|
||||
if (!resolutionsMap.has(r.condition_name)) {
|
||||
resolutionsMap.set(r.condition_name, []);
|
||||
}
|
||||
resolutionsMap.get(r.condition_name)!.push(r.resolution);
|
||||
}
|
||||
|
||||
const qualityModifiersMap = new Map<string, string[]>();
|
||||
for (const q of qualityModifiers) {
|
||||
if (!qualityModifiersMap.has(q.condition_name)) {
|
||||
qualityModifiersMap.set(q.condition_name, []);
|
||||
}
|
||||
qualityModifiersMap.get(q.condition_name)!.push(q.quality_modifier);
|
||||
}
|
||||
|
||||
const releaseTypesMap = new Map<string, string[]>();
|
||||
for (const r of releaseTypes) {
|
||||
if (!releaseTypesMap.has(r.condition_name)) {
|
||||
releaseTypesMap.set(r.condition_name, []);
|
||||
}
|
||||
releaseTypesMap.get(r.condition_name)!.push(r.release_type);
|
||||
}
|
||||
|
||||
const indexerFlagsMap = new Map<string, string[]>();
|
||||
for (const f of indexerFlags) {
|
||||
if (!indexerFlagsMap.has(f.condition_name)) {
|
||||
indexerFlagsMap.set(f.condition_name, []);
|
||||
}
|
||||
indexerFlagsMap.get(f.condition_name)!.push(f.flag);
|
||||
}
|
||||
|
||||
const sizesMap = new Map<string, { minBytes: number | null; maxBytes: number | null }>();
|
||||
for (const s of sizes) {
|
||||
sizesMap.set(s.condition_name, {
|
||||
minBytes: s.min_bytes,
|
||||
maxBytes: s.max_bytes
|
||||
});
|
||||
}
|
||||
|
||||
const yearsMap = new Map<string, { minYear: number | null; maxYear: number | null }>();
|
||||
for (const y of years) {
|
||||
yearsMap.set(y.condition_name, {
|
||||
minYear: y.min_year,
|
||||
maxYear: y.max_year
|
||||
});
|
||||
}
|
||||
|
||||
// Build final result
|
||||
return conditions.map((c) => ({
|
||||
name: c.name,
|
||||
type: c.type,
|
||||
arrType: c.arr_type as 'all' | 'radarr' | 'sonarr',
|
||||
negate: c.negate === 1,
|
||||
required: c.required === 1,
|
||||
patterns: patternsMap.get(c.name),
|
||||
languages: languagesMap.get(c.name),
|
||||
sources: sourcesMap.get(c.name),
|
||||
resolutions: resolutionsMap.get(c.name),
|
||||
qualityModifiers: qualityModifiersMap.get(c.name),
|
||||
releaseTypes: releaseTypesMap.get(c.name),
|
||||
indexerFlags: indexerFlagsMap.get(c.name),
|
||||
size: sizesMap.get(c.name),
|
||||
years: yearsMap.get(c.name)
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all custom formats with their conditions for batch evaluation
|
||||
* Optimized to fetch all data in minimal queries
|
||||
*/
|
||||
export async function getAllConditionsForEvaluation(
|
||||
cache: PCDCache
|
||||
): Promise<CustomFormatWithConditions[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
// Get all custom formats
|
||||
const formats = await db
|
||||
.selectFrom('custom_formats')
|
||||
.select(['id', 'name'])
|
||||
.orderBy('name')
|
||||
.execute();
|
||||
|
||||
if (formats.length === 0) return [];
|
||||
|
||||
// Get all conditions for all formats
|
||||
const conditions = await db
|
||||
.selectFrom('custom_format_conditions')
|
||||
.select(['custom_format_name', 'name', 'type', 'arr_type', 'negate', 'required'])
|
||||
.execute();
|
||||
|
||||
if (conditions.length === 0) {
|
||||
return formats.map((f) => ({ name: f.name, conditions: [] }));
|
||||
}
|
||||
|
||||
// Build composite keys for condition lookups
|
||||
const conditionKeys = conditions.map((c) => `${c.custom_format_name}|${c.name}`);
|
||||
|
||||
// Get all related data in parallel
|
||||
const [
|
||||
patterns,
|
||||
languages,
|
||||
sources,
|
||||
resolutions,
|
||||
qualityModifiers,
|
||||
releaseTypes,
|
||||
indexerFlags,
|
||||
sizes,
|
||||
years
|
||||
] = await Promise.all([
|
||||
// Patterns with regex
|
||||
db
|
||||
.selectFrom('condition_patterns as cp')
|
||||
.innerJoin('regular_expressions as re', 're.name', 'cp.regular_expression_name')
|
||||
.select(['cp.custom_format_name', 'cp.condition_name', 're.name', 're.pattern'])
|
||||
.execute(),
|
||||
|
||||
// Languages
|
||||
db
|
||||
.selectFrom('condition_languages as cl')
|
||||
.innerJoin('languages as l', 'l.name', 'cl.language_name')
|
||||
.select(['cl.custom_format_name', 'cl.condition_name', 'l.name', 'cl.except_language'])
|
||||
.execute(),
|
||||
|
||||
// Sources
|
||||
db
|
||||
.selectFrom('condition_sources')
|
||||
.select(['custom_format_name', 'condition_name', 'source'])
|
||||
.execute(),
|
||||
|
||||
// Resolutions
|
||||
db
|
||||
.selectFrom('condition_resolutions')
|
||||
.select(['custom_format_name', 'condition_name', 'resolution'])
|
||||
.execute(),
|
||||
|
||||
// Quality modifiers
|
||||
db
|
||||
.selectFrom('condition_quality_modifiers')
|
||||
.select(['custom_format_name', 'condition_name', 'quality_modifier'])
|
||||
.execute(),
|
||||
|
||||
// Release types
|
||||
db
|
||||
.selectFrom('condition_release_types')
|
||||
.select(['custom_format_name', 'condition_name', 'release_type'])
|
||||
.execute(),
|
||||
|
||||
// Indexer flags
|
||||
db
|
||||
.selectFrom('condition_indexer_flags')
|
||||
.select(['custom_format_name', 'condition_name', 'flag'])
|
||||
.execute(),
|
||||
|
||||
// Sizes
|
||||
db
|
||||
.selectFrom('condition_sizes')
|
||||
.select(['custom_format_name', 'condition_name', 'min_bytes', 'max_bytes'])
|
||||
.execute(),
|
||||
|
||||
// Years
|
||||
db
|
||||
.selectFrom('condition_years')
|
||||
.select(['custom_format_name', 'condition_name', 'min_year', 'max_year'])
|
||||
.execute()
|
||||
]);
|
||||
|
||||
// Build lookup maps using composite key (custom_format_name|condition_name)
|
||||
const patternsMap = new Map<string, { name: string; pattern: string }[]>();
|
||||
for (const p of patterns) {
|
||||
const key = `${p.custom_format_name}|${p.condition_name}`;
|
||||
if (!patternsMap.has(key)) {
|
||||
patternsMap.set(key, []);
|
||||
}
|
||||
patternsMap.get(key)!.push({ name: p.name, pattern: p.pattern });
|
||||
}
|
||||
|
||||
const languagesMap = new Map<string, { name: string; except: boolean }[]>();
|
||||
for (const l of languages) {
|
||||
const key = `${l.custom_format_name}|${l.condition_name}`;
|
||||
if (!languagesMap.has(key)) {
|
||||
languagesMap.set(key, []);
|
||||
}
|
||||
languagesMap.get(key)!.push({
|
||||
name: l.name,
|
||||
except: l.except_language === 1
|
||||
});
|
||||
}
|
||||
|
||||
const sourcesMap = new Map<string, string[]>();
|
||||
for (const s of sources) {
|
||||
const key = `${s.custom_format_name}|${s.condition_name}`;
|
||||
if (!sourcesMap.has(key)) {
|
||||
sourcesMap.set(key, []);
|
||||
}
|
||||
sourcesMap.get(key)!.push(s.source);
|
||||
}
|
||||
|
||||
const resolutionsMap = new Map<string, string[]>();
|
||||
for (const r of resolutions) {
|
||||
const key = `${r.custom_format_name}|${r.condition_name}`;
|
||||
if (!resolutionsMap.has(key)) {
|
||||
resolutionsMap.set(key, []);
|
||||
}
|
||||
resolutionsMap.get(key)!.push(r.resolution);
|
||||
}
|
||||
|
||||
const qualityModifiersMap = new Map<string, string[]>();
|
||||
for (const q of qualityModifiers) {
|
||||
const key = `${q.custom_format_name}|${q.condition_name}`;
|
||||
if (!qualityModifiersMap.has(key)) {
|
||||
qualityModifiersMap.set(key, []);
|
||||
}
|
||||
qualityModifiersMap.get(key)!.push(q.quality_modifier);
|
||||
}
|
||||
|
||||
const releaseTypesMap = new Map<string, string[]>();
|
||||
for (const r of releaseTypes) {
|
||||
const key = `${r.custom_format_name}|${r.condition_name}`;
|
||||
if (!releaseTypesMap.has(key)) {
|
||||
releaseTypesMap.set(key, []);
|
||||
}
|
||||
releaseTypesMap.get(key)!.push(r.release_type);
|
||||
}
|
||||
|
||||
const indexerFlagsMap = new Map<string, string[]>();
|
||||
for (const f of indexerFlags) {
|
||||
const key = `${f.custom_format_name}|${f.condition_name}`;
|
||||
if (!indexerFlagsMap.has(key)) {
|
||||
indexerFlagsMap.set(key, []);
|
||||
}
|
||||
indexerFlagsMap.get(key)!.push(f.flag);
|
||||
}
|
||||
|
||||
const sizesMap = new Map<string, { minBytes: number | null; maxBytes: number | null }>();
|
||||
for (const s of sizes) {
|
||||
const key = `${s.custom_format_name}|${s.condition_name}`;
|
||||
sizesMap.set(key, {
|
||||
minBytes: s.min_bytes,
|
||||
maxBytes: s.max_bytes
|
||||
});
|
||||
}
|
||||
|
||||
const yearsMap = new Map<string, { minYear: number | null; maxYear: number | null }>();
|
||||
for (const y of years) {
|
||||
const key = `${y.custom_format_name}|${y.condition_name}`;
|
||||
yearsMap.set(key, {
|
||||
minYear: y.min_year,
|
||||
maxYear: y.max_year
|
||||
});
|
||||
}
|
||||
|
||||
// Build conditions by format
|
||||
const conditionsByFormat = new Map<string, ConditionData[]>();
|
||||
for (const c of conditions) {
|
||||
if (!conditionsByFormat.has(c.custom_format_name)) {
|
||||
conditionsByFormat.set(c.custom_format_name, []);
|
||||
}
|
||||
const key = `${c.custom_format_name}|${c.name}`;
|
||||
conditionsByFormat.get(c.custom_format_name)!.push({
|
||||
name: c.name,
|
||||
type: c.type,
|
||||
arrType: c.arr_type as 'all' | 'radarr' | 'sonarr',
|
||||
negate: c.negate === 1,
|
||||
required: c.required === 1,
|
||||
patterns: patternsMap.get(key),
|
||||
languages: languagesMap.get(key),
|
||||
sources: sourcesMap.get(key),
|
||||
resolutions: resolutionsMap.get(key),
|
||||
qualityModifiers: qualityModifiersMap.get(key),
|
||||
releaseTypes: releaseTypesMap.get(key),
|
||||
indexerFlags: indexerFlagsMap.get(key),
|
||||
size: sizesMap.get(key),
|
||||
years: yearsMap.get(key)
|
||||
});
|
||||
}
|
||||
|
||||
// Build final result
|
||||
return formats.map((f) => ({
|
||||
name: f.name,
|
||||
conditions: conditionsByFormat.get(f.name) || []
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conditions for a custom format (basic info for list display)
|
||||
*/
|
||||
export async function listConditions(
|
||||
cache: PCDCache,
|
||||
formatName: string
|
||||
): Promise<ConditionListItem[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
const conditions = await db
|
||||
.selectFrom('custom_format_conditions')
|
||||
.select(['name', 'type', 'negate', 'required'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.orderBy('name')
|
||||
.execute();
|
||||
|
||||
return conditions.map((c) => ({
|
||||
name: c.name,
|
||||
type: c.type,
|
||||
negate: c.negate === 1,
|
||||
required: c.required === 1
|
||||
}));
|
||||
}
|
||||
@@ -7,12 +7,12 @@
|
||||
* - Updating existing conditions
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
import type { ConditionData } from './conditions.ts';
|
||||
import type { PCDCache } from '$pcd/cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '$pcd/writer.ts';
|
||||
import type { ConditionData } from '$shared/pcd/display.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
|
||||
export interface UpdateConditionsOptions {
|
||||
interface UpdateConditionsOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
@@ -2,17 +2,17 @@
|
||||
* Create a custom format 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 CreateCustomFormatInput {
|
||||
interface CreateCustomFormatInput {
|
||||
name: string;
|
||||
description: string | null;
|
||||
includeInRename: boolean;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface CreateCustomFormatOptions {
|
||||
interface CreateCustomFormatOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
* Delete a custom format 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 DeleteCustomFormatOptions {
|
||||
interface DeleteCustomFormatOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Evaluates conditions against parsed release titles
|
||||
*/
|
||||
|
||||
import { logger } from '$logger/logger.ts';
|
||||
import type { ParseResult } from '$lib/server/utils/arr/parser/types.ts';
|
||||
import {
|
||||
QualitySource,
|
||||
@@ -12,46 +11,13 @@ import {
|
||||
ReleaseType,
|
||||
Language
|
||||
} from '$lib/server/utils/arr/parser/types.ts';
|
||||
import type { ConditionData } from './conditions.ts';
|
||||
|
||||
export interface ConditionResult {
|
||||
conditionName: string;
|
||||
conditionType: string;
|
||||
matched: boolean;
|
||||
required: boolean;
|
||||
negate: boolean;
|
||||
/** Final result after applying negate */
|
||||
passes: boolean;
|
||||
/** What the condition expected */
|
||||
expected: string;
|
||||
/** What was actually found in the parsed title */
|
||||
actual: string;
|
||||
}
|
||||
|
||||
export interface EvaluationResult {
|
||||
/** Whether the custom format matches overall */
|
||||
matches: boolean;
|
||||
/** Individual condition results */
|
||||
conditions: ConditionResult[];
|
||||
}
|
||||
|
||||
/** Serializable parsed info for frontend display */
|
||||
export interface ParsedInfo {
|
||||
source: string;
|
||||
resolution: string;
|
||||
modifier: string;
|
||||
languages: string[];
|
||||
releaseGroup: string | null;
|
||||
year: number;
|
||||
edition: string | null;
|
||||
releaseType: string | null;
|
||||
}
|
||||
|
||||
/** Custom format with conditions for evaluation */
|
||||
export interface CustomFormatWithConditions {
|
||||
name: string;
|
||||
conditions: ConditionData[];
|
||||
}
|
||||
import type {
|
||||
ConditionData,
|
||||
ConditionResult,
|
||||
EvaluationResult,
|
||||
ParsedInfo,
|
||||
CustomFormatWithConditions
|
||||
} from '$shared/pcd/display.ts';
|
||||
|
||||
/**
|
||||
* Extract all unique regex patterns from custom format conditions
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Custom format general queries and mutations
|
||||
*/
|
||||
|
||||
// Read
|
||||
export { general } from './read.ts';
|
||||
|
||||
// Update
|
||||
export { updateGeneral } from './update.ts';
|
||||
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Custom format general queries
|
||||
* Custom format general read queries
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import type { CustomFormatGeneral } from './types.ts';
|
||||
import type { PCDCache } from '$pcd/cache.ts';
|
||||
import type { CustomFormatGeneral } from '$shared/pcd/display.ts';
|
||||
|
||||
/**
|
||||
* Get general information for a single custom format
|
||||
@@ -2,19 +2,19 @@
|
||||
* Update custom format general information
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
import type { CustomFormatGeneral } from './types.ts';
|
||||
import type { PCDCache } from '$pcd/cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '$pcd/writer.ts';
|
||||
import type { CustomFormatGeneral } from '$shared/pcd/display.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
|
||||
export interface UpdateGeneralInput {
|
||||
interface UpdateGeneralInput {
|
||||
name: string;
|
||||
description: string;
|
||||
includeInRename: boolean;
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
export interface UpdateGeneralOptions {
|
||||
interface UpdateGeneralOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
@@ -1,39 +1,25 @@
|
||||
/**
|
||||
* Custom Format queries and mutations
|
||||
*
|
||||
* Types: import from '$shared/pcd/display.ts'
|
||||
*/
|
||||
|
||||
// Export all types
|
||||
export type {
|
||||
CustomFormatTableRow,
|
||||
ConditionRef,
|
||||
CustomFormatBasic,
|
||||
CustomFormatGeneral,
|
||||
CustomFormatTest
|
||||
} from './types.ts';
|
||||
export type { CreateTestInput, CreateTestOptions } from './testCreate.ts';
|
||||
export type { UpdateTestInput, UpdateTestOptions } from './testUpdate.ts';
|
||||
export type { DeleteTestOptions } from './testDelete.ts';
|
||||
export type { ConditionData } from './conditions.ts';
|
||||
export type { ConditionListItem } from './listConditions.ts';
|
||||
export type { ConditionResult, EvaluationResult, ParsedInfo } from './evaluator.ts';
|
||||
export type { UpdateGeneralInput, UpdateGeneralOptions } from './updateGeneral.ts';
|
||||
export type { UpdateConditionsOptions } from './updateConditions.ts';
|
||||
export type { CreateCustomFormatInput, CreateCustomFormatOptions } from './create.ts';
|
||||
export type { DeleteCustomFormatOptions } from './delete.ts';
|
||||
// General queries/mutations
|
||||
export { general } from './general/index.ts';
|
||||
export { updateGeneral } from './general/index.ts';
|
||||
|
||||
// Export query functions (reads)
|
||||
// Condition queries/mutations
|
||||
export { getConditionsForEvaluation, getAllConditionsForEvaluation, listConditions } from './conditions/index.ts';
|
||||
export { updateConditions } from './conditions/index.ts';
|
||||
|
||||
// Test queries/mutations
|
||||
export { getById, listTests, getTest } from './tests/index.ts';
|
||||
export { createTest } from './tests/index.ts';
|
||||
export { updateTest } from './tests/index.ts';
|
||||
export { deleteTest } from './tests/index.ts';
|
||||
|
||||
// Main custom format operations
|
||||
export { list } from './list.ts';
|
||||
export { general } from './general.ts';
|
||||
export { getById, listTests, getTest } from './tests.ts';
|
||||
export { getConditionsForEvaluation } from './conditions.ts';
|
||||
export { listConditions } from './listConditions.ts';
|
||||
export { evaluateCustomFormat, getParsedInfo } from './evaluator.ts';
|
||||
|
||||
// Export mutation functions (writes via PCD operations)
|
||||
export { create } from './create.ts';
|
||||
export { remove } from './delete.ts';
|
||||
export { createTest } from './testCreate.ts';
|
||||
export { updateTest } from './testUpdate.ts';
|
||||
export { deleteTest } from './testDelete.ts';
|
||||
export { updateGeneral } from './updateGeneral.ts';
|
||||
export { updateConditions } from './updateConditions.ts';
|
||||
export { evaluateCustomFormat, getParsedInfo, extractAllPatterns } from './evaluator.ts';
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
* Custom format list queries
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import type { Tag } from '$shared/pcd/display.ts';
|
||||
import type { CustomFormatTableRow, ConditionRef } from './types.ts';
|
||||
import type { PCDCache } from '$pcd/cache.ts';
|
||||
import type { Tag, CustomFormatTableRow, ConditionRef } from '$shared/pcd/display.ts';
|
||||
|
||||
/**
|
||||
* Get custom formats with full data for table/card views
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Custom format condition list query
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
|
||||
/** Condition item for list display */
|
||||
export interface ConditionListItem {
|
||||
name: string;
|
||||
type: string;
|
||||
negate: boolean;
|
||||
required: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all conditions for a custom format (basic info for list display)
|
||||
*/
|
||||
export async function listConditions(
|
||||
cache: PCDCache,
|
||||
formatName: string
|
||||
): Promise<ConditionListItem[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
const conditions = await db
|
||||
.selectFrom('custom_format_conditions')
|
||||
.select(['name', 'type', 'negate', 'required'])
|
||||
.where('custom_format_name', '=', formatName)
|
||||
.orderBy('name')
|
||||
.execute();
|
||||
|
||||
return conditions.map((c) => ({
|
||||
name: c.name,
|
||||
type: c.type,
|
||||
negate: c.negate === 1,
|
||||
required: c.required === 1
|
||||
}));
|
||||
}
|
||||
@@ -2,16 +2,16 @@
|
||||
* Create a custom format test operation
|
||||
*/
|
||||
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
import { writeOperation, type OperationLayer } from '$pcd/writer.ts';
|
||||
|
||||
export interface CreateTestInput {
|
||||
interface CreateTestInput {
|
||||
title: string;
|
||||
type: 'movie' | 'series';
|
||||
should_match: boolean;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface CreateTestOptions {
|
||||
interface CreateTestOptions {
|
||||
databaseId: number;
|
||||
layer: OperationLayer;
|
||||
formatName: string;
|
||||
@@ -2,10 +2,10 @@
|
||||
* Delete a custom format test operation
|
||||
*/
|
||||
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
import type { CustomFormatTest } from './types.ts';
|
||||
import { writeOperation, type OperationLayer } from '$pcd/writer.ts';
|
||||
import type { CustomFormatTest } from '$shared/pcd/display.ts';
|
||||
|
||||
export interface DeleteTestOptions {
|
||||
interface DeleteTestOptions {
|
||||
databaseId: number;
|
||||
layer: OperationLayer;
|
||||
formatName: string;
|
||||
15
src/lib/server/pcd/queries/customFormats/tests/index.ts
Normal file
15
src/lib/server/pcd/queries/customFormats/tests/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Custom format test queries and mutations
|
||||
*/
|
||||
|
||||
// Read
|
||||
export { getById, listTests, getTest } from './read.ts';
|
||||
|
||||
// Create
|
||||
export { createTest } from './create.ts';
|
||||
|
||||
// Update
|
||||
export { updateTest } from './update.ts';
|
||||
|
||||
// Delete
|
||||
export { deleteTest } from './delete.ts';
|
||||
@@ -2,8 +2,8 @@
|
||||
* Custom format test read queries
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import type { CustomFormatBasic, CustomFormatTest } from './types.ts';
|
||||
import type { PCDCache } from '$pcd/cache.ts';
|
||||
import type { CustomFormatBasic, CustomFormatTest } from '$shared/pcd/display.ts';
|
||||
|
||||
/**
|
||||
* Get custom format basic info by ID
|
||||
@@ -2,17 +2,17 @@
|
||||
* Update a custom format test operation
|
||||
*/
|
||||
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
import type { CustomFormatTest } from './types.ts';
|
||||
import { writeOperation, type OperationLayer } from '$pcd/writer.ts';
|
||||
import type { CustomFormatTest } from '$shared/pcd/display.ts';
|
||||
|
||||
export interface UpdateTestInput {
|
||||
interface UpdateTestInput {
|
||||
title: string;
|
||||
type: 'movie' | 'series';
|
||||
should_match: boolean;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateTestOptions {
|
||||
interface UpdateTestOptions {
|
||||
databaseId: number;
|
||||
layer: OperationLayer;
|
||||
formatName: string;
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* Custom Format query-specific types
|
||||
*/
|
||||
|
||||
import type { Tag } from '$shared/pcd/display.ts';
|
||||
|
||||
/** Condition reference for display */
|
||||
export interface ConditionRef {
|
||||
name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
negate: boolean;
|
||||
}
|
||||
|
||||
/** Custom format data for table/card views */
|
||||
export interface CustomFormatTableRow {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
tags: Tag[];
|
||||
conditions: ConditionRef[];
|
||||
testCount: number;
|
||||
}
|
||||
|
||||
/** Custom format basic info */
|
||||
export interface CustomFormatBasic {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string | null;
|
||||
include_in_rename: boolean;
|
||||
}
|
||||
|
||||
/** Custom format general information (for general tab) */
|
||||
export interface CustomFormatGeneral {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
include_in_rename: boolean;
|
||||
tags: Tag[];
|
||||
}
|
||||
|
||||
/** Custom format test case */
|
||||
export interface CustomFormatTest {
|
||||
custom_format_name: string;
|
||||
title: string;
|
||||
type: string;
|
||||
should_match: boolean;
|
||||
description: string | null;
|
||||
}
|
||||
@@ -109,3 +109,98 @@ export type TestRelease = Omit<
|
||||
export type TestEntity = Omit<TestEntitiesRow, 'created_at' | 'updated_at'> & {
|
||||
releases: TestRelease[];
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// CUSTOM FORMATS
|
||||
// ============================================================================
|
||||
|
||||
import type {
|
||||
CustomFormatsRow,
|
||||
CustomFormatConditionsRow,
|
||||
CustomFormatTestsRow
|
||||
} from './types.ts';
|
||||
|
||||
/** Condition reference for display (minimal info) */
|
||||
export type ConditionRef = Pick<CustomFormatConditionsRow, 'name' | 'type' | 'required' | 'negate'>;
|
||||
|
||||
/** Condition item for list display */
|
||||
export type ConditionListItem = ConditionRef;
|
||||
|
||||
/** Custom format basic info */
|
||||
export type CustomFormatBasic = Omit<CustomFormatsRow, 'created_at' | 'updated_at'>;
|
||||
|
||||
/** Custom format test case */
|
||||
export type CustomFormatTest = Omit<CustomFormatTestsRow, 'id' | 'created_at'>;
|
||||
|
||||
/** Custom format data for table/card views (with JOINed data) */
|
||||
export type CustomFormatTableRow = Omit<CustomFormatsRow, 'include_in_rename' | 'created_at' | 'updated_at'> & {
|
||||
tags: Tag[];
|
||||
conditions: ConditionRef[];
|
||||
testCount: number;
|
||||
};
|
||||
|
||||
/** Custom format general information (for general tab) */
|
||||
export type CustomFormatGeneral = Omit<CustomFormatsRow, 'description' | 'created_at' | 'updated_at'> & {
|
||||
description: string; // non-nullable for form
|
||||
tags: Tag[];
|
||||
};
|
||||
|
||||
/** Full condition data for evaluation and editing (assembled from multiple tables) */
|
||||
export interface ConditionData {
|
||||
name: string;
|
||||
type: string;
|
||||
arrType: 'all' | 'radarr' | 'sonarr';
|
||||
negate: boolean;
|
||||
required: boolean;
|
||||
// Type-specific data (only one populated based on `type`)
|
||||
patterns?: { name: string; pattern: string }[];
|
||||
languages?: { name: string; except: boolean }[];
|
||||
sources?: string[];
|
||||
resolutions?: string[];
|
||||
qualityModifiers?: string[];
|
||||
releaseTypes?: string[];
|
||||
indexerFlags?: string[];
|
||||
size?: { minBytes: number | null; maxBytes: number | null };
|
||||
years?: { minYear: number | null; maxYear: number | null };
|
||||
}
|
||||
|
||||
/** Single condition evaluation result */
|
||||
export interface ConditionResult {
|
||||
conditionName: string;
|
||||
conditionType: string;
|
||||
matched: boolean;
|
||||
required: boolean;
|
||||
negate: boolean;
|
||||
/** Final result after applying negate */
|
||||
passes: boolean;
|
||||
/** What the condition expected */
|
||||
expected: string;
|
||||
/** What was actually found in the parsed title */
|
||||
actual: string;
|
||||
}
|
||||
|
||||
/** Full evaluation result of all conditions */
|
||||
export interface EvaluationResult {
|
||||
/** Whether the custom format matches overall */
|
||||
matches: boolean;
|
||||
/** Individual condition results */
|
||||
conditions: ConditionResult[];
|
||||
}
|
||||
|
||||
/** Serializable parsed info for frontend display */
|
||||
export interface ParsedInfo {
|
||||
source: string;
|
||||
resolution: string;
|
||||
modifier: string;
|
||||
languages: string[];
|
||||
releaseGroup: string | null;
|
||||
year: number;
|
||||
edition: string | null;
|
||||
releaseType: string | null;
|
||||
}
|
||||
|
||||
/** Custom format with conditions for batch evaluation */
|
||||
export interface CustomFormatWithConditions {
|
||||
name: string;
|
||||
conditions: ConditionData[];
|
||||
}
|
||||
|
||||
@@ -11,12 +11,12 @@ import {
|
||||
isParserHealthy,
|
||||
matchPatternsBatch
|
||||
} from '$lib/server/utils/arr/parser/index.ts';
|
||||
import { getAllConditionsForEvaluation } from '$pcd/queries/customFormats/allConditions.ts';
|
||||
import {
|
||||
getAllConditionsForEvaluation,
|
||||
evaluateCustomFormat,
|
||||
getParsedInfo,
|
||||
extractAllPatterns
|
||||
} from '$pcd/queries/customFormats/evaluator.ts';
|
||||
} from '$pcd/queries/customFormats/index.ts';
|
||||
import type { components } from '$api/v1.d.ts';
|
||||
|
||||
type EvaluateRequest = components['schemas']['EvaluateRequest'];
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { Info, Plus } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { CustomFormatTableRow } from '$pcd/queries/customFormats';
|
||||
import type { CustomFormatTableRow } from '$shared/pcd/display.ts';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as customFormatQueries from '$pcd/queries/customFormats/index.ts';
|
||||
import * as regularExpressionQueries from '$pcd/queries/regularExpressions/index.ts';
|
||||
import { getLanguagesWithSupport } from '$lib/server/sync/mappings.ts';
|
||||
import type { OperationLayer } from '$pcd/writer.ts';
|
||||
import type { ConditionData } from '$pcd/queries/customFormats/index.ts';
|
||||
import type { ConditionData } from '$shared/pcd/display.ts';
|
||||
|
||||
export const load: ServerLoad = async ({ params }) => {
|
||||
const { databaseId, id } = params;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { sortConditions } from '$lib/shared/conditionTypes';
|
||||
import { current, isDirty, initEdit, update } from '$lib/client/stores/dirty';
|
||||
import type { PageData } from './$types';
|
||||
import type { ConditionData } from '$pcd/queries/customFormats/index';
|
||||
import type { ConditionData } from '$shared/pcd/display.ts';
|
||||
|
||||
// Extended type with stable key for Svelte keying
|
||||
type KeyedCondition = ConditionData & { _key: string };
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
INDEXER_FLAG_VALUES,
|
||||
type ArrType
|
||||
} from '$lib/shared/conditionTypes';
|
||||
import type { ConditionData } from '$pcd/queries/customFormats/index';
|
||||
import type { ConditionData } from '$shared/pcd/display.ts';
|
||||
|
||||
const dispatch = createEventDispatcher<{
|
||||
remove: void;
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ServerLoad, Actions } from '@sveltejs/kit';
|
||||
import { pcdManager } from '$pcd/pcd.ts';
|
||||
import { canWriteToBase, type OperationLayer } from '$pcd/writer.ts';
|
||||
import * as customFormatQueries from '$pcd/queries/customFormats/index.ts';
|
||||
import type { ConditionResult, ParsedInfo } from '$pcd/queries/customFormats/index.ts';
|
||||
import type { ConditionResult, ParsedInfo } from '$shared/pcd/display.ts';
|
||||
import { parse, isParserHealthy } from '$lib/server/utils/arr/parser/client.ts';
|
||||
import type { MediaType } from '$lib/server/utils/arr/parser/types.ts';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { CustomFormatTableRow } from '$pcd/queries/customFormats';
|
||||
import type { CustomFormatTableRow } from '$shared/pcd/display.ts';
|
||||
import { Layers, FlaskConical } from 'lucide-svelte';
|
||||
import { marked } from 'marked';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import Table from '$ui/table/Table.svelte';
|
||||
import type { Column } from '$ui/table/types';
|
||||
import type { CustomFormatTableRow } from '$pcd/queries/customFormats';
|
||||
import type { CustomFormatTableRow } from '$shared/pcd/display.ts';
|
||||
import { Tag, FileText, Layers, FlaskConical } from 'lucide-svelte';
|
||||
import { marked } from 'marked';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
Reference in New Issue
Block a user