Phase 1-3 + 6: pluggable filter system, app factory, test harness
Implements the parallel.md workstreams (Agents A-E) toward REFACTOR_GOAL.md. Phase 1 — app factory + blueprints + /api/v1: - app.py -> create_app() factory (no module-level app); entrypoints updated - routes/ (auth, pages, settings, admin, assets) + blueprints/api.py at /api/v1 - config.py / extensions.py / security.py extracted; services/ layer added - endpoint names preserved so template url_for() calls keep resolving (static check: all 27 template url_for endpoints are defined routes) Phase 2 — one pluggable filter system: - filter_pipeline/registry.py: @register_stage / @register_plugin + discover_modules - engine._init_stages() instantiates registered stages (no hardcoded dict); process_batch is AI-aware: only short-circuits to the AI-disabled path when a filterset's stages declare requires_ai, so offline filtersets run with AI off - BaseFilterPlugin gets a consumer (stages/plugins.py); Keyword/Quality re-enabled via filter_config.json plugins config - comment tree modes ported to stages/comment_filter.py + shared rules.py; wired into /api/v1/posts/<uuid> and /api/v1/comments/<uuid> via FilterEngine.filter_comments() (fails open) - offline quality_filter filterset exercises plugins+ranker without AI - legacy filter_lib / comment_lib / html_generation_lib / generate_html / active_html path deleted Phase 3 prep — pluggable fetchers + Postgres models: - Post / Comment SQLAlchemy models added to models.py - migrate_content_to_db.py backfill (idempotent by uuid, batched, --dry-run) - platforms/ fetcher registry (extension point) - live reads/writes still go through PostService (disk JSON); cutover deferred Phase 6 — test harness: - pytest.ini + tests/ (conftest with in-memory SQLite fixture, no Postgres; stubbed polling/filter singletons) - test_app_factory.py (route registration, no module-level app), test_api_contracts.py (posts/post_detail/comments/filters shape with monkeypatched post_service + get_filter_engine), test_filter_pipeline.py + test_plugin_contract.py (Flask-free; validated locally 12/12 incl. drop-in stage/plugin discovered with zero core edits) Other: .gitignore added (__pycache__, data/, secrets); pytest in requirements. Verification: py_compile clean across the project; the Flask-free filter-pipeline and plugin-contract tests pass locally. App-factory / API-contract tests need deps+docker to run; runtime flask routes / Auth0-repeated-create_app also gated on docker. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -363,7 +363,7 @@
|
||||
const originalText = button.querySelector('.bookmark-text').textContent;
|
||||
button.querySelector('.bookmark-text').textContent = 'Saving...';
|
||||
|
||||
const response = await fetch('/api/bookmark', {
|
||||
const response = await fetch('/api/v1/bookmark', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -412,7 +412,7 @@
|
||||
const postId = button.getAttribute('data-post-id');
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/bookmark-status/${postId}`);
|
||||
const response = await fetch(`/api/v1/bookmark-status/${postId}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.bookmarked) {
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
# Template Creation Prompt for AI
|
||||
|
||||
This document describes the data structures, helper functions, and conventions an AI needs to create or modify HTML templates for this social media archive system.
|
||||
|
||||
## Data Structures Available
|
||||
|
||||
### Post Data (when rendering posts)
|
||||
- **Available in all post templates (card, list, detail):**
|
||||
- platform: string (e.g., "reddit", "hackernews")
|
||||
- id: string (unique post identifier)
|
||||
- title: string
|
||||
- author: string
|
||||
- timestamp: integer (unix timestamp)
|
||||
- score: integer (up/down vote score)
|
||||
- replies: integer (number of comments)
|
||||
- url: string (original post URL)
|
||||
- content: string (optional post body text)
|
||||
- source: string (optional subreddit/community)
|
||||
- tags: array of strings (optional tags/flair)
|
||||
- meta: object (optional platform-specific metadata)
|
||||
- comments: array (optional nested comment tree - only in detail templates)
|
||||
- post_url: string (generated: "{uuid}.html" - for local linking to detail pages)
|
||||
|
||||
### Comment Data (when rendering comments)
|
||||
- **Available in comment templates:**
|
||||
- uuid: string (unique comment identifier)
|
||||
- id: string (platform-specific identifier)
|
||||
- author: string (comment author username)
|
||||
- content: string (comment text)
|
||||
- timestamp: integer (unix timestamp)
|
||||
- score: integer (comment score)
|
||||
- platform: string
|
||||
- depth: integer (nesting level)
|
||||
- children: array (nested replies)
|
||||
- children_section: string (pre-rendered HTML of nested children)
|
||||
|
||||
## Template Engine: Jinja2
|
||||
|
||||
Templates use Jinja2 syntax (`{{ }}` for variables, `{% %}` for control flow).
|
||||
|
||||
### Important Filters:
|
||||
- `|safe`: Mark content as safe HTML (for already-escaped content)
|
||||
- Example: `{{ renderMarkdown(content)|safe }}`
|
||||
|
||||
### Available Control Structures:
|
||||
- `{% if variable %}...{% endif %}`
|
||||
- `{% for item in array %}...{% endfor %}`
|
||||
- `{% set variable = value %}` (create local variables)
|
||||
|
||||
## Helper Functions Available
|
||||
|
||||
Call these in templates using `{{ function(arg) }}`:
|
||||
|
||||
### Time/Date Formatting:
|
||||
- `formatTime(timestamp)` -> "HH:MM"
|
||||
- `formatTimeAgo(timestamp)` -> "2 hours ago"
|
||||
- `formatDateTime(timestamp)` -> "January 15, 2024 at 14:30"
|
||||
|
||||
### Text Processing:
|
||||
- `truncate(text, max_length)` -> truncated string with "..."
|
||||
- `escapeHtml(text)` -> HTML-escaped version
|
||||
|
||||
### Content Rendering:
|
||||
- `renderMarkdown(text)` -> Basic HTML from markdown (returns already-escaped HTML)
|
||||
|
||||
## Template Types
|
||||
|
||||
### Card Template (for index/listing pages)
|
||||
- Used for summary view of posts
|
||||
- Links should use `post_url` to point to local detail pages
|
||||
- Keep concise - truncated content, basic info
|
||||
|
||||
### List Template (compact listing)
|
||||
- Even more compact than cards
|
||||
- Vote scores, basic metadata, title link
|
||||
|
||||
### Detail Template (full post view)
|
||||
- Full content, meta information
|
||||
- Source link uses `url` (external)
|
||||
- Must include `{{comments_section|safe}}` for rendered comments
|
||||
|
||||
### Comment Template (nested comments)
|
||||
- Recursive rendering with depth styling
|
||||
- Children rendered as flattened HTML in `children_section`
|
||||
|
||||
## Convenience Data Added by System
|
||||
|
||||
In `generate_html.py`, `post_url` is added to each post before rendering: `{post['uuid']}.html`
|
||||
|
||||
This allows templates to link to local detail pages instead of external Reddit.
|
||||
|
||||
## CSS Classes Convention
|
||||
|
||||
Templates use semantic CSS classes:
|
||||
- Post cards: `.post-card`, `.post-header`, `.post-meta`, etc.
|
||||
- Comments: `.comment`, `.comment-header`, `.comment-body`, etc.
|
||||
- Platform: `.platform-{platform}` for platform-specific styling
|
||||
|
||||
## Examples
|
||||
|
||||
### Conditional Rendering:
|
||||
```
|
||||
{% if content %}
|
||||
<p class="content">{{ renderMarkdown(content)|safe }}</p>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
### Looping Tags:
|
||||
```
|
||||
{% for tag in tags if tag %}
|
||||
<span class="tag">{{ tag }}</span>
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
### Styling by Depth (comments):
|
||||
```
|
||||
<div class="comment" style="margin-left: {{depth * 20}}px">
|
||||
```
|
||||
|
||||
When creating new templates, follow these patterns and use the available data and helper functions appropriately.
|
||||
Reference in New Issue
Block a user