Story 3.4 generated by scrum manager
parent
ee66d9e894
commit
de9c015afd
|
|
@ -294,10 +294,15 @@ Story 3.3 is complete as specified. Potential future improvements:
|
||||||
2. **Custom See Also Heading**: Make "See Also" heading configurable
|
2. **Custom See Also Heading**: Make "See Also" heading configurable
|
||||||
3. **Link Position Strategy**: Preference for intro/body/conclusion placement
|
3. **Link Position Strategy**: Preference for intro/body/conclusion placement
|
||||||
4. **Anchor Text Variety**: More sophisticated rotation strategies
|
4. **Anchor Text Variety**: More sophisticated rotation strategies
|
||||||
5. **About/Privacy/Contact Pages**: Create pages to match nav menu links
|
5. ~~**About/Privacy/Contact Pages**: Create pages to match nav menu links~~ ✅ **PROMOTED TO STORY 3.4**
|
||||||
|
|
||||||
None of these are required for Story 3.3 completion.
|
None of these are required for Story 3.3 completion.
|
||||||
|
|
||||||
|
### Story 3.4 Emerged from Story 3.3
|
||||||
|
During Story 3.3 implementation, we added navigation menus to all templates that link to `about.html`, `contact.html`, and `privacy.html`. However, these pages don't exist, creating broken links. This was identified as a high-priority issue and promoted to **Story 3.4: Boilerplate Site Pages**.
|
||||||
|
|
||||||
|
See: `docs/stories/story-3.4-boilerplate-site-pages.md`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Sign-Off
|
## Sign-Off
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,277 @@
|
||||||
|
# Story 3.4: Boilerplate Site Pages - CREATED
|
||||||
|
|
||||||
|
**Status**: Specification Complete, Ready for Implementation
|
||||||
|
**Date Created**: October 21, 2025
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Story 3.4 has been created to address the broken navigation menu links introduced in Story 3.3.
|
||||||
|
|
||||||
|
### The Problem
|
||||||
|
|
||||||
|
In Story 3.3, we added navigation menus to all HTML templates:
|
||||||
|
```html
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/index.html">Home</a></li>
|
||||||
|
<li><a href="about.html">About</a></li>
|
||||||
|
<li><a href="privacy.html">Privacy</a></li>
|
||||||
|
<li><a href="contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
```
|
||||||
|
|
||||||
|
However, we never created the `about.html`, `contact.html`, or `privacy.html` pages, resulting in broken links.
|
||||||
|
|
||||||
|
### The Solution
|
||||||
|
|
||||||
|
Story 3.4 will automatically generate these boilerplate pages for each site during batch generation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Will Be Delivered
|
||||||
|
|
||||||
|
### 1. Three Boilerplate Pages Per Site (Heading Only)
|
||||||
|
- **About Page** (`about.html`) - `<h1>About Us</h1>` + template/navigation
|
||||||
|
- **Contact Page** (`contact.html`) - `<h1>Contact</h1>` + template/navigation
|
||||||
|
- **Privacy Policy** (`privacy.html`) - `<h1>Privacy Policy</h1>` + template/navigation
|
||||||
|
|
||||||
|
All pages have just a heading wrapped in the template structure. No other content text. User can add content manually later if desired.
|
||||||
|
|
||||||
|
### 2. Database Storage
|
||||||
|
New `site_pages` table stores pages separately from articles:
|
||||||
|
```sql
|
||||||
|
CREATE TABLE site_pages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
site_deployment_id INTEGER NOT NULL,
|
||||||
|
page_type VARCHAR(20) NOT NULL, -- about, contact, privacy
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (site_deployment_id) REFERENCES site_deployments(id),
|
||||||
|
UNIQUE (site_deployment_id, page_type)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Template Integration
|
||||||
|
- Pages use the same template as articles on the same site
|
||||||
|
- Professional, visually consistent with article content
|
||||||
|
- Navigation menu included (which links to these same pages)
|
||||||
|
|
||||||
|
### 4. Smart Generation
|
||||||
|
- Generated ONLY when new sites are created (not for existing sites)
|
||||||
|
- One-time backfill script for all existing imported sites
|
||||||
|
- Integrated into site creation workflow (not batch generation)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Scope
|
||||||
|
|
||||||
|
### Effort Estimate
|
||||||
|
**15 story points** (reduced from 20, approximately 1.5-2 days of development)
|
||||||
|
|
||||||
|
Simplified due to empty pages - no complex content generation needed.
|
||||||
|
|
||||||
|
### Key Components
|
||||||
|
|
||||||
|
1. **Database Schema** (2 points)
|
||||||
|
- New `SitePage` model
|
||||||
|
- Migration script
|
||||||
|
- Repository layer
|
||||||
|
|
||||||
|
2. **Page Content Templates** (1 point - simplified)
|
||||||
|
- Heading-only page content
|
||||||
|
- Returns `<h1>About Us</h1>`, `<h1>Contact</h1>`, `<h1>Privacy Policy</h1>`
|
||||||
|
- No complex content generation
|
||||||
|
|
||||||
|
3. **Generation Logic** (2 points - simplified)
|
||||||
|
- Generate heading-only pages for each site
|
||||||
|
- Wrap heading in HTML template
|
||||||
|
- Store in database
|
||||||
|
|
||||||
|
4. **Site Creation Integration** (2 points)
|
||||||
|
- Hook into `site_provisioning.py`
|
||||||
|
- Generate pages when new sites are created
|
||||||
|
- Handle errors gracefully
|
||||||
|
|
||||||
|
5. **Backfill Script** (2 points)
|
||||||
|
- CLI script to generate pages for all existing sites
|
||||||
|
- Dry-run mode for safety
|
||||||
|
- Progress reporting and error handling
|
||||||
|
|
||||||
|
6. **Testing** (3 points - simplified)
|
||||||
|
- Unit tests for heading-only page generation
|
||||||
|
- Integration tests with site creation
|
||||||
|
- Backfill script testing
|
||||||
|
- Template application tests
|
||||||
|
|
||||||
|
**Total: 15 story points** (reduced from 20)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration Point
|
||||||
|
|
||||||
|
Story 3.4 hooks into site creation, not batch generation:
|
||||||
|
|
||||||
|
### One-Time Setup (Existing Sites)
|
||||||
|
```bash
|
||||||
|
# Backfill all existing imported sites (hundreds of sites)
|
||||||
|
uv run python scripts/backfill_site_pages.py \
|
||||||
|
--username admin \
|
||||||
|
--password yourpass \
|
||||||
|
--template basic
|
||||||
|
|
||||||
|
# Output: Generated pages for 423 sites
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ongoing (New Sites Only)
|
||||||
|
```
|
||||||
|
When creating new sites:
|
||||||
|
1. Create Storage Zone (bunny.net)
|
||||||
|
2. Create Pull Zone (bunny.net)
|
||||||
|
3. Save to database
|
||||||
|
4. ✨ Generate boilerplate pages (Story 3.4) ← NEW
|
||||||
|
5. Return site ready to use
|
||||||
|
|
||||||
|
Triggered by:
|
||||||
|
- provision-site CLI command
|
||||||
|
- auto_create_sites in job config
|
||||||
|
- create_sites_for_keywords in job config
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Generation (Unchanged)
|
||||||
|
```
|
||||||
|
1. Generate articles (Epic 2)
|
||||||
|
2. Assign sites (Story 3.1) ← May use existing sites with pages
|
||||||
|
3. Generate URLs (Story 3.1)
|
||||||
|
4. Find tiered links (Story 3.2)
|
||||||
|
5. Inject interlinks (Story 3.3)
|
||||||
|
6. Apply templates (Story 2.4)
|
||||||
|
7. Deploy (Epic 4) ← Pages already exist on site
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Created
|
||||||
|
|
||||||
|
### Documentation
|
||||||
|
- `docs/stories/story-3.4-boilerplate-site-pages.md` (full specification)
|
||||||
|
- `docs/prd/epic-3-pre-deployment.md` (updated to include Story 3.4)
|
||||||
|
- `STORY_3.4_CREATED.md` (this summary)
|
||||||
|
|
||||||
|
### Implementation Files (To Be Created)
|
||||||
|
- `src/generation/page_templates.py` - Generic page content
|
||||||
|
- `src/generation/site_page_generator.py` - Page generation logic
|
||||||
|
- `src/database/models.py` - SitePage model (update)
|
||||||
|
- `scripts/migrate_add_site_pages.py` - Database migration
|
||||||
|
- `scripts/backfill_site_pages.py` - One-time backfill script
|
||||||
|
- `tests/unit/test_site_page_generator.py` - Unit tests
|
||||||
|
- `tests/integration/test_site_pages_integration.py` - Integration tests
|
||||||
|
- `tests/unit/test_backfill_script.py` - Backfill script tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
### Requires (Already Complete)
|
||||||
|
- ✅ Story 3.1: Site assignment (need to know which sites are in use)
|
||||||
|
- ✅ Story 3.3: Navigation menus (these pages fulfill those links)
|
||||||
|
- ✅ Story 2.4: Template service (for applying HTML templates)
|
||||||
|
- ✅ Story 1.6: SiteDeployment table (for site relationships)
|
||||||
|
|
||||||
|
### Enables
|
||||||
|
- Story 4.1: Deployment (pages will be deployed along with articles)
|
||||||
|
- Complete, professional-looking sites with working navigation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|
### Site Structure After Story 3.4
|
||||||
|
```
|
||||||
|
https://example.com/
|
||||||
|
├── index.html (homepage - future/Epic 4)
|
||||||
|
├── about.html ← NEW (Story 3.4)
|
||||||
|
├── contact.html ← NEW (Story 3.4)
|
||||||
|
├── privacy.html ← NEW (Story 3.4)
|
||||||
|
├── how-to-fix-your-engine.html (article)
|
||||||
|
├── engine-maintenance-tips.html (article)
|
||||||
|
└── best-engine-oil-brands.html (article)
|
||||||
|
```
|
||||||
|
|
||||||
|
### About Page Preview (Heading Only)
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>About Us</title>
|
||||||
|
<!-- Same template/styling as articles -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/index.html">Home</a></li>
|
||||||
|
<li><a href="about.html">About</a></li>
|
||||||
|
<li><a href="privacy.html">Privacy</a></li>
|
||||||
|
<li><a href="contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>About Us</h1>
|
||||||
|
<!-- No other content - user can add manually later if desired -->
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why heading-only pages?**
|
||||||
|
- Fixes broken navigation links (no 404 errors)
|
||||||
|
- Better UX than completely blank (user sees page title)
|
||||||
|
- Minimal implementation effort
|
||||||
|
- User can customize specific sites later if needed
|
||||||
|
- Deployment ready as-is
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
### Option 1: Implement Now
|
||||||
|
- Start implementation of Story 3.4
|
||||||
|
- Fixes broken navigation links
|
||||||
|
- Makes sites look complete and professional
|
||||||
|
|
||||||
|
### Option 2: Defer to Later
|
||||||
|
- Add to backlog/technical debt
|
||||||
|
- Focus on Epic 4 deployment first
|
||||||
|
- Sites work but have broken nav links temporarily
|
||||||
|
|
||||||
|
### Option 3: Minimal Quick Fix
|
||||||
|
- Create simple placeholder pages without full story implementation
|
||||||
|
- Just enough to avoid 404 errors
|
||||||
|
- Come back later for full implementation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
**Implement Story 3.4 before Epic 4 deployment** because:
|
||||||
|
|
||||||
|
1. Sites look unprofessional with broken nav links
|
||||||
|
2. Fixes 404 errors on every deployed site
|
||||||
|
3. Only 15 story points (1.5-2 days) - simplified implementation
|
||||||
|
4. Empty pages are deployment-ready
|
||||||
|
5. User can add content to specific pages later if desired
|
||||||
|
|
||||||
|
The alternative is to deploy with broken links and fix later, but that creates technical debt and poor user experience.
|
||||||
|
|
||||||
|
**Simplified approach:** Pages have heading only (e.g., `<h1>About Us</h1>`), no body content. This makes implementation faster while still fixing the broken link issue and providing better UX than completely blank pages.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Created by**: AI Code Assistant
|
||||||
|
**Created on**: October 21, 2025
|
||||||
|
**Next**: Decide when to implement Story 3.4 (now vs. later vs. minimal fix)
|
||||||
|
|
||||||
|
|
@ -43,3 +43,16 @@ A script iterates through each new article in the batch.
|
||||||
* **If Tier 1:** It scans the HTML for anchor text from the T1 list and links the first instance to the `money_site_url`.
|
* **If Tier 1:** It scans the HTML for anchor text from the T1 list and links the first instance to the `money_site_url`.
|
||||||
* **If Tier 2 or higher:** It scans the HTML for anchor text from the appropriate tier's list. For each of the 2-4 anchor texts found, it inserts a link to one of the `lower_tier_urls`.
|
* **If Tier 2 or higher:** It scans the HTML for anchor text from the appropriate tier's list. For each of the 2-4 anchor texts found, it inserts a link to one of the `lower_tier_urls`.
|
||||||
* The script produces the **final, fully interlinked HTML content,** ready for deployment in Epic 4.
|
* The script produces the **final, fully interlinked HTML content,** ready for deployment in Epic 4.
|
||||||
|
|
||||||
|
### Story 3.4: Generate Boilerplate Site Pages
|
||||||
|
**As a developer**, I want to automatically generate boilerplate `about.html`, `contact.html`, and `privacy.html` pages for each site in my batch, so that the navigation menu links from Story 3.3 work and the sites appear complete.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
* A function generates three boilerplate pages for each unique site in the batch.
|
||||||
|
* Pages created: `about.html`, `contact.html`, `privacy.html`
|
||||||
|
* Each page uses the same template as the articles for that site (basic/modern/classic/minimal).
|
||||||
|
* Pages contain professional, generic content suitable for any niche.
|
||||||
|
* Privacy policy is comprehensive and legally sound (generic template).
|
||||||
|
* Pages are stored in database and associated with the correct site.
|
||||||
|
* Pages are generated once per site (skip if already exist from previous batches).
|
||||||
|
* Navigation menu links from Story 3.3 now point to actual pages instead of being broken.
|
||||||
|
|
@ -0,0 +1,519 @@
|
||||||
|
# Story 3.4: Generate Boilerplate Site Pages
|
||||||
|
|
||||||
|
## Status
|
||||||
|
Not Started
|
||||||
|
|
||||||
|
## Story
|
||||||
|
**As a developer**, I want to automatically generate boilerplate `about.html`, `contact.html`, and `privacy.html` pages for each site in my batch, so that the navigation menu links from Story 3.3 work and the sites appear complete.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Story 3.3 added navigation menus to all HTML templates with links to:
|
||||||
|
- `/index.html` (homepage)
|
||||||
|
- `about.html` (about page)
|
||||||
|
- `privacy.html` (privacy policy)
|
||||||
|
- `contact.html` (contact page)
|
||||||
|
- Currently, these pages don't exist, creating broken links
|
||||||
|
- Each site needs its own set of these pages
|
||||||
|
- Pages should use the same template as the articles (basic/modern/classic/minimal)
|
||||||
|
- Content should be generic but professional enough for a real site
|
||||||
|
- Privacy policy needs to be comprehensive and legally sound (generic template)
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
### Core Functionality
|
||||||
|
- A function generates the three boilerplate pages for a given site
|
||||||
|
- Pages are created AFTER articles are generated but BEFORE deployment
|
||||||
|
- Each page uses the same template as the articles for that site
|
||||||
|
- Pages are stored in the database for deployment
|
||||||
|
- Pages are associated with the correct site (via `site_deployment_id`)
|
||||||
|
|
||||||
|
### Page Content Requirements
|
||||||
|
|
||||||
|
#### About Page (`about.html`)
|
||||||
|
- Empty page with just the template applied
|
||||||
|
- No content text required (just template navigation/structure)
|
||||||
|
- User can add content later if needed
|
||||||
|
|
||||||
|
#### Contact Page (`contact.html`)
|
||||||
|
- Empty page with just the template applied
|
||||||
|
- No content text required (just template navigation/structure)
|
||||||
|
- User can add content later if needed
|
||||||
|
|
||||||
|
#### Privacy Policy (`privacy.html`)
|
||||||
|
- **Option 1 (Minimal):** Empty page like about/contact
|
||||||
|
- No content text required (just template navigation/structure)
|
||||||
|
- User can add content later if needed
|
||||||
|
|
||||||
|
|
||||||
|
**Decision:** Start with Option 1 (empty pages) for all three pages. Privacy policy content can be added later via backfill update or manual edit if needed.
|
||||||
|
|
||||||
|
### Template Integration
|
||||||
|
- Use same template engine as article content (`src/templating/service.py`)
|
||||||
|
- Apply the site's assigned template (basic/modern/classic/minimal)
|
||||||
|
- Pages should visually match the articles on the same site
|
||||||
|
- Include navigation menu (which will link to these same pages)
|
||||||
|
|
||||||
|
### Database Storage
|
||||||
|
- Create new `site_pages` table (clean separation from articles):
|
||||||
|
- `id`, `site_deployment_id`, `page_type`, `content`, `created_at`, `updated_at`
|
||||||
|
- Foreign key to `site_deployments` with CASCADE delete
|
||||||
|
- Unique constraint on (site_deployment_id, page_type)
|
||||||
|
- Indexes on site_deployment_id and page_type
|
||||||
|
- Each site can have one of each page type (about, contact, privacy)
|
||||||
|
- Pages are fundamentally different from articles, deserve own table
|
||||||
|
|
||||||
|
### URL Generation
|
||||||
|
- Pages use simple filenames: `about.html`, `contact.html`, `privacy.html`
|
||||||
|
- Full URLs: `https://{hostname}/about.html`
|
||||||
|
- No slug generation needed (fixed filenames)
|
||||||
|
- Pages tracked separately from article URLs
|
||||||
|
|
||||||
|
### Integration Point
|
||||||
|
- Hook into batch generation workflow in `src/generation/batch_processor.py`
|
||||||
|
- After site assignment (Story 3.1) and before deployment (Epic 4)
|
||||||
|
- Generate pages ONLY for newly created sites (not existing sites)
|
||||||
|
- One-time backfill script to add pages to all existing imported sites
|
||||||
|
|
||||||
|
### Two Use Cases
|
||||||
|
1. **One-time backfill**: Script to generate pages for all existing sites in database (hundreds of sites)
|
||||||
|
2. **Ongoing generation**: Automatically generate pages only when new sites are created (provision-site, auto_create_sites, etc.)
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
### 1. Create SitePage Database Table
|
||||||
|
**Effort:** 2 story points
|
||||||
|
|
||||||
|
- [ ] Create new `site_pages` table with schema:
|
||||||
|
- `id`, `site_deployment_id`, `page_type`, `content`, `created_at`, `updated_at`
|
||||||
|
- [ ] Add `SitePage` model to `src/database/models.py`
|
||||||
|
- [ ] Create migration script `scripts/migrate_add_site_pages.py`
|
||||||
|
- [ ] Add unique constraint on (site_deployment_id, page_type)
|
||||||
|
- [ ] Add indexes on site_deployment_id and page_type
|
||||||
|
- [ ] Add CASCADE delete (if site deleted, pages deleted)
|
||||||
|
- [ ] Test migration on development database
|
||||||
|
|
||||||
|
### 2. Create SitePage Repository
|
||||||
|
**Effort:** 2 story points
|
||||||
|
|
||||||
|
- [ ] Create `ISitePageRepository` interface in `src/database/interfaces.py`:
|
||||||
|
- `create(site_deployment_id, page_type, content) -> SitePage`
|
||||||
|
- `get_by_site(site_deployment_id) -> List[SitePage]`
|
||||||
|
- `get_by_site_and_type(site_deployment_id, page_type) -> Optional[SitePage]`
|
||||||
|
- `update_content(page_id, content) -> SitePage`
|
||||||
|
- `exists(site_deployment_id, page_type) -> bool`
|
||||||
|
- `delete(page_id) -> bool`
|
||||||
|
- [ ] Implement `SitePageRepository` in `src/database/repositories.py`
|
||||||
|
- [ ] Add to repository factory/dependency injection
|
||||||
|
|
||||||
|
### 3. Create Page Content Templates (SIMPLIFIED)
|
||||||
|
**Effort:** 1 story point (reduced from 3)
|
||||||
|
|
||||||
|
- [ ] Create `src/generation/page_templates.py` module
|
||||||
|
- [ ] Implement `get_page_content(page_type: str, domain: str) -> str`:
|
||||||
|
- Returns just a heading: `<h1>About Us</h1>`, `<h1>Contact</h1>`, `<h1>Privacy Policy</h1>`
|
||||||
|
- All three pages use same heading-only approach
|
||||||
|
- No other content text
|
||||||
|
- [ ] No need for extensive content generation
|
||||||
|
- [ ] Pages are just placeholders until user adds content manually
|
||||||
|
|
||||||
|
### 4. Implement Page Generation Logic (SIMPLIFIED)
|
||||||
|
**Effort:** 2 story points (reduced from 3)
|
||||||
|
|
||||||
|
- [ ] Create `src/generation/site_page_generator.py` module
|
||||||
|
- [ ] Implement `generate_site_pages(site_deployment: SiteDeployment, template_name: str, page_repo, template_service) -> List[SitePage]`:
|
||||||
|
- Get domain from site (custom_hostname or bcdn_hostname)
|
||||||
|
- For each page type (about, contact, privacy):
|
||||||
|
- Get heading-only content from `page_templates.py`
|
||||||
|
- Wrap heading in HTML template using `template_service`
|
||||||
|
- Store page in database
|
||||||
|
- Return list of created pages
|
||||||
|
- [ ] Pages have just heading (e.g., `<h1>About Us</h1>`) wrapped in template
|
||||||
|
- [ ] Log page generation at INFO level
|
||||||
|
|
||||||
|
### 5. Integrate with Site Creation (Not Batch Processor)
|
||||||
|
**Effort:** 2 story points
|
||||||
|
|
||||||
|
- [ ] Update `src/generation/site_provisioning.py`:
|
||||||
|
- After creating new site via bunny.net API, generate boilerplate pages
|
||||||
|
- Call `generate_site_pages()` immediately after site creation
|
||||||
|
- Log page generation results
|
||||||
|
- [ ] Update `provision-site` CLI command:
|
||||||
|
- Generate pages after site is provisioned
|
||||||
|
- [ ] Handle errors gracefully (log warning if page generation fails, continue with site creation)
|
||||||
|
- [ ] **DO NOT generate pages in batch processor** (only for new sites, not existing sites)
|
||||||
|
|
||||||
|
### 6. Update Template Service
|
||||||
|
**Effort:** 1 story point
|
||||||
|
|
||||||
|
- [ ] Verify `src/templating/service.py` can handle page content:
|
||||||
|
- Pages don't have titles/outlines like articles
|
||||||
|
- May need simpler template application for pages
|
||||||
|
- Ensure navigation menu is included
|
||||||
|
- [ ] Add helper method if needed: `apply_template_to_page(content, template_name, domain)`
|
||||||
|
|
||||||
|
### 7. Create Backfill Script for Existing Sites
|
||||||
|
**Effort:** 2 story points
|
||||||
|
|
||||||
|
- [ ] Create `scripts/backfill_site_pages.py`:
|
||||||
|
- Query all sites in database that don't have pages
|
||||||
|
- For each site: generate about, contact, privacy pages
|
||||||
|
- Use default template (or infer from site name if possible)
|
||||||
|
- Progress reporting (e.g., "Generating pages for site 50/400...")
|
||||||
|
- Dry-run mode to preview changes
|
||||||
|
- CLI arguments: `--dry-run`, `--template`, `--batch-size`
|
||||||
|
- [ ] Add error handling for individual site failures (continue with next site)
|
||||||
|
- [ ] Log results: successful, failed, skipped counts
|
||||||
|
|
||||||
|
### 8. Homepage Generation (Optional - Deferred)
|
||||||
|
**Effort:** 2 story points (if implemented)
|
||||||
|
|
||||||
|
- [ ] **DEFER to Epic 4 or later**
|
||||||
|
- [ ] Homepage (`index.html`) requires knowing all articles on the site
|
||||||
|
- [ ] Not needed for Story 3.4 (navigation menu links to `/index.html` can 404 for now)
|
||||||
|
- [ ] Document in technical notes
|
||||||
|
|
||||||
|
### 9. Unit Tests (SIMPLIFIED)
|
||||||
|
**Effort:** 2 story points (reduced from 3)
|
||||||
|
|
||||||
|
- [ ] Test heading-only page content generation
|
||||||
|
- [ ] Test domain extraction from SiteDeployment (custom vs bcdn hostname)
|
||||||
|
- [ ] Test page HTML wrapping with each template type
|
||||||
|
- [ ] Test SitePage repository CRUD operations
|
||||||
|
- [ ] Test duplicate page prevention (unique constraint)
|
||||||
|
- [ ] Test page generation for single site
|
||||||
|
- [ ] Test backfill script logic
|
||||||
|
- [ ] Mock template service and repositories
|
||||||
|
- [ ] Achieve >80% code coverage for new modules
|
||||||
|
|
||||||
|
### 10. Integration Tests (SIMPLIFIED)
|
||||||
|
**Effort:** 1 story point (reduced from 2)
|
||||||
|
|
||||||
|
- [ ] Test site creation triggers page generation
|
||||||
|
- [ ] Test with different template types (basic, modern, classic, minimal)
|
||||||
|
- [ ] Test with custom domain sites vs bunny.net-only sites
|
||||||
|
- [ ] Test pages stored correctly in database
|
||||||
|
- [ ] Test backfill script on real database
|
||||||
|
- [ ] Verify navigation menu links work (pages exist at expected paths)
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
### SitePage Model
|
||||||
|
```python
|
||||||
|
class SitePage(Base):
|
||||||
|
__tablename__ = "site_pages"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||||
|
site_deployment_id: Mapped[int] = mapped_column(
|
||||||
|
Integer,
|
||||||
|
ForeignKey('site_deployments.id', ondelete='CASCADE'),
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
page_type: Mapped[str] = mapped_column(String(20), nullable=False) # about, contact, privacy, homepage
|
||||||
|
content: Mapped[str] = mapped_column(Text, nullable=False) # Full HTML
|
||||||
|
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, nullable=False)
|
||||||
|
updated_at: Mapped[datetime] = mapped_column(
|
||||||
|
DateTime,
|
||||||
|
default=datetime.utcnow,
|
||||||
|
onupdate=datetime.utcnow,
|
||||||
|
nullable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Relationships
|
||||||
|
site_deployment: Mapped["SiteDeployment"] = relationship("SiteDeployment", back_populates="pages")
|
||||||
|
|
||||||
|
# Unique constraint
|
||||||
|
__table_args__ = (
|
||||||
|
UniqueConstraint('site_deployment_id', 'page_type', name='uq_site_page_type'),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Migration
|
||||||
|
```sql
|
||||||
|
CREATE TABLE site_pages (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
site_deployment_id INTEGER NOT NULL,
|
||||||
|
page_type VARCHAR(20) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (site_deployment_id) REFERENCES site_deployments(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE (site_deployment_id, page_type)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_site_pages_site ON site_pages(site_deployment_id);
|
||||||
|
CREATE INDEX idx_site_pages_type ON site_pages(page_type);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Page Content Template Examples (SIMPLIFIED)
|
||||||
|
|
||||||
|
#### Implementation - Heading Only
|
||||||
|
```python
|
||||||
|
# src/generation/page_templates.py
|
||||||
|
|
||||||
|
def get_page_content(page_type: str, domain: str) -> str:
|
||||||
|
"""
|
||||||
|
Generate minimal content for boilerplate pages.
|
||||||
|
Just a heading - no other content text.
|
||||||
|
"""
|
||||||
|
page_titles = {
|
||||||
|
"about": "About Us",
|
||||||
|
"contact": "Contact",
|
||||||
|
"privacy": "Privacy Policy"
|
||||||
|
}
|
||||||
|
return f"<h1>{page_titles.get(page_type, page_type.title())}</h1>"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Result - Heading Only Example
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>About Us</title>
|
||||||
|
<!-- Same template/styling as articles -->
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li><a href="/index.html">Home</a></li>
|
||||||
|
<li><a href="about.html">About</a></li>
|
||||||
|
<li><a href="privacy.html">Privacy</a></li>
|
||||||
|
<li><a href="contact.html">Contact</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<h1>About Us</h1>
|
||||||
|
<!-- No other content - user can add later if needed -->
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Why Heading-Only Pages Work
|
||||||
|
1. **Fixes broken nav links** - Pages exist, no 404 errors
|
||||||
|
2. **Better UX than completely empty** - User sees something when they click the link
|
||||||
|
3. **User can customize** - Add content manually later for specific sites
|
||||||
|
4. **Minimal effort** - No need to generate/maintain generic content
|
||||||
|
5. **Deployment ready** - Pages can be deployed as-is
|
||||||
|
6. **Future enhancement** - Can add content generation later if needed
|
||||||
|
|
||||||
|
### Integration with Site Creation
|
||||||
|
```python
|
||||||
|
# In src/generation/site_provisioning.py
|
||||||
|
|
||||||
|
def create_bunnynet_site(name_prefix: str, region: str = "DE", template: str = "basic"):
|
||||||
|
# Step 1: Create Storage Zone
|
||||||
|
storage = bunny_client.create_storage_zone(...)
|
||||||
|
|
||||||
|
# Step 2: Create Pull Zone
|
||||||
|
pull = bunny_client.create_pull_zone(...)
|
||||||
|
|
||||||
|
# Step 3: Save to database
|
||||||
|
site = site_repo.create(...)
|
||||||
|
|
||||||
|
# Step 4: Generate boilerplate pages (NEW - Story 3.4)
|
||||||
|
logger.info(f"Generating boilerplate pages for new site {site.id}...")
|
||||||
|
try:
|
||||||
|
generate_site_pages(site, template, page_repo, template_service)
|
||||||
|
logger.info(f"Successfully created about, contact, privacy pages for site {site.id}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to generate pages for site {site.id}: {e}")
|
||||||
|
# Don't fail site creation if page generation fails
|
||||||
|
|
||||||
|
return site
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backfill Script Usage
|
||||||
|
```bash
|
||||||
|
# One-time backfill for all existing sites (dry-run first)
|
||||||
|
uv run python scripts/backfill_site_pages.py \
|
||||||
|
--username admin \
|
||||||
|
--password yourpass \
|
||||||
|
--template basic \
|
||||||
|
--dry-run
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# Found 423 sites without boilerplate pages
|
||||||
|
# [DRY RUN] Would generate pages for site 1 (www.example.com)
|
||||||
|
# [DRY RUN] Would generate pages for site 2 (site123.b-cdn.net)
|
||||||
|
# ...
|
||||||
|
# [DRY RUN] Total: 423 sites would be updated
|
||||||
|
|
||||||
|
# Actually generate pages
|
||||||
|
uv run python scripts/backfill_site_pages.py \
|
||||||
|
--username admin \
|
||||||
|
--password yourpass \
|
||||||
|
--template basic
|
||||||
|
|
||||||
|
# Output:
|
||||||
|
# Generating pages for site 1/423 (www.example.com)... ✓
|
||||||
|
# Generating pages for site 2/423 (site123.b-cdn.net)... ✓
|
||||||
|
# ...
|
||||||
|
# Complete: 423 successful, 0 failed, 0 skipped
|
||||||
|
|
||||||
|
# Use different template per site (default: basic)
|
||||||
|
uv run python scripts/backfill_site_pages.py \
|
||||||
|
--username admin \
|
||||||
|
--password yourpass \
|
||||||
|
--template modern \
|
||||||
|
--batch-size 50 # Process 50 sites at a time
|
||||||
|
```
|
||||||
|
|
||||||
|
### Page URL Structure
|
||||||
|
```
|
||||||
|
Homepage: https://example.com/index.html
|
||||||
|
About: https://example.com/about.html
|
||||||
|
Contact: https://example.com/contact.html
|
||||||
|
Privacy: https://example.com/privacy.html
|
||||||
|
Article 1: https://example.com/how-to-fix-engines.html
|
||||||
|
Article 2: https://example.com/engine-maintenance-tips.html
|
||||||
|
```
|
||||||
|
|
||||||
|
### Template Application Example
|
||||||
|
```python
|
||||||
|
# For articles (existing)
|
||||||
|
template_service.apply_template(
|
||||||
|
content=article.content,
|
||||||
|
template_name="modern",
|
||||||
|
title=article.title,
|
||||||
|
meta_description=article.meta_description,
|
||||||
|
url=article_url
|
||||||
|
)
|
||||||
|
|
||||||
|
# For pages (new)
|
||||||
|
template_service.apply_template_to_page(
|
||||||
|
content=page_content, # Markdown or HTML from page_templates.py
|
||||||
|
template_name="modern",
|
||||||
|
page_title="About Us", # Static title
|
||||||
|
domain=site.custom_hostname or site.pull_zone_bcdn_hostname
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backfill Script Implementation
|
||||||
|
```python
|
||||||
|
# scripts/backfill_site_pages.py
|
||||||
|
|
||||||
|
def backfill_site_pages(
|
||||||
|
page_repo,
|
||||||
|
site_repo,
|
||||||
|
template_service,
|
||||||
|
template: str = "basic",
|
||||||
|
dry_run: bool = False,
|
||||||
|
batch_size: int = 100
|
||||||
|
):
|
||||||
|
"""Generate boilerplate pages for all sites that don't have them"""
|
||||||
|
|
||||||
|
# Get all sites
|
||||||
|
all_sites = site_repo.get_all()
|
||||||
|
logger.info(f"Found {len(all_sites)} total sites in database")
|
||||||
|
|
||||||
|
# Filter to sites without pages
|
||||||
|
sites_needing_pages = []
|
||||||
|
for site in all_sites:
|
||||||
|
existing_pages = page_repo.get_by_site(site.id)
|
||||||
|
if len(existing_pages) < 3: # Should have about, contact, privacy
|
||||||
|
sites_needing_pages.append(site)
|
||||||
|
|
||||||
|
logger.info(f"Found {len(sites_needing_pages)} sites without boilerplate pages")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
for site in sites_needing_pages:
|
||||||
|
domain = site.custom_hostname or site.pull_zone_bcdn_hostname
|
||||||
|
logger.info(f"[DRY RUN] Would generate pages for site {site.id} ({domain})")
|
||||||
|
logger.info(f"[DRY RUN] Total: {len(sites_needing_pages)} sites would be updated")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Generate pages for each site
|
||||||
|
successful = 0
|
||||||
|
failed = 0
|
||||||
|
|
||||||
|
for idx, site in enumerate(sites_needing_pages, 1):
|
||||||
|
domain = site.custom_hostname or site.pull_zone_bcdn_hostname
|
||||||
|
logger.info(f"Generating pages for site {idx}/{len(sites_needing_pages)} ({domain})...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
generate_site_pages(site, template, page_repo, template_service)
|
||||||
|
successful += 1
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to generate pages for site {site.id}: {e}")
|
||||||
|
failed += 1
|
||||||
|
|
||||||
|
# Progress checkpoint every batch_size sites
|
||||||
|
if idx % batch_size == 0:
|
||||||
|
logger.info(f"Progress: {idx}/{len(sites_needing_pages)} sites processed")
|
||||||
|
|
||||||
|
logger.info(f"Complete: {successful} successful, {failed} failed")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Domain Extraction
|
||||||
|
```python
|
||||||
|
def get_domain_from_site(site_deployment: SiteDeployment) -> str:
|
||||||
|
"""Extract domain for use in page content (email addresses, etc.)"""
|
||||||
|
if site_deployment.custom_hostname:
|
||||||
|
return site_deployment.custom_hostname
|
||||||
|
else:
|
||||||
|
return site_deployment.pull_zone_bcdn_hostname
|
||||||
|
```
|
||||||
|
|
||||||
|
### Privacy Policy Legal Note
|
||||||
|
The privacy policy template should be:
|
||||||
|
- Generic enough to apply to blog/content sites
|
||||||
|
- Comprehensive enough to cover common scenarios (cookies, analytics, third-party links)
|
||||||
|
- NOT legal advice - users should consult a lawyer for specific requirements
|
||||||
|
- Include standard disclaimers
|
||||||
|
- Regularly reviewed and updated (document version/date)
|
||||||
|
|
||||||
|
Recommended approach: Use a well-tested generic template from a reputable source (e.g., Privacy Policy Generator) and adapt it to fit our template structure.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
- Story 3.1: Site assignment must be complete (need to know which sites are in use)
|
||||||
|
- Story 3.3: Navigation menu is already in templates (pages fulfill those links)
|
||||||
|
- Story 2.4: Template service exists and can apply HTML templates
|
||||||
|
- Story 1.6: SiteDeployment table exists
|
||||||
|
|
||||||
|
## Future Considerations
|
||||||
|
- Story 4.1 will deploy these pages along with articles
|
||||||
|
- Future: Custom page content per project (override generic templates)
|
||||||
|
- Future: Homepage generation with dynamic article listing
|
||||||
|
- Future: Allow users to edit boilerplate page content via CLI or web interface
|
||||||
|
- Future: Additional pages (terms of service, disclaimer, etc.)
|
||||||
|
- Future: Page templates with more customization options (site name, tagline, etc.)
|
||||||
|
|
||||||
|
## Deferred to Later
|
||||||
|
- **Homepage (`index.html`) generation** - Could be part of this story or deferred to Epic 4
|
||||||
|
- If generated here: Simple page listing all articles on the site
|
||||||
|
- If deferred: Epic 4 deployment could create a basic redirect or placeholder
|
||||||
|
- **Custom page content per project** - Allow projects to override default templates
|
||||||
|
- **Multi-language support** - Generate pages in different languages based on project settings
|
||||||
|
|
||||||
|
## Total Effort
|
||||||
|
15 story points (reduced from 20 due to heading-only simplification)
|
||||||
|
|
||||||
|
### Effort Breakdown
|
||||||
|
1. Database Schema (2 points)
|
||||||
|
2. Repository Layer (2 points)
|
||||||
|
3. Page Content Templates (1 point)
|
||||||
|
4. Generation Logic (2 points)
|
||||||
|
5. Site Creation Integration (2 points)
|
||||||
|
6. Template Service Updates (1 point)
|
||||||
|
7. Backfill Script (2 points)
|
||||||
|
8. Homepage Generation (deferred)
|
||||||
|
9. Unit Tests (2 points)
|
||||||
|
10. Integration Tests (1 point)
|
||||||
|
|
||||||
|
**Total: 15 story points**
|
||||||
|
|
||||||
|
### Effort Reduction
|
||||||
|
Original estimate: 20 story points (with full page content)
|
||||||
|
Simplified (heading-only pages): 15 story points
|
||||||
|
Savings: 5 story points (no complex content generation needed)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Pages should be visually consistent with articles (same template)
|
||||||
|
- **Pages have heading only** - just `<h1>` tag, no body content
|
||||||
|
- Better UX than completely empty (user sees page title when they click nav link)
|
||||||
|
- User can manually add content later for specific sites if desired
|
||||||
|
- Pages are generated once per site at creation time
|
||||||
|
- Future enhancement: Add content generation for privacy policy if legally required
|
||||||
|
- Future enhancement: CLI command to update page content for specific sites
|
||||||
|
|
||||||
|
|
@ -455,6 +455,44 @@ This would still provide value with much less complexity (2-3 story points inste
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Story 3.3: Content Interlinking Injection
|
||||||
|
|
||||||
|
### Boilerplate Site Pages (About, Contact, Privacy)
|
||||||
|
|
||||||
|
**Priority**: High
|
||||||
|
**Epic Suggestion**: Epic 3 (Pre-deployment) - Story 3.4
|
||||||
|
**Estimated Effort**: Medium (20 story points, 2-3 days)
|
||||||
|
**Status**: ✅ **PROMOTED TO STORY 3.4** (specification complete)
|
||||||
|
|
||||||
|
#### Problem
|
||||||
|
During Story 3.3 implementation, we added navigation menus to all HTML templates with links to:
|
||||||
|
- `about.html`
|
||||||
|
- `contact.html`
|
||||||
|
- `privacy.html`
|
||||||
|
- `/index.html`
|
||||||
|
|
||||||
|
However, these pages don't exist, creating broken links on every deployed site.
|
||||||
|
|
||||||
|
#### Impact
|
||||||
|
- Unprofessional appearance (404 errors on nav links)
|
||||||
|
- Poor user experience
|
||||||
|
- Privacy policy may be legally required for public sites
|
||||||
|
- No contact mechanism for users
|
||||||
|
|
||||||
|
#### Solution (Now Story 3.4)
|
||||||
|
See full specification: `docs/stories/story-3.4-boilerplate-site-pages.md`
|
||||||
|
|
||||||
|
**Summary:**
|
||||||
|
- Automatically generate boilerplate pages for each site during batch generation
|
||||||
|
- Store in new `site_pages` table
|
||||||
|
- Use same template as articles for visual consistency
|
||||||
|
- Generic but professional content suitable for any niche
|
||||||
|
- Generated once per site, skip if already exists
|
||||||
|
|
||||||
|
**Implementation tracked in Story 3.4.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Future Sections
|
## Future Sections
|
||||||
|
|
||||||
Add new technical debt items below as they're identified during development.
|
Add new technical debt items below as they're identified during development.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue