mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-31 06:40:50 +01:00
refactor(pcd): reorganize regularExpressions to CRUD pattern
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -4,6 +4,9 @@ node_modules
|
||||
CLAUDE.md
|
||||
.claudeignore
|
||||
|
||||
# Local TODO/planning docs
|
||||
docs/todo/
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
|
||||
8
deno.lock
generated
8
deno.lock
generated
@@ -4,10 +4,12 @@
|
||||
"jsr:@denosaurs/plug@^1.1.0": "1.1.0",
|
||||
"jsr:@felix/bcrypt@^1.0.8": "1.0.8",
|
||||
"jsr:@soapbox/kysely-deno-sqlite@^2.2.0": "2.2.0",
|
||||
"jsr:@std/assert@1": "1.0.15",
|
||||
"jsr:@std/encoding@1": "1.0.10",
|
||||
"jsr:@std/fmt@1": "1.0.8",
|
||||
"jsr:@std/fs@1": "1.0.19",
|
||||
"jsr:@std/internal@^1.0.10": "1.0.12",
|
||||
"jsr:@std/internal@^1.0.12": "1.0.12",
|
||||
"jsr:@std/internal@^1.0.9": "1.0.12",
|
||||
"jsr:@std/path@1": "1.1.2",
|
||||
"jsr:@std/path@^1.1.1": "1.1.2"
|
||||
@@ -31,6 +33,12 @@
|
||||
"@soapbox/kysely-deno-sqlite@2.2.0": {
|
||||
"integrity": "668ec94600bc4b4d7bd618dd7ca65d4ef30ee61c46ffcb379b6f45203c08517a"
|
||||
},
|
||||
"@std/assert@1.0.15": {
|
||||
"integrity": "d64018e951dbdfab9777335ecdb000c0b4e3df036984083be219ce5941e4703b",
|
||||
"dependencies": [
|
||||
"jsr:@std/internal@^1.0.12"
|
||||
]
|
||||
},
|
||||
"@std/encoding@1.0.10": {
|
||||
"integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1"
|
||||
},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
|
||||
export interface CreateRegularExpressionInput {
|
||||
interface CreateRegularExpressionInput {
|
||||
name: string;
|
||||
pattern: string;
|
||||
tags: string[];
|
||||
@@ -13,7 +13,7 @@ export interface CreateRegularExpressionInput {
|
||||
regex101Id: string | null;
|
||||
}
|
||||
|
||||
export interface CreateRegularExpressionOptions {
|
||||
interface CreateRegularExpressionOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
import type { RegularExpressionTableRow } from './types.ts';
|
||||
import type { RegularExpressionWithTags } from '$shared/pcd/display.ts';
|
||||
|
||||
export interface DeleteRegularExpressionOptions {
|
||||
interface DeleteRegularExpressionOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
/** The current regular expression data (for value guards) */
|
||||
current: RegularExpressionTableRow;
|
||||
current: RegularExpressionWithTags;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* Get a single regular expression by ID
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import type { RegularExpressionTableRow } from './types.ts';
|
||||
|
||||
/**
|
||||
* Get a regular expression by ID with its tags
|
||||
*/
|
||||
export async function get(cache: PCDCache, id: number): Promise<RegularExpressionTableRow | null> {
|
||||
const db = cache.kb;
|
||||
|
||||
// Get the regular expression
|
||||
const regex = await db
|
||||
.selectFrom('regular_expressions')
|
||||
.select(['id', 'name', 'pattern', 'regex101_id', 'description', 'created_at', 'updated_at'])
|
||||
.where('id', '=', id)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!regex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get tags for this regular expression
|
||||
const tags = await db
|
||||
.selectFrom('regular_expression_tags as ret')
|
||||
.innerJoin('tags as t', 't.name', 'ret.tag_name')
|
||||
.select(['t.name', 't.created_at'])
|
||||
.where('ret.regular_expression_name', '=', regex.name)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
...regex,
|
||||
tags
|
||||
};
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
/**
|
||||
* Regular Expression queries and mutations
|
||||
* Regular Expression CRUD operations
|
||||
*/
|
||||
|
||||
// Export all types
|
||||
export type { RegularExpressionTableRow } from './types.ts';
|
||||
export type { CreateRegularExpressionInput } from './create.ts';
|
||||
export type { UpdateRegularExpressionInput } 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';
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
/**
|
||||
* Regular expression list queries
|
||||
* Regular expression read operations
|
||||
*/
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import type { Tag } from '../../types.ts';
|
||||
import type { RegularExpressionTableRow } from './types.ts';
|
||||
import type { Tag, RegularExpressionWithTags } from '$shared/pcd/display.ts';
|
||||
|
||||
/**
|
||||
* Get regular expressions with full data for table/card views
|
||||
* List all regular expressions with tags
|
||||
*/
|
||||
export async function list(cache: PCDCache): Promise<RegularExpressionTableRow[]> {
|
||||
export async function list(cache: PCDCache): Promise<RegularExpressionWithTags[]> {
|
||||
const db = cache.kb;
|
||||
|
||||
// 1. Get all regular expressions
|
||||
// Get all regular expressions
|
||||
const expressions = await db
|
||||
.selectFrom('regular_expressions')
|
||||
.select(['id', 'name', 'pattern', 'regex101_id', 'description', 'created_at', 'updated_at'])
|
||||
@@ -23,7 +22,7 @@ export async function list(cache: PCDCache): Promise<RegularExpressionTableRow[]
|
||||
|
||||
const expressionNames = expressions.map((e) => e.name);
|
||||
|
||||
// 2. Get all tags for all expressions
|
||||
// Get all tags for all expressions
|
||||
const allTags = await db
|
||||
.selectFrom('regular_expression_tags as ret')
|
||||
.innerJoin('tags as t', 't.name', 'ret.tag_name')
|
||||
@@ -47,13 +46,38 @@ export async function list(cache: PCDCache): Promise<RegularExpressionTableRow[]
|
||||
|
||||
// Build the final result
|
||||
return expressions.map((expression) => ({
|
||||
id: expression.id,
|
||||
name: expression.name,
|
||||
pattern: expression.pattern,
|
||||
regex101_id: expression.regex101_id,
|
||||
description: expression.description,
|
||||
tags: tagsMap.get(expression.name) || [],
|
||||
created_at: expression.created_at,
|
||||
updated_at: expression.updated_at
|
||||
...expression,
|
||||
tags: tagsMap.get(expression.name) || []
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single regular expression by ID with tags
|
||||
*/
|
||||
export async function get(cache: PCDCache, id: number): Promise<RegularExpressionWithTags | null> {
|
||||
const db = cache.kb;
|
||||
|
||||
// Get the regular expression
|
||||
const regex = await db
|
||||
.selectFrom('regular_expressions')
|
||||
.select(['id', 'name', 'pattern', 'regex101_id', 'description', 'created_at', 'updated_at'])
|
||||
.where('id', '=', id)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!regex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get tags for this regular expression
|
||||
const tags = await db
|
||||
.selectFrom('regular_expression_tags as ret')
|
||||
.innerJoin('tags as t', 't.name', 'ret.tag_name')
|
||||
.select(['t.name', 't.created_at'])
|
||||
.where('ret.regular_expression_name', '=', regex.name)
|
||||
.execute();
|
||||
|
||||
return {
|
||||
...regex,
|
||||
tags
|
||||
};
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Regular Expression query-specific types
|
||||
*/
|
||||
|
||||
import type { Tag } from '../../types.ts';
|
||||
|
||||
/** Regular expression data for table/card views */
|
||||
export interface RegularExpressionTableRow {
|
||||
id: number;
|
||||
name: string;
|
||||
pattern: string;
|
||||
regex101_id: string | null;
|
||||
description: string | null;
|
||||
tags: Tag[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
import type { PCDCache } from '../../cache.ts';
|
||||
import { writeOperation, type OperationLayer } from '../../writer.ts';
|
||||
import type { RegularExpressionTableRow } from './types.ts';
|
||||
import type { RegularExpressionWithTags } from '$shared/pcd/display.ts';
|
||||
import { logger } from '$logger/logger.ts';
|
||||
|
||||
export interface UpdateRegularExpressionInput {
|
||||
interface UpdateRegularExpressionInput {
|
||||
name: string;
|
||||
pattern: string;
|
||||
tags: string[];
|
||||
@@ -15,12 +15,12 @@ export interface UpdateRegularExpressionInput {
|
||||
regex101Id: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateRegularExpressionOptions {
|
||||
interface UpdateRegularExpressionOptions {
|
||||
databaseId: number;
|
||||
cache: PCDCache;
|
||||
layer: OperationLayer;
|
||||
/** The current regular expression data (for value guards) */
|
||||
current: RegularExpressionTableRow;
|
||||
current: RegularExpressionWithTags;
|
||||
/** The new values */
|
||||
input: UpdateRegularExpressionInput;
|
||||
}
|
||||
|
||||
27
src/lib/shared/pcd/display.ts
Normal file
27
src/lib/shared/pcd/display.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* PCD Display Types
|
||||
*
|
||||
* Types for query results that include JOINed data.
|
||||
* These extend the generated Row types with related entities.
|
||||
*/
|
||||
|
||||
import type { RegularExpressionsRow } from './types.ts';
|
||||
|
||||
// ============================================================================
|
||||
// COMMON
|
||||
// ============================================================================
|
||||
|
||||
/** Tag with metadata */
|
||||
export interface Tag {
|
||||
name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// REGULAR EXPRESSIONS
|
||||
// ============================================================================
|
||||
|
||||
/** Regular expression with tags (from JOIN) */
|
||||
export type RegularExpressionWithTags = RegularExpressionsRow & {
|
||||
tags: Tag[];
|
||||
};
|
||||
@@ -14,7 +14,7 @@
|
||||
import { browser } from '$app/environment';
|
||||
import { Info, Plus, FileText, Users } from 'lucide-svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { RegularExpressionTableRow } from '$pcd/queries/regularExpressions';
|
||||
import type { RegularExpressionWithTags } from '$shared/pcd/display';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
@@ -74,10 +74,10 @@
|
||||
$: filtered = filterExpressions(data.regularExpressions, $debouncedQuery, searchOptions);
|
||||
|
||||
function filterExpressions(
|
||||
items: RegularExpressionTableRow[],
|
||||
items: RegularExpressionWithTags[],
|
||||
query: string,
|
||||
options: typeof searchOptions
|
||||
): RegularExpressionTableRow[] {
|
||||
): RegularExpressionWithTags[] {
|
||||
if (!query) return items;
|
||||
|
||||
const queryLower = query.toLowerCase();
|
||||
@@ -89,7 +89,7 @@
|
||||
// Search within tag names
|
||||
return item.tags.some((tag) => tag.name.toLowerCase().includes(queryLower));
|
||||
}
|
||||
const value = item[key as keyof RegularExpressionTableRow];
|
||||
const value = item[key as keyof RegularExpressionWithTags];
|
||||
if (value == null) return false;
|
||||
return String(value).toLowerCase().includes(queryLower);
|
||||
});
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { RegularExpressionTableRow } from '$pcd/queries/regularExpressions';
|
||||
import type { RegularExpressionWithTags } from '$shared/pcd/display';
|
||||
import { ExternalLink } from 'lucide-svelte';
|
||||
import { marked } from 'marked';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
export let expressions: RegularExpressionTableRow[];
|
||||
export let expressions: RegularExpressionWithTags[];
|
||||
|
||||
function handleCardClick(expression: RegularExpressionTableRow) {
|
||||
function handleCardClick(expression: RegularExpressionWithTags) {
|
||||
const databaseId = $page.params.databaseId;
|
||||
goto(`/regular-expressions/${databaseId}/${expression.id}`);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<script lang="ts">
|
||||
import Table from '$ui/table/Table.svelte';
|
||||
import type { Column } from '$ui/table/types';
|
||||
import type { RegularExpressionTableRow } from '$pcd/queries/regularExpressions';
|
||||
import type { RegularExpressionWithTags } from '$shared/pcd/display';
|
||||
import { Tag, Code, FileText, Link, Calendar } from 'lucide-svelte';
|
||||
import { marked } from 'marked';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { parseUTC } from '$shared/dates';
|
||||
|
||||
export let expressions: RegularExpressionTableRow[];
|
||||
export let expressions: RegularExpressionWithTags[];
|
||||
|
||||
function formatDate(dateString: string): string {
|
||||
const date = parseUTC(dateString);
|
||||
@@ -22,7 +22,7 @@
|
||||
});
|
||||
}
|
||||
|
||||
function handleRowClick(row: RegularExpressionTableRow) {
|
||||
function handleRowClick(row: RegularExpressionWithTags) {
|
||||
const databaseId = $page.params.databaseId;
|
||||
goto(`/regular-expressions/${databaseId}/${row.id}`);
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
return marked.parseInline(text) as string;
|
||||
}
|
||||
|
||||
const columns: Column<RegularExpressionTableRow>[] = [
|
||||
const columns: Column<RegularExpressionWithTags>[] = [
|
||||
{
|
||||
key: 'name',
|
||||
header: 'Name',
|
||||
@@ -49,7 +49,7 @@
|
||||
align: 'left',
|
||||
sortable: true,
|
||||
width: 'w-48',
|
||||
cell: (row: RegularExpressionTableRow) => ({
|
||||
cell: (row: RegularExpressionWithTags) => ({
|
||||
html: `
|
||||
<div>
|
||||
<div class="font-medium">${escapeHtml(row.name)}</div>
|
||||
@@ -79,7 +79,7 @@
|
||||
header: 'Pattern',
|
||||
headerIcon: Code,
|
||||
align: 'left',
|
||||
cell: (row: RegularExpressionTableRow) => ({
|
||||
cell: (row: RegularExpressionWithTags) => ({
|
||||
html: `<code class="font-mono text-xs bg-neutral-100 dark:bg-neutral-800 px-2 py-1 rounded break-all">${escapeHtml(row.pattern)}</code>`
|
||||
})
|
||||
},
|
||||
@@ -88,7 +88,7 @@
|
||||
header: 'Description',
|
||||
headerIcon: FileText,
|
||||
align: 'left',
|
||||
cell: (row: RegularExpressionTableRow) => ({
|
||||
cell: (row: RegularExpressionWithTags) => ({
|
||||
html: row.description
|
||||
? `<span class="text-sm text-neutral-600 dark:text-neutral-400 prose-inline">${parseMarkdown(row.description)}</span>`
|
||||
: `<span class="text-neutral-400">-</span>`
|
||||
@@ -100,7 +100,7 @@
|
||||
headerIcon: Link,
|
||||
align: 'left',
|
||||
width: 'w-32',
|
||||
cell: (row: RegularExpressionTableRow) => ({
|
||||
cell: (row: RegularExpressionWithTags) => ({
|
||||
html: row.regex101_id
|
||||
? `<a href="https://regex101.com/r/${escapeHtml(row.regex101_id)}" target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1 font-mono text-xs text-accent-600 hover:text-accent-700 dark:text-accent-400 dark:hover:text-accent-300 hover:underline">${escapeHtml(row.regex101_id)}<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg></a>`
|
||||
: `<span class="text-neutral-400">-</span>`
|
||||
@@ -113,7 +113,7 @@
|
||||
align: 'left',
|
||||
width: 'w-44',
|
||||
sortable: true,
|
||||
cell: (row: RegularExpressionTableRow) => ({
|
||||
cell: (row: RegularExpressionWithTags) => ({
|
||||
html: `<span class="text-xs text-neutral-500 dark:text-neutral-400">${formatDate(row.updated_at)}</span>`
|
||||
})
|
||||
},
|
||||
@@ -124,7 +124,7 @@
|
||||
align: 'left',
|
||||
width: 'w-44',
|
||||
sortable: true,
|
||||
cell: (row: RegularExpressionTableRow) => ({
|
||||
cell: (row: RegularExpressionWithTags) => ({
|
||||
html: `<span class="text-xs text-neutral-500 dark:text-neutral-400">${formatDate(row.created_at)}</span>`
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user