## Implementation Plan: Show Linked Project on Post Details Page & Expose in API ### 1. Add `project_uid` to `PostOut` schema **File:** `devplacepy/schemas/content.py` (lines 75–85) - Add `project_uid: Optional[int] = None` field to `PostOut` class. - Ensure it is included in serialization (already part of `_Out` base if listed; add explicit field). ### 2. Create a project resolution helper **File:** `devplacepy/templating.py` (or a new utility module) - Implement `get_project_by_uid(project_uid: int) -> Optional[dict]` that queries the `projects` table for `uid`, `name`, `slug`. - Return a dict with keys: `uid`, `name`, `slug`, `url` (constructed using existing project detail route, e.g., `url_for('project.view', slug=slug)`). - Register as a Jinja global if needed for templates, but primarily for backend context building. ### 3. Update `PostDetailOut` and `FeedItemOut` schemas **Files:** `devplacepy/schemas/listings.py` - Add a nested `project: Optional[dict] = None` field to both `PostDetailOut` and `FeedItemOut`. - The nested dict should contain `uid`, `name`, `url`. ### 4. Wire project info into `load_detail()` and `detail_context()` **File:** `devplacepy/content.py` - In `load_detail` (line ~654): after loading the post, if `post.project_uid` is set, call `get_project_by_uid()` and add `project` to the returned dict. - In `detail_context` (line ~492): pass the `project` dict from `load_detail` into the context. ### 5. Wire project info into feed `enrich_items()` **File:** `devplacepy/content.py` (line ~697) - For each item in `enrich_items`, if the post has a non‑none `project_uid`, resolve the project info via the helper and attach it to the item’s `project` field (or to the `PostOut` equivalent in the feed item). - This ensures the feed API returns project data when requested. ### 6. Update post detail template **File:** `devplacepy/templates/posts/detail.html` (or similar) - After the post content, add a conditional block: ```html {% if project %} Project: {{ project.name }} {% endif %} ``` ### 7. Update feed card template (optional but consistent) **File:** `devplacepy/templates/feed/_card.html` or similar - Add the same conditional project link, placed near the topic or timestamp line. ### 8. Expose project in DevRant API **File:** `devplacepy/services/devrant/serializers.py` (line ~46) - Modify `serialize_rant` to include `project_uid` and a `project` dict (uid, name, url) when the rant’s `project_uid` is set. - **Also:** update the create rant endpoint (`routers/devrant/rants.py` line 98) to respect the incoming `project_uid` from the request (currently hardcoded `None`). The form data already carries it; pass it through. ### 9. Add test coverage **Files:** - `tests/api/posts/create.py` - `tests/api/posts/detail.py` (new or existing) - `tests/api/devrant/rants.py` - `tests/e2e/posts/detail.py` **Changes:** - In post creation tests, modify some fixtures to pass a valid `project_uid` (create a project first or use a seeded one). - Add a test that verifies the post detail API response contains `project` info when the post has a linked project. - Add a test that verifies the post details page renders the project link (E2E test with Playwright). - Add a test that verifies the DevRant rant serialization includes `project` data. - Ensure all new tests pass and existing tests are not broken. ### 10. (Optional) Add `project_uid` to edit route **File:** `devplacepy/routers/posts.py` line 231–235 - If time permits, add `project_uid` to the list of updatable fields in `edit_post`. This fills a gap identified in the investigation but is not strictly required by the ticket. --- ### Definition of Done - [ ] `PostOut` schema includes `project_uid` field. - [ ] `PostDetailOut` and `FeedItemOut` schemas include an optional `project` dict (`uid`, `name`, `url`). - [ ] A helper `get_project_by_uid()` exists and resolves project info correctly. - [ ] `load_detail()` returns project info and `detail_context()` passes it to the template. - [ ] Post details template shows a clickable project link when a project is linked. - [ ] `serialize_rant` includes `project_uid` and `project` dict. - [ ] DevRant create rant endpoint no longer hardcodes `project_uid: None` – it passes the incoming value. - [ ] At least one new test per layer (API, E2E, DevRant) exercises a post with a linked project and verifies the data is present. - [ ] Running `pip install -e '.[dev]' -q && python -m pytest {test_files}` passes (where `{test_files}` includes all relevant test files, e.g., `tests/api/ tests/e2e/ tests/units/`). - [ ] No regressions in existing test suite (all previous post, feed, and rant tests still pass).