From 74b38df686415ca2ad0b1b373fef8ddafa0ea7a7 Mon Sep 17 00:00:00 2001 From: Sam Chau Date: Wed, 14 Jan 2026 23:50:20 +1030 Subject: [PATCH] feat: add entity and release management components - Created EntityTable component for displaying test entities with expandable rows for releases. - Implemented ReleaseTable component to manage and display test releases with actions for editing and deleting. - Added ReleaseModal component for creating and editing releases - Introduced types for TestEntity, TestRelease, and related evaluations - Enhanced general settings page to include TMDB API configuration with connection testing functionality. - Added TMDBSettings component for managing TMDB API access token with reset and test connection features. --- docs/CONTRIBUTING.md | 151 +++++++ services/parser/Program.cs | 53 ++- src/lib/client/ui/actions/SearchAction.svelte | 37 +- src/lib/client/ui/form/FormInput.svelte | 4 +- src/lib/client/ui/form/TagInput.svelte | 13 +- src/lib/client/ui/modal/Modal.svelte | 17 +- .../ui/navigation/pageNav/pageNav.svelte | 4 +- .../client/ui/table/ExpandableTable.svelte | 42 +- src/lib/server/db/migrations.ts | 6 +- .../db/migrations/020_create_tmdb_settings.ts | 36 ++ .../021_create_parsed_release_cache.ts | 28 ++ .../server/db/queries/parsedReleaseCache.ts | 89 ++++ src/lib/server/db/queries/tmdbSettings.ts | 70 ++++ src/lib/server/pcd/cache.ts | 3 +- .../queries/customFormats/allConditions.ts | 221 ++++++++++ .../pcd/queries/customFormats/evaluator.ts | 237 +++++++---- .../server/pcd/queries/entityTests/create.ts | 93 +++++ .../pcd/queries/entityTests/createRelease.ts | 56 +++ .../server/pcd/queries/entityTests/delete.ts | 44 ++ .../pcd/queries/entityTests/deleteRelease.ts | 40 ++ .../server/pcd/queries/entityTests/index.ts | 21 + .../server/pcd/queries/entityTests/list.ts | 75 ++++ .../pcd/queries/entityTests/updateRelease.ts | 56 +++ .../queries/qualityProfiles/allCfScores.ts | 86 ++++ .../pcd/queries/qualityProfiles/index.ts | 4 + .../pcd/queries/qualityProfiles/select.ts | 25 ++ src/lib/server/pcd/schema.ts | 29 ++ src/lib/server/utils/arr/parser/client.ts | 224 +++++++++- src/lib/server/utils/arr/parser/index.ts | 14 +- src/lib/server/utils/tmdb/client.ts | 53 +++ src/lib/server/utils/tmdb/types.ts | 57 +++ .../api/entity-testing/evaluate/+server.ts | 108 +++++ src/routes/api/tmdb/search/+server.ts | 97 +++++ src/routes/api/tmdb/test/+server.ts | 27 ++ .../entity-testing/+page.server.ts | 18 + .../entity-testing/+page.svelte | 17 + .../[databaseId]/+page.server.ts | 388 ++++++++++++++++++ .../entity-testing/[databaseId]/+page.svelte | 342 +++++++++++++++ .../components/AddEntityModal.svelte | 355 ++++++++++++++++ .../components/EntityTable.svelte | 166 ++++++++ .../components/ReleaseModal.svelte | 158 +++++++ .../components/ReleaseTable.svelte | 287 +++++++++++++ .../[databaseId]/components/types.ts | 57 +++ src/routes/settings/general/+page.server.ts | 34 ++ src/routes/settings/general/+page.svelte | 4 + .../general/components/TMDBSettings.svelte | 152 +++++++ .../settings/general/components/types.ts | 4 + 47 files changed, 4000 insertions(+), 102 deletions(-) create mode 100644 docs/CONTRIBUTING.md create mode 100644 src/lib/server/db/migrations/020_create_tmdb_settings.ts create mode 100644 src/lib/server/db/migrations/021_create_parsed_release_cache.ts create mode 100644 src/lib/server/db/queries/parsedReleaseCache.ts create mode 100644 src/lib/server/db/queries/tmdbSettings.ts create mode 100644 src/lib/server/pcd/queries/customFormats/allConditions.ts create mode 100644 src/lib/server/pcd/queries/entityTests/create.ts create mode 100644 src/lib/server/pcd/queries/entityTests/createRelease.ts create mode 100644 src/lib/server/pcd/queries/entityTests/delete.ts create mode 100644 src/lib/server/pcd/queries/entityTests/deleteRelease.ts create mode 100644 src/lib/server/pcd/queries/entityTests/index.ts create mode 100644 src/lib/server/pcd/queries/entityTests/list.ts create mode 100644 src/lib/server/pcd/queries/entityTests/updateRelease.ts create mode 100644 src/lib/server/pcd/queries/qualityProfiles/allCfScores.ts create mode 100644 src/lib/server/pcd/queries/qualityProfiles/select.ts create mode 100644 src/lib/server/utils/tmdb/client.ts create mode 100644 src/lib/server/utils/tmdb/types.ts create mode 100644 src/routes/api/entity-testing/evaluate/+server.ts create mode 100644 src/routes/api/tmdb/search/+server.ts create mode 100644 src/routes/api/tmdb/test/+server.ts create mode 100644 src/routes/quality-profiles/entity-testing/+page.server.ts create mode 100644 src/routes/quality-profiles/entity-testing/+page.svelte create mode 100644 src/routes/quality-profiles/entity-testing/[databaseId]/+page.server.ts create mode 100644 src/routes/quality-profiles/entity-testing/[databaseId]/+page.svelte create mode 100644 src/routes/quality-profiles/entity-testing/[databaseId]/components/AddEntityModal.svelte create mode 100644 src/routes/quality-profiles/entity-testing/[databaseId]/components/EntityTable.svelte create mode 100644 src/routes/quality-profiles/entity-testing/[databaseId]/components/ReleaseModal.svelte create mode 100644 src/routes/quality-profiles/entity-testing/[databaseId]/components/ReleaseTable.svelte create mode 100644 src/routes/quality-profiles/entity-testing/[databaseId]/components/types.ts create mode 100644 src/routes/settings/general/components/TMDBSettings.svelte diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..0ec5db7 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,151 @@ +# Contributing to Profilarr + +Profilarr is a work-in-progress rewrite, so please coordinate larger changes first. This guide explains how the repo is organized and the expected contribution workflows. + +## Project Overview + +Profilarr is a SvelteKit + Deno app that manages and syncs configurations across \*arr apps using Profilarr Compliant Databases (PCDs). It compiles to standalone binaries. + +- **Frontend:** `src/routes/`, `src/lib/client/` +- **Backend:** `src/lib/server/` +- **PCDs:** git repositories cloned under `data/databases/` and compiled into an in-memory SQLite cache + +## Prerequisites + +- **Deno 2.x** +- **Node + npm** only if you want to run ESLint/Prettier (`deno task lint` or `deno task format`). +- **.NET 8** only if you work on the parser microservice in `services/parser/`. + +## Development Commands + +- `deno task dev` (default port 6969) +- `deno task test` +- `deno task lint` +- `deno task format` + +Useful environment variables: + +- `APP_BASE_PATH` (defaults to the compiled binary location) +- `PARSER_HOST`, `PARSER_PORT` (C# parser microservice) +- `PORT`, `HOST` + +## Repo Tour + +- `docs/ARCHITECTURE.md` — system overview +- `docs/PCD SPEC.md` — operational SQL & layering model +- `docs/manifest.md` — `pcd.json` schema +- `docs/PARSER_PORT_DESIGN.md` — parser microservice +- `services/parser/` — C# parser microservice + +## App Database vs PCD Databases + +**Profilarr app database** + +- SQLite file: `data/profilarr.db` +- Boot sequence initializes config, opens DB, runs migrations, starts job system. +- Migrations live in `src/lib/server/db/migrations/` and are run on startup. + +**PCD databases** + +- Git repos cloned into `data/databases/`. +- Compiled into an in-memory SQLite cache (`PCDCache`) using ordered SQL operations. +- Layers in order: `schema` → `base` → `tweaks` → `user`. +- SQL helper functions available inside PCD ops: `qp`, `cf`, `dp`, `tag`. + +## Adding a Migration + +1. Copy `src/lib/server/db/migrations/_template.ts` to a new file like `021_add_foo.ts`. +2. Update `version` and `name`, then fill out `up` SQL and (ideally) `down` SQL. +3. Add a static import in `src/lib/server/db/migrations.ts`. +4. Add the new migration to `loadMigrations()` (keep sequential ordering). + +Notes: + +- Versions must be unique and sequential. +- Never edit an applied migration; create a new one instead. +- Migrations run automatically on server startup. + +## Working with PCDs + +**PCD layout** + +``` +my-pcd/ +├── pcd.json +├── ops/ +└── tweaks/ +``` + +**Authoring operations** + +- Follow the append-only Operational SQL approach. +- Use expected-value guards in `UPDATE` statements to surface conflicts. +- New ops go in `ops/` or `tweaks/` depending on intent. + +**User ops** + +Profilarr writes user edits via `src/lib/server/pcd/writer.ts` into `user_ops/`, rebuilding the in-memory cache after write. + +## Client UI Components + +Shared UI lives in `src/lib/client/ui/`. Route-specific components live next to their routes. + +**Alerts and toasts** + +- Store: `src/lib/client/alerts/store.ts` +- Use the alert store for success/error/info toasts in `enhance` actions and API responses. + +**Actions and toolbars** + +- `src/lib/client/ui/actions/ActionsBar.svelte` +- `src/lib/client/ui/actions/ActionButton.svelte` +- `src/lib/client/ui/actions/SearchAction.svelte` +- `src/lib/client/ui/actions/ViewToggle.svelte` + +**Dropdowns** + +- `src/lib/client/ui/dropdown/Dropdown.svelte` +- `src/lib/client/ui/dropdown/DropdownItem.svelte` + +**Buttons** + +- `src/lib/client/ui/button/Button.svelte` (variants + sizes) + +**Forms** + +- `FormInput`, `NumberInput`, `TagInput`, `IconCheckbox` + +**Tables and lists** + +- `Table`, `ExpandableTable`, `ReorderableList` + +**Modals** + +- `Modal`, `SaveTargetModal`, `UnsavedChangesModal`, `InfoModal` + +**Navigation** + +- `navbar`, `pageNav`, `tabs` + +**State and empty views** + +- `EmptyState` + +## Svelte Conventions + +- Use Svelte 4 syntax (`export let`, `$:`) even though Svelte 5 is installed. +- Avoid Svelte 5 runes unless explicitly used in that module. +- Route-specific components should be colocated under their route directory. + +## Tests + +- Tests live in `src/tests/` and run with `deno task test`. +- Base test utilities are in `src/tests/base/BaseTest.ts`. +- Many tests create temp dirs under `/tmp/profilarr-tests`. + +## Parser Microservice (Optional) + +If you touch parser-related code, see `docs/PARSER_PORT_DESIGN.md` and `services/parser/`. + +- `dotnet run` from `services/parser/` +- Configure `PARSER_HOST` / `PARSER_PORT` in Profilarr diff --git a/services/parser/Program.cs b/services/parser/Program.cs index 77b165c..bb37a51 100644 --- a/services/parser/Program.cs +++ b/services/parser/Program.cs @@ -1,5 +1,9 @@ using Parser.Core; +// Bump this version when parser logic changes (regex patterns, parsing behavior, etc.) +// This invalidates the parse result cache in Profilarr +const string ParserVersion = "1.0.0"; + var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); @@ -93,7 +97,47 @@ app.MapPost("/parse", (ParseRequest request) => } }); -app.MapGet("/health", () => Results.Ok(new { status = "healthy" })); +app.MapGet("/health", () => Results.Ok(new { status = "healthy", version = ParserVersion })); + +app.MapPost("/match", (MatchRequest request) => +{ + if (string.IsNullOrWhiteSpace(request.Text)) + { + return Results.BadRequest(new { error = "Text is required" }); + } + + if (request.Patterns == null || request.Patterns.Count == 0) + { + return Results.BadRequest(new { error = "At least one pattern is required" }); + } + + var results = new Dictionary(); + + foreach (var pattern in request.Patterns) + { + try + { + var regex = new System.Text.RegularExpressions.Regex( + pattern, + System.Text.RegularExpressions.RegexOptions.IgnoreCase, + TimeSpan.FromMilliseconds(100) // Timeout to prevent ReDoS + ); + results[pattern] = regex.IsMatch(request.Text); + } + catch (System.Text.RegularExpressions.RegexMatchTimeoutException) + { + // Pattern took too long, treat as no match + results[pattern] = false; + } + catch (System.ArgumentException) + { + // Invalid regex pattern + results[pattern] = false; + } + } + + return Results.Ok(new MatchResponse { Results = results }); +}); app.Run(); @@ -140,3 +184,10 @@ public record EpisodeResponse public bool Special { get; init; } public string ReleaseType { get; init; } = "Unknown"; } + +public record MatchRequest(string Text, List Patterns); + +public record MatchResponse +{ + public Dictionary Results { get; init; } = new(); +} diff --git a/src/lib/client/ui/actions/SearchAction.svelte b/src/lib/client/ui/actions/SearchAction.svelte index 07d1aaa..62f8880 100644 --- a/src/lib/client/ui/actions/SearchAction.svelte +++ b/src/lib/client/ui/actions/SearchAction.svelte @@ -1,9 +1,14 @@
@@ -31,16 +49,24 @@
+ + {#if activeQuery} +
+ {activeQuery} +
+ {/if} + (isFocused = true)} on:blur={() => (isFocused = false)} - {placeholder} - class="h-full w-full bg-transparent pl-10 pr-10 text-sm text-neutral-900 placeholder-neutral-500 outline-none dark:text-neutral-100 dark:placeholder-neutral-400" + placeholder={activeQuery ? '' : placeholder} + class="h-full w-full bg-transparent pr-10 text-sm text-neutral-900 placeholder-neutral-500 outline-none dark:text-neutral-100 dark:placeholder-neutral-400 {activeQuery ? 'pl-2' : 'pl-10'}" /> @@ -51,6 +77,13 @@ > + {:else if activeQuery} + {/if} diff --git a/src/lib/client/ui/form/FormInput.svelte b/src/lib/client/ui/form/FormInput.svelte index 9d24c97..9cd256d 100644 --- a/src/lib/client/ui/form/FormInput.svelte +++ b/src/lib/client/ui/form/FormInput.svelte @@ -22,14 +22,14 @@ bind:value {placeholder} rows="6" - class="block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-neutral-900 placeholder-neutral-400 transition-colors focus:border-accent-500 focus:outline-none focus:ring-1 focus:ring-accent-500 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-50 dark:placeholder-neutral-500" + class="block w-full rounded-lg border border-neutral-300 bg-white px-3 py-2 text-neutral-900 placeholder-neutral-400 transition-colors focus:border-neutral-400 focus:outline-none dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-50 dark:placeholder-neutral-500 dark:focus:border-neutral-500" > {:else} {/if} diff --git a/src/lib/client/ui/form/TagInput.svelte b/src/lib/client/ui/form/TagInput.svelte index ca5c5dc..f2166c0 100644 --- a/src/lib/client/ui/form/TagInput.svelte +++ b/src/lib/client/ui/form/TagInput.svelte @@ -1,5 +1,6 @@
{#each tags as tag, index (tag)} -
- {tag} + + {tag} -
+ {/each}
-
+

{header}

-
+

{bodyMessage}

@@ -78,7 +87,7 @@