Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .github/workflows/create-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: Create Release

on:
push:
branches:
- main

jobs:
release:
name: Create GitHub release and tag
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Extract version
id: version
run: |
VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2)
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"

- name: Check if tag exists
id: tag-check
env:
TAG: ${{ steps.version.outputs.tag }}
run: |
if git rev-parse "$TAG" >/dev/null 2>&1; then
echo "Tag $TAG already exists, skipping release"
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "Tag $TAG does not exist, creating release"
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Extract changelog
if: steps.tag-check.outputs.exists == 'false'
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
# Extract changelog section for this version
awk "/^## \[$VERSION\]/,/^## \[/" CHANGELOG.md | sed '$d' | tail -n +2 > changelog.txt

echo "### Release Notes for $VERSION" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
cat changelog.txt >> "$GITHUB_STEP_SUMMARY"

- name: Create GitHub release
if: steps.tag-check.outputs.exists == 'false'
env:
GH_TOKEN: ${{ github.token }}
TAG: ${{ steps.version.outputs.tag }}
VERSION: ${{ steps.version.outputs.version }}
run: |
gh release create "$TAG" \
--title "Release $VERSION" \
--notes-file changelog.txt

echo "" >> "$GITHUB_STEP_SUMMARY"
echo "✅ Created release $TAG" >> "$GITHUB_STEP_SUMMARY"

- name: Skip summary
if: steps.tag-check.outputs.exists == 'true'
env:
TAG: ${{ steps.version.outputs.tag }}
run: |
echo "### Release Skipped" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Tag $TAG already exists" >> "$GITHUB_STEP_SUMMARY"
2 changes: 0 additions & 2 deletions .github/workflows/prod-image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ on:
push:
tags:
- v*
branches:
- main

jobs:
build:
Expand Down
67 changes: 67 additions & 0 deletions .github/workflows/validate-release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Validate Release

on:
pull_request:
branches:
- main

jobs:
validate:
name: Validate version and changelog
runs-on: ubuntu-latest
steps:
- name: Checkout PR branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0

- name: Fetch base branch
env:
BASE_REF: ${{ github.base_ref }}
run: git fetch origin "$BASE_REF"

- name: Run all validation checks
env:
BASE_REF: ${{ github.base_ref }}
run: |
FAILED=0

echo "### Release Validation" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"

# Check 1: Version bump
BASE_VERSION=$(git show "origin/$BASE_REF:pyproject.toml" | grep '^version = ' | cut -d'"' -f2)
PR_VERSION=$(grep '^version = ' pyproject.toml | cut -d'"' -f2)

if [ "$BASE_VERSION" = "$PR_VERSION" ]; then
echo "❌ Version not bumped in pyproject.toml (still $BASE_VERSION)" >> "$GITHUB_STEP_SUMMARY"
FAILED=1
else
echo "✅ Version bumped from $BASE_VERSION to $PR_VERSION" >> "$GITHUB_STEP_SUMMARY"
fi

# Check 2: CHANGELOG.md updated
if ! git diff --name-only "origin/$BASE_REF...HEAD" | grep -q '^CHANGELOG.md$'; then
echo "❌ CHANGELOG.md not updated" >> "$GITHUB_STEP_SUMMARY"
FAILED=1
else
echo "✅ CHANGELOG.md updated" >> "$GITHUB_STEP_SUMMARY"

# Check 3: Version in changelog (only if version was bumped and changelog updated)
if [ "$BASE_VERSION" != "$PR_VERSION" ]; then
if ! grep -q "^## \[$PR_VERSION\]" CHANGELOG.md; then
echo "❌ Version $PR_VERSION not found in CHANGELOG.md (expected: \`## [$PR_VERSION] - YYYY-MM-DD\`)" >> "$GITHUB_STEP_SUMMARY"
FAILED=1
else
echo "✅ Version $PR_VERSION found in CHANGELOG.md" >> "$GITHUB_STEP_SUMMARY"
fi
fi
fi

echo "" >> "$GITHUB_STEP_SUMMARY"
if [ $FAILED -eq 1 ]; then
echo "**Result:** ❌ Validation failed" >> "$GITHUB_STEP_SUMMARY"
exit 1
else
echo "**Result:** ✅ All checks passed" >> "$GITHUB_STEP_SUMMARY"
fi
104 changes: 104 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Changelog

All notable changes to Fragforce Reads will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.0.0] - 2026-07-01

Initial MVP release of Fragforce Reads - an audio playback service for the VTO Book Reading project.

### Added

**Core Recording Features**
- Browser-based audio recording workflow with mic access checks
- Pre-flight checklist for copyright compliance before recording
- Upload progress bar with user-friendly error handling
- Automatic audio remux pipeline for seeking support with ffmpeg
- Recording status tracking (pending, processing, ready, failed)
- Narrator dashboard showing available books and personal recordings
- Recording detail page with playback and flag-for-review functionality
- Profile page for narrators to update name and email
- Re-record capability for flagged recordings

**QR Code System**
- Auto-generated QR codes with short codes and passwords
- QR code labels with book info, narrator name, and password
- Printable QR sheet view for admins
- Short URL redirect (`/q/<code>`) to playback pages
- Case-insensitive password entry with rate limiting

**Playback & Security**
- Public playback page with password protection
- Range request support for audio seeking
- License expiry enforcement at playback time
- Session-based password unlocking (7-day sessions)
- Rate limiting on password attempts (5 attempts, 5-minute lockout)
- Narrator attribution visible before password entry
- Duration display in mm:ss format

**Registration & Authentication**
- Event code registration with rate limiting
- Invite link registration system
- Passphrase login with rate limiting
- Case-insensitive login
- Logout endpoint
- Welcome/login flow at `/login/`

**Admin Features**
- Book management with max narrator limits
- Narrator management with passphrase generation
- Recording admin with status filters and retry action
- QR code management with admin links
- Event code and invite link management

**Infrastructure & Deployment**
- Docker containerization (dev and prod variants)
- PostgreSQL 18.3 support
- GitHub Actions CI/CD (lint, test, coverage, SonarCloud)
- Docker image builds for dev and prod
- Whitenoise static file serving
- SSL security settings with SECURE_SSL flag
- Health check endpoint (`/healthz/`) for internal networks
- Persistent database connections (CONN_MAX_AGE=600)
- Non-root container execution
- Media file storage and serving

**Testing & Quality**
- Comprehensive test suite (146+ tests)
- Coverage reporting
- SonarCloud quality gate integration
- Per-app test package structure
- CODEOWNERS for CI/build config protection

### Security
- Rate limiting on login, event registration, and playback passwords
- Upload size validation (100MB limit)
- Recording duration bounds validation (max 3600s)
- Attestation text length validation (max 5000 chars)
- HTTP method restrictions on all views
- CSRF protection
- Session security with 7-day expiry
- File handle cleanup in range-request serving
- Healthz endpoint restricted to internal networks only

### Changed
- Simplified QR password format from `4alphanumeric-word-word` to `word-word-2digits`
- Narrator attribution now visible before password entry
- Audio files served through view with auth checks (no direct static serving)
- Recovered stuck recordings on app startup (gunicorn/runserver only)

### Fixed
- N+1 query optimization in dashboard view
- File handle leak in range-request audio serving
- Max narrators check when set to 0
- Visited link color on buttons
- Audio URL routing conflicts
- Admin retry action queryset re-evaluation
- Remux claim race condition with SELECT FOR UPDATE SKIP LOCKED
- Signal registration and file cleanup on recording delete
- HTML accessibility and CSS deprecation warnings
- Cognitive complexity in views (SonarCloud)

[1.0.0]: https://github.com/fragforce/read/releases/tag/v1.0.0
Loading