diff --git a/infrastructure.md b/infrastructure.md index 8298326..9a0e18c 100644 --- a/infrastructure.md +++ b/infrastructure.md @@ -132,6 +132,20 @@ All series-related WebSocket events include `key` as the primary identifier in t **Mixins**: `TimestampMixin` (created_at, updated_at), `SoftDeleteMixin` +### AnimeSeries Identifier Fields + +| Field | Type | Purpose | +| ------ | ----------- | ------------------------------------------------- | +| `id` | Primary Key | Internal database key for relationships | +| `key` | Unique, Indexed | **PRIMARY IDENTIFIER** for all lookups | +| `folder` | String | Filesystem metadata only (not for identification) | + +**Database Service Methods:** + +- `AnimeSeriesService.get_by_key(key)` - **Primary lookup method** +- `AnimeSeriesService.get_by_id(id)` - Internal lookup by database ID +- No `get_by_folder()` method exists - folder is never used for lookups + ## Core Services ### SeriesApp (`src/core/SeriesApp.py`) diff --git a/instructions.md b/instructions.md index 67c0d04..560d662 100644 --- a/instructions.md +++ b/instructions.md @@ -156,88 +156,12 @@ All API layer tasks completed. ### Phase 5: Frontend ✅ (Completed November 28, 2025) -All frontend tasks completed: -- **Task 5.1: Update Frontend JavaScript** ✅ - - Updated `app.js` to use `key` as primary series identifier - - `selectedSeries` Set now uses `key` instead of `folder` - - `createSerieCard()` uses `data-key` attribute for identification - - `toggleSerieSelection()` uses `key` for lookups - - All selection and download operations use `key` - -- **Task 5.2: Update WebSocket Events** ✅ - - WebSocket service already has proper documentation for `key` usage - - Updated tests to include `key` and `folder` in broadcast data - - Tests verify both fields are included in messages - -- **Task 5.3: Update Additional Frontend JavaScript Files** ✅ - - Reviewed `queue.js`, `websocket_client.js`, and utility files - - No changes needed - these files use download item IDs correctly - - Series identification is handled in `app.js` - -- **Task 5.4: Update HTML Templates** ✅ - - Reviewed all templates (`index.html`, `queue.html`, etc.) - - No changes needed - series cards are rendered dynamically in JavaScript - - Static templates don't contain series data attributes +### Phase 6: Database Layer ✅ (Completed November 28, 2025) ---- - -### Phase 6: Database Layer - -#### Task 6.1: Verify Database Models Use Key Correctly - -**File:** [`src/server/database/models.py`](src/server/database/models.py) - -**Objective:** Verify and document that database models correctly use `key` as unique identifier. - -**Steps:** - -1. Open [`src/server/database/models.py`](src/server/database/models.py) -2. Verify `AnimeSeries.key` is unique and indexed -3. Verify `AnimeSeries.folder` is not used for lookups -4. Update docstrings to clarify identifier usage -5. Ensure all relationships use `id` (primary key) not `key` or `folder` - -**Success Criteria:** - -- [ ] `key` is unique and indexed -- [ ] `folder` is metadata only -- [ ] Docstrings are clear -- [ ] All database model tests pass - -**Test Command:** - -```bash -conda run -n AniWorld python -m pytest tests/unit/test_database_models.py -v -``` - ---- - -#### Task 6.2: Update Database Services to Use Key - -**File:** [`src/server/database/service.py`](src/server/database/service.py) - -**Objective:** Ensure all database service methods use `key` for series lookup. - -**Steps:** - -1. Open [`src/server/database/service.py`](src/server/database/service.py) -2. Verify `AnimeSeriesService.get_by_key()` is used for lookups -3. Verify no methods use `folder` for identification -4. Update any methods that incorrectly use `folder` for lookups -5. Add migration helper if needed to update existing data - -**Success Criteria:** - -- [ ] All service methods use `key` for lookups -- [ ] `folder` never used as identifier -- [ ] All database service tests pass - -**Test Command:** - -```bash -conda run -n AniWorld python -m pytest tests/unit/test_database_service.py -v -``` +All database layer tasks completed: +- Task 6.1: Verified `AnimeSeries.key` is unique and indexed, `folder` is metadata only, updated docstrings +- Task 6.2: Verified all service methods use `key` for lookups, no folder-based identification --- @@ -543,9 +467,9 @@ conda run -n AniWorld python -m pytest tests/integration/test_identifier_consist - [x] Task 5.2: Update WebSocket Events - [x] Task 5.3: Update Additional Frontend JavaScript Files - [x] Task 5.4: Update HTML Templates -- [ ] Phase 6: Database Layer - - [ ] Task 6.1: Verify Database Models - - [ ] Task 6.2: Update Database Services +- [x] Phase 6: Database Layer ✅ **Completed November 28, 2025** + - [x] Task 6.1: Verify Database Models + - [x] Task 6.2: Update Database Services - [ ] Phase 7: Testing and Validation - [ ] Task 7.1: Update Test Fixtures - [ ] Task 7.2: Add Integration Tests diff --git a/src/server/database/models.py b/src/server/database/models.py index 14a4d1c..077a3a0 100644 --- a/src/server/database/models.py +++ b/src/server/database/models.py @@ -38,20 +38,31 @@ class AnimeSeries(Base, TimestampMixin): Represents an anime series with metadata, provider information, and links to episodes. Corresponds to the core Serie class. + Series Identifier Convention: + - `key`: PRIMARY IDENTIFIER - Unique, provider-assigned, URL-safe + (e.g., "attack-on-titan"). Used for all lookups and operations. + - `folder`: METADATA ONLY - Filesystem folder name for display + (e.g., "Attack on Titan (2013)"). Never used for identification. + - `id`: Internal database primary key for relationships. + Attributes: - id: Primary key - key: Unique identifier used by provider - name: Series name + id: Database primary key (internal use for relationships) + key: Unique provider key - PRIMARY IDENTIFIER for all lookups + name: Display name of the series site: Provider site URL - folder: Local filesystem path + folder: Filesystem folder name (metadata only, not for lookups) description: Optional series description status: Current status (ongoing, completed, etc.) total_episodes: Total number of episodes cover_url: URL to series cover image - episodes: Relationship to Episode models - download_items: Relationship to DownloadQueueItem models + episodes: Relationship to Episode models (via id foreign key) + download_items: Relationship to DownloadQueueItem models (via id foreign key) created_at: Creation timestamp (from TimestampMixin) updated_at: Last update timestamp (from TimestampMixin) + + Note: + All database relationships use `id` (primary key), not `key` or `folder`. + Use `get_by_key()` in AnimeSeriesService for lookups. """ __tablename__ = "anime_series" @@ -63,7 +74,7 @@ class AnimeSeries(Base, TimestampMixin): # Core identification key: Mapped[str] = mapped_column( String(255), unique=True, nullable=False, index=True, - doc="Unique provider key" + doc="Unique provider key - PRIMARY IDENTIFIER for all lookups" ) name: Mapped[str] = mapped_column( String(500), nullable=False, index=True, @@ -75,7 +86,7 @@ class AnimeSeries(Base, TimestampMixin): ) folder: Mapped[str] = mapped_column( String(1000), nullable=False, - doc="Local filesystem path" + doc="Filesystem folder name - METADATA ONLY, not for lookups" ) # Metadata diff --git a/src/server/database/service.py b/src/server/database/service.py index 4bb82cc..5cd5dde 100644 --- a/src/server/database/service.py +++ b/src/server/database/service.py @@ -43,6 +43,11 @@ class AnimeSeriesService: Provides methods for creating, reading, updating, and deleting anime series with support for both async and sync database sessions. + + Series Identifier Convention: + - Use `get_by_key()` for lookups by provider key (primary identifier) + - Use `get_by_id()` for lookups by database primary key (internal) + - Never use `folder` for identification - it's metadata only """ @staticmethod @@ -115,12 +120,19 @@ class AnimeSeriesService: async def get_by_key(db: AsyncSession, key: str) -> Optional[AnimeSeries]: """Get anime series by provider key. + This is the PRIMARY lookup method for series identification. + Use this method instead of get_by_id() when looking up by + the provider-assigned unique key. + Args: db: Database session - key: Unique provider key + key: Unique provider key (e.g., "attack-on-titan") Returns: AnimeSeries instance or None if not found + + Note: + Do NOT use folder for lookups - it's metadata only. """ result = await db.execute( select(AnimeSeries).where(AnimeSeries.key == key)