Files
StarPunk/docs/decisions/ADR-039-micropub-url-construction-fix.md
Phil Skentelbery 82bb1499d5 docs: Add v1.1.0 architecture and validation documentation
- ADR-033: Database migration redesign
- ADR-034: Full-text search with FTS5
- ADR-035: Custom slugs in Micropub
- ADR-036: IndieAuth token verification method
- ADR-039: Micropub URL construction fix
- Implementation plan and decisions
- Architecture specifications
- Validation reports for implementation and search UI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 10:39:58 -07:00

144 lines
5.3 KiB
Markdown

# ADR-039: Micropub URL Construction Fix
## Status
Accepted
## Context
After the v1.0.0 release, a bug was discovered in the Micropub implementation where the Location header returned after creating a post contains a double slash:
- **Expected**: `https://starpunk.thesatelliteoflove.com/notes/so-starpunk-v100-is-complete`
- **Actual**: `https://starpunk.thesatelliteoflove.com//notes/so-starpunk-v100-is-complete`
### Root Cause Analysis
The issue occurs due to a mismatch between how SITE_URL is stored and used:
1. **Configuration Storage** (`starpunk/config.py`):
- SITE_URL is normalized to always end with a trailing slash (lines 26, 92)
- This is required for IndieAuth/OAuth specs where root URLs must have trailing slashes
- Example: `https://starpunk.thesatelliteoflove.com/`
2. **URL Construction** (`starpunk/micropub.py`):
- Constructs URLs using: `f"{site_url}/notes/{note.slug}"` (lines 311, 381)
- This adds a leading slash to the path segment
- Results in: `https://starpunk.thesatelliteoflove.com/` + `/notes/...` = double slash
3. **Inconsistent Handling**:
- RSS feed module (`starpunk/feed.py`) correctly strips trailing slash before use (line 77)
- Micropub module doesn't handle this, causing the bug
## Decision
Fix the URL construction in the Micropub module by removing the leading slash from the path segment. This maintains the trailing slash convention in SITE_URL while ensuring correct URL construction.
### Implementation Approach
Change the URL construction pattern from:
```python
permalink = f"{site_url}/notes/{note.slug}"
```
To:
```python
permalink = f"{site_url}notes/{note.slug}"
```
This works because SITE_URL is guaranteed to have a trailing slash.
### Affected Code Locations
1. `starpunk/micropub.py` line 311 - Location header in `handle_create`
2. `starpunk/micropub.py` line 381 - URL in Microformats2 response in `handle_query`
## Rationale
### Why Not Strip the Trailing Slash?
We could follow the RSS feed approach and strip the trailing slash:
```python
site_url = site_url.rstrip("/")
permalink = f"{site_url}/notes/{note.slug}"
```
However, this approach has downsides:
- Adds unnecessary processing to every request
- Creates inconsistency with how SITE_URL is used elsewhere
- The trailing slash is intentionally added for IndieAuth compliance
### Why This Solution?
- **Minimal change**: Only modifies the string literal, not the logic
- **Consistent**: SITE_URL remains normalized with trailing slash throughout
- **Efficient**: No runtime string manipulation needed
- **Clear intent**: The code explicitly shows we expect SITE_URL to end with `/`
## Consequences
### Positive
- Fixes the immediate bug with minimal code changes
- No configuration changes required
- No database migrations needed
- Backward compatible - doesn't break existing data
- Fast to implement and test
### Negative
- Developers must remember that SITE_URL has a trailing slash
- Could be confusing without documentation
- Potential for similar bugs if pattern isn't followed elsewhere
### Mitigation
- Add a comment at each URL construction site explaining the trailing slash convention
- Consider adding a utility function in future versions for URL construction
- Document the SITE_URL trailing slash convention clearly
## Alternatives Considered
### 1. Strip Trailing Slash at Usage Site
```python
site_url = current_app.config.get("SITE_URL", "http://localhost:5000").rstrip("/")
permalink = f"{site_url}/notes/{note.slug}"
```
- **Pros**: More explicit, follows RSS feed pattern
- **Cons**: Extra processing, inconsistent with config intention
### 2. Remove Trailing Slash from Configuration
Modify `config.py` to not add trailing slashes to SITE_URL.
- **Pros**: Simpler URL construction
- **Cons**: Breaks IndieAuth spec compliance, requires migration for existing deployments
### 3. Create URL Builder Utility
```python
def build_url(base, *segments):
"""Build URL from base and path segments"""
return "/".join([base.rstrip("/")] + list(segments))
```
- **Pros**: Centralized URL construction, prevents future bugs
- **Cons**: Over-engineering for a simple fix, adds unnecessary abstraction for v1.0.1
### 4. Use urllib.parse.urljoin
```python
from urllib.parse import urljoin
permalink = urljoin(site_url, f"notes/{note.slug}")
```
- **Pros**: Standard library solution, handles edge cases
- **Cons**: Adds import, slightly less readable, overkill for this use case
## Implementation Notes
### Version Impact
- Current version: v1.0.0
- Fix version: v1.0.1 (PATCH increment - backward-compatible bug fix)
### Testing Requirements
1. Verify Location header has single slash
2. Test with various SITE_URL configurations (with/without trailing slash)
3. Ensure RSS feed still works correctly
4. Check all other URL constructions in the codebase
### Release Type
This qualifies as a **hotfix** because:
- It fixes a bug in production (v1.0.0)
- The fix is isolated and low-risk
- No new features or breaking changes
- Critical for proper Micropub client operation
## References
- [Issue Report]: Malformed redirect URL in Micropub implementation
- [W3C Micropub Spec](https://www.w3.org/TR/micropub/): Location header requirements
- [IndieAuth Spec](https://indieauth.spec.indieweb.org/): Client ID URL requirements
- ADR-028: Micropub Implementation Strategy
- docs/standards/versioning-strategy.md: Version increment guidelines