refactor(pcd): reorganize delayProfiles to CRUD pattern

This commit is contained in:
Sam Chau
2026-01-27 22:35:04 +10:30
parent be64e11599
commit 8a75f718b6
17 changed files with 147 additions and 158 deletions

View File

@@ -4,9 +4,9 @@
import type { PCDCache } from '../../cache.ts';
import { writeOperation, type OperationLayer } from '../../writer.ts';
import type { PreferredProtocol } from './types.ts';
import type { PreferredProtocol } from '$shared/pcd/display.ts';
export interface CreateDelayProfileInput {
interface CreateDelayProfileInput {
name: string;
preferredProtocol: PreferredProtocol;
usenetDelay: number;
@@ -16,7 +16,7 @@ export interface CreateDelayProfileInput {
minimumCfScore: number;
}
export interface CreateDelayProfileOptions {
interface CreateDelayProfileOptions {
databaseId: number;
cache: PCDCache;
layer: OperationLayer;

View File

@@ -4,14 +4,14 @@
import type { PCDCache } from '../../cache.ts';
import { writeOperation, type OperationLayer } from '../../writer.ts';
import type { DelayProfileTableRow } from './types.ts';
import type { DelayProfilesRow } from '$shared/pcd/display.ts';
export interface DeleteDelayProfileOptions {
interface DeleteDelayProfileOptions {
databaseId: number;
cache: PCDCache;
layer: OperationLayer;
/** The current profile data (for value guards) */
current: DelayProfileTableRow;
current: DelayProfilesRow;
}
/**

View File

@@ -1,45 +0,0 @@
/**
* Get a single delay profile by ID
*/
import type { PCDCache } from '../../cache.ts';
import type { DelayProfileTableRow, PreferredProtocol } from './types.ts';
/**
* Get a single delay profile by ID with all data
*/
export async function get(cache: PCDCache, id: number): Promise<DelayProfileTableRow | null> {
const db = cache.kb;
const profile = await db
.selectFrom('delay_profiles')
.select([
'id',
'name',
'preferred_protocol',
'usenet_delay',
'torrent_delay',
'bypass_if_highest_quality',
'bypass_if_above_custom_format_score',
'minimum_custom_format_score',
'created_at',
'updated_at'
])
.where('id', '=', id)
.executeTakeFirst();
if (!profile) return null;
return {
id: profile.id,
name: profile.name,
preferred_protocol: profile.preferred_protocol as PreferredProtocol,
usenet_delay: profile.usenet_delay,
torrent_delay: profile.torrent_delay,
bypass_if_highest_quality: profile.bypass_if_highest_quality === 1,
bypass_if_above_custom_format_score: profile.bypass_if_above_custom_format_score === 1,
minimum_custom_format_score: profile.minimum_custom_format_score,
created_at: profile.created_at,
updated_at: profile.updated_at
};
}

View File

@@ -1,17 +1,15 @@
/**
* Delay Profile queries and mutations
* Delay Profile CRUD operations
*/
// Export all types
export type { DelayProfileTableRow, PreferredProtocol } from './types.ts';
export type { CreateDelayProfileInput } from './create.ts';
export type { UpdateDelayProfileInput } from './update.ts';
// Read
export { list, get } from './read.ts';
// Export query functions
export { list } from './list.ts';
export { get } from './get.ts';
// Export mutation functions
// Create
export { create } from './create.ts';
// Update
export { update } from './update.ts';
// Delete
export { remove } from './delete.ts';

View File

@@ -1,43 +0,0 @@
/**
* Delay profile list queries
*/
import type { PCDCache } from '../../cache.ts';
import type { DelayProfileTableRow, PreferredProtocol } from './types.ts';
/**
* Get delay profiles with full data for table/card views
*/
export async function list(cache: PCDCache): Promise<DelayProfileTableRow[]> {
const db = cache.kb;
const profiles = await db
.selectFrom('delay_profiles')
.select([
'id',
'name',
'preferred_protocol',
'usenet_delay',
'torrent_delay',
'bypass_if_highest_quality',
'bypass_if_above_custom_format_score',
'minimum_custom_format_score',
'created_at',
'updated_at'
])
.orderBy('name')
.execute();
return profiles.map((profile) => ({
id: profile.id,
name: profile.name,
preferred_protocol: profile.preferred_protocol as PreferredProtocol,
usenet_delay: profile.usenet_delay,
torrent_delay: profile.torrent_delay,
bypass_if_highest_quality: profile.bypass_if_highest_quality === 1,
bypass_if_above_custom_format_score: profile.bypass_if_above_custom_format_score === 1,
minimum_custom_format_score: profile.minimum_custom_format_score,
created_at: profile.created_at,
updated_at: profile.updated_at
}));
}

View File

@@ -1,7 +0,0 @@
/**
* Delay Profile mutations - re-exports for cleaner imports
*/
export { create, type CreateDelayProfileInput, type CreateDelayProfileOptions } from './create.ts';
export { update, type UpdateDelayProfileInput, type UpdateDelayProfileOptions } from './update.ts';
export { remove, type DeleteDelayProfileOptions } from './delete.ts';

View File

@@ -0,0 +1,94 @@
/**
* Delay profile read operations
*/
/**
* Delay profile read operations
*/
import type { PCDCache } from '../../cache.ts';
import type { DelayProfilesRow, PreferredProtocol } from '$shared/pcd/display.ts';
/**
* Convert a database row to DelayProfilesRow with boolean conversion.
* SQLite returns 0/1 for booleans, we convert to true/false.
*/
function toDelayProfile(row: {
id: number;
name: string;
preferred_protocol: string;
usenet_delay: number | null;
torrent_delay: number | null;
bypass_if_highest_quality: number;
bypass_if_above_custom_format_score: number;
minimum_custom_format_score: number | null;
created_at: string;
updated_at: string;
}): DelayProfilesRow {
return {
id: row.id,
name: row.name,
preferred_protocol: row.preferred_protocol as PreferredProtocol,
usenet_delay: row.usenet_delay,
torrent_delay: row.torrent_delay,
bypass_if_highest_quality: row.bypass_if_highest_quality === 1,
bypass_if_above_custom_format_score: row.bypass_if_above_custom_format_score === 1,
minimum_custom_format_score: row.minimum_custom_format_score,
created_at: row.created_at,
updated_at: row.updated_at
};
}
/**
* Get all delay profiles ordered by name
*/
export async function list(cache: PCDCache): Promise<DelayProfilesRow[]> {
const db = cache.kb;
const profiles = await db
.selectFrom('delay_profiles')
.select([
'id',
'name',
'preferred_protocol',
'usenet_delay',
'torrent_delay',
'bypass_if_highest_quality',
'bypass_if_above_custom_format_score',
'minimum_custom_format_score',
'created_at',
'updated_at'
])
.orderBy('name')
.execute();
return profiles.map(toDelayProfile);
}
/**
* Get a single delay profile by ID
*/
export async function get(cache: PCDCache, id: number): Promise<DelayProfilesRow | null> {
const db = cache.kb;
const profile = await db
.selectFrom('delay_profiles')
.select([
'id',
'name',
'preferred_protocol',
'usenet_delay',
'torrent_delay',
'bypass_if_highest_quality',
'bypass_if_above_custom_format_score',
'minimum_custom_format_score',
'created_at',
'updated_at'
])
.where('id', '=', id)
.executeTakeFirst();
if (!profile) return null;
return toDelayProfile(profile);
}

View File

@@ -1,20 +0,0 @@
/**
* Delay Profile query-specific types
*/
/** Preferred protocol options */
export type PreferredProtocol = 'prefer_usenet' | 'prefer_torrent' | 'only_usenet' | 'only_torrent';
/** Delay profile data for table/card views */
export interface DelayProfileTableRow {
id: number;
name: string;
preferred_protocol: PreferredProtocol;
usenet_delay: number | null;
torrent_delay: number | null;
bypass_if_highest_quality: boolean;
bypass_if_above_custom_format_score: boolean;
minimum_custom_format_score: number | null;
created_at: string;
updated_at: string;
}

View File

@@ -4,10 +4,10 @@
import type { PCDCache } from '../../cache.ts';
import { writeOperation, type OperationLayer } from '../../writer.ts';
import type { PreferredProtocol, DelayProfileTableRow } from './types.ts';
import type { DelayProfilesRow, PreferredProtocol } from '$shared/pcd/display.ts';
import { logger } from '$logger/logger.ts';
export interface UpdateDelayProfileInput {
interface UpdateDelayProfileInput {
name: string;
preferredProtocol: PreferredProtocol;
usenetDelay: number;
@@ -17,12 +17,12 @@ export interface UpdateDelayProfileInput {
minimumCfScore: number;
}
export interface UpdateDelayProfileOptions {
interface UpdateDelayProfileOptions {
databaseId: number;
cache: PCDCache;
layer: OperationLayer;
/** The current profile data (for value guards) */
current: DelayProfileTableRow;
current: DelayProfilesRow;
/** The new values */
input: UpdateDelayProfileInput;
}

View File

@@ -8,8 +8,8 @@
import { BaseSyncer, type SyncResult } from '../base.ts';
import { arrSyncQueries } from '$db/queries/arrSync.ts';
import { getCache } from '$pcd/cache.ts';
import { get as getDelayProfile } from '$pcd/queries/delayProfiles/get.ts';
import type { DelayProfileTableRow } from '$pcd/queries/delayProfiles/types.ts';
import { get as getDelayProfile } from '$pcd/queries/delayProfiles/index.ts';
import type { DelayProfilesRow } from '$shared/pcd/display.ts';
import type { ArrDelayProfile } from '$arr/types.ts';
import { logger } from '$logger/logger.ts';
@@ -66,7 +66,7 @@ export class DelayProfileSyncer extends BaseSyncer {
return { success: true, itemsSynced: 1 };
}
private transform(profile: DelayProfileTableRow): ArrDelayProfile {
private transform(profile: DelayProfilesRow): ArrDelayProfile {
let enableUsenet = true;
let enableTorrent = true;
let preferredProtocol = 'usenet';

View File

@@ -1,11 +1,12 @@
/**
* PCD Display Types
*
* Types for query results that include JOINed data.
* These extend the generated Row types with related entities.
* Types for query results that include JOINed data or need semantic naming.
* Simple aliases to generated Row types are provided for cleaner API.
*/
import type { RegularExpressionsRow } from './types.ts';
import type { DelayProfilesRow } from './types.ts';
// ============================================================================
// COMMON
@@ -25,3 +26,14 @@ export interface Tag {
export type RegularExpressionWithTags = RegularExpressionsRow & {
tags: Tag[];
};
// ============================================================================
// DELAY PROFILES
// ============================================================================
// No JOINs needed - the generated Row type is already semantic (booleans, unions).
// Re-exported here for consistent import pattern across all entities.
export type { DelayProfilesRow } from './types.ts';
/** Preferred protocol options - extracted for use in mutations */
export type PreferredProtocol = DelayProfilesRow['preferred_protocol'];

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import type { DelayProfileTableRow } from '$pcd/queries/delayProfiles/types.ts';
import type { DelayProfilesRow } from '$shared/pcd/display.ts';
import IconCheckbox from '$ui/form/IconCheckbox.svelte';
import SyncFooter from './SyncFooter.svelte';
import { Check } from 'lucide-svelte';
@@ -8,7 +8,7 @@
interface DatabaseWithProfiles {
id: number;
name: string;
delayProfiles: DelayProfileTableRow[];
delayProfiles: DelayProfilesRow[];
}
export let databases: DatabaseWithProfiles[];

View File

@@ -4,7 +4,7 @@ import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import * as delayProfileQueries from '$pcd/queries/delayProfiles/index.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import type { PreferredProtocol } from '$pcd/queries/delayProfiles/index.ts';
import type { PreferredProtocol } from '$shared/pcd/display.ts';
import { logger } from '$logger/logger.ts';
export const load: ServerLoad = async ({ params }) => {

View File

@@ -5,7 +5,7 @@
import SaveTargetModal from '$ui/modal/SaveTargetModal.svelte';
import { alertStore } from '$alerts/store';
import { Check, Save, Trash2, Loader2 } from 'lucide-svelte';
import type { PreferredProtocol } from '$pcd/queries/delayProfiles';
import type { PreferredProtocol } from '$shared/pcd/display.ts';
import { current, isDirty, initEdit, initCreate, update } from '$lib/client/stores/dirty';
// Form data shape

View File

@@ -4,7 +4,7 @@ import { pcdManager } from '$pcd/pcd.ts';
import { canWriteToBase } from '$pcd/writer.ts';
import * as delayProfileQueries from '$pcd/queries/delayProfiles/index.ts';
import type { OperationLayer } from '$pcd/writer.ts';
import type { PreferredProtocol } from '$pcd/queries/delayProfiles/index.ts';
import type { PreferredProtocol } from '$shared/pcd/display.ts';
import { logger } from '$logger/logger.ts';
export const load: ServerLoad = ({ params }) => {

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import type { DelayProfileTableRow } from '$pcd/queries/delayProfiles';
import type { DelayProfilesRow } from '$shared/pcd/display.ts';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { Clock, Zap, Shield } from 'lucide-svelte';
export let profiles: DelayProfileTableRow[];
export let profiles: DelayProfilesRow[];
function handleCardClick(profile: DelayProfileTableRow) {
function handleCardClick(profile: DelayProfilesRow) {
const databaseId = $page.params.databaseId;
goto(`/delay-profiles/${databaseId}/${profile.id}`);
}

View File

@@ -1,13 +1,13 @@
<script lang="ts">
import Table from '$ui/table/Table.svelte';
import type { Column } from '$ui/table/types';
import type { DelayProfileTableRow } from '$pcd/queries/delayProfiles';
import type { DelayProfilesRow } from '$shared/pcd/display.ts';
import { Tag, Clock, Zap, Shield, Calendar } from 'lucide-svelte';
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { parseUTC } from '$shared/dates';
export let profiles: DelayProfileTableRow[];
export let profiles: DelayProfilesRow[];
function formatDate(dateString: string): string {
const date = parseUTC(dateString);
@@ -21,7 +21,7 @@
});
}
function handleRowClick(row: DelayProfileTableRow) {
function handleRowClick(row: DelayProfilesRow) {
const databaseId = $page.params.databaseId;
goto(`/delay-profiles/${databaseId}/${row.id}`);
}
@@ -50,14 +50,14 @@
return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
}
const columns: Column<DelayProfileTableRow>[] = [
const columns: Column<DelayProfilesRow>[] = [
{
key: 'name',
header: 'Name',
headerIcon: Tag,
align: 'left',
sortable: true,
cell: (row: DelayProfileTableRow) => ({
cell: (row: DelayProfilesRow) => ({
html: `<div class="font-medium">${row.name}</div>`
})
},
@@ -67,7 +67,7 @@
headerIcon: Zap,
align: 'left',
width: 'w-44',
cell: (row: DelayProfileTableRow) => ({
cell: (row: DelayProfilesRow) => ({
html: `<span class="font-mono text-xs bg-neutral-100 dark:bg-neutral-800 px-2 py-0.5 rounded">${formatProtocol(row.preferred_protocol)}</span>`
})
},
@@ -77,7 +77,7 @@
headerIcon: Clock,
align: 'left',
width: 'w-48',
cell: (row: DelayProfileTableRow) => ({
cell: (row: DelayProfilesRow) => ({
html: `
<div class="text-xs space-y-0.5">
${row.usenet_delay !== null ? `<div>Usenet: <span class="font-mono text-[10px] bg-neutral-100 dark:bg-neutral-800 px-1 rounded">${formatDelay(row.usenet_delay)}</span></div>` : ''}
@@ -92,7 +92,7 @@
headerIcon: Shield,
align: 'left',
width: 'w-56',
cell: (row: DelayProfileTableRow) => {
cell: (row: DelayProfilesRow) => {
const bypasses: string[] = [];
if (row.bypass_if_highest_quality) {
bypasses.push('Highest Quality');
@@ -121,7 +121,7 @@
align: 'left',
width: 'w-44',
sortable: true,
cell: (row: DelayProfileTableRow) => ({
cell: (row: DelayProfilesRow) => ({
html: `<span class="text-xs text-neutral-500 dark:text-neutral-400">${formatDate(row.updated_at)}</span>`
})
},
@@ -132,7 +132,7 @@
align: 'left',
width: 'w-44',
sortable: true,
cell: (row: DelayProfileTableRow) => ({
cell: (row: DelayProfilesRow) => ({
html: `<span class="text-xs text-neutral-500 dark:text-neutral-400">${formatDate(row.created_at)}</span>`
})
}