A modern HTML-native preprint server for academic research documents. Built with FastAPI, Scroll Press accepts research from any authoring tool that produces HTML—Typst, Quarto, MyST, Jupyter, or handwritten HTML. Format freedom, instant publication, permanent URLs.
Governance: Press is fully community-owned—open source, community contributions accepted, roadmap driven by community needs, forever free. Supported by community donations and academic grants.
- HTML-native publishing: Upload complete HTML documents with embedded CSS and JavaScript
- Session-based authentication: Secure user registration and login system with email verification
- Email verification: Token-based email verification with password reset functionality
- GDPR compliance: Data export endpoint for user data portability (Article 20)
- Subject categorization: Organize research by academic disciplines
- Draft and publish workflow: Save drafts and publish when ready
- Scroll cards: Browse recent submissions with rich metadata
- Responsive design: Clean, academic-focused UI with HTMX interactions
- Performance optimized: Static file caching with CDN-ready headers
- Python 3.11+
- PostgreSQL database
- uv package manager
justto run common commands
-
Clone the repository
git clone <repository-url> cd press
-
Set up environment
cp .env.example .env # Edit .env with your database URL, port, and email service credentialsRequired environment variables:
DATABASE_URL: PostgreSQL connection stringPORT: Server port (default: 7999)RESEND_API_KEY: API key for Resend email service (for email verification)FROM_EMAIL: Email address to send from (default: noreply@updates.aris.pub)BASE_URL: Base URL for email links (defaults to https://127.0.0.1:{PORT})
-
Install dependencies and setup
just init
-
Start the development server
just dev
Visit https://localhost:7999 to access Scroll Press (HTTPS with self-signed certificate).
app/
├── auth/ # Session-based authentication and token management
│ ├── session.py # Session handling
│ └── tokens.py # Email verification and password reset tokens
├── emails/ # Email service integration
│ ├── service.py # Resend email service
│ └── templates.py # Email HTML templates
├── models/ # SQLAlchemy database models
│ ├── user.py # User model with email verification
│ ├── token.py # Token model for verification/reset
│ ├── scroll.py # Research manuscript model
│ └── subject.py # Academic subject categorization
├── routes/ # FastAPI route handlers
├── templates/ # Jinja2 templates with component macros
│ └── auth/ # Authentication templates (login, register, verify, reset)
└── database.py # Async database configuration
static/
├── css/ # Stylesheet
└── images/ # Static assets
tests/ # Comprehensive test suite
- Backend: FastAPI with async/await patterns
- Database: PostgreSQL with SQLAlchemy 2.0 async
- Authentication: Session-based with in-memory storage and token-based email verification
- Email Service: Resend API for transactional emails (verification, password reset)
- Frontend: Jinja2 templates with HTMX for dynamic interactions
- Security: HTTPS-only development with self-signed certificates
- Testing: pytest with asyncio support, parallel execution, and Playwright e2e tests
Scroll Press uses different databases for each environment:
| Environment | Database | Purpose |
|---|---|---|
| Local Development | PostgreSQL (localhost) | Development work with persistent data |
| Local Testing | SQLite (in-memory) | Fast, isolated test execution |
| CI Testing | PostgreSQL (CI container) | Production-like testing environment |
| Production | Supabase PostgreSQL | Hosted production database |
Set your DATABASE_URL in .env:
# Local development (adjust username as needed)
DATABASE_URL=postgresql+asyncpg://leo.torres@localhost:5432/press
# Production (Supabase)
DATABASE_URL=postgresql+asyncpg://postgres.xyz:password@aws-0-region.pooler.supabase.com:6543/postgres# Apply migrations
just migrate
# or: uv run alembic upgrade head
# Create new migration
just migration "description"
# or: uv run alembic revision --autogenerate -m "description"
# Reset database with fresh seed data
just reset-db- Method: Automated GitHub Actions backups
- Schedule: Daily at 2 AM UTC
- Retention: 30 days (last 7 backups kept)
- Cost: Free using GitHub Actions
- Security: Private artifacts, repository collaborators only
- Add required secrets to GitHub repository (see
BACKUP_SETUP.md) - Backups run automatically via
.github/workflows/database-backup.yml - Manual backups can be triggered from GitHub Actions tab
Plan to upgrade to Supabase Pro Plan ($25/month) for official backups once user base grows:
- 14-day automated backups
- Point-in-time recovery
- Professional support
- Integrated dashboard management
- UUID primary keys
- Email verification status and password hashing
- Display names and timestamps
- Email verification and password reset tokens
- Hashed token storage for security
- Expiration timestamps (1 hour for password reset, 24 hours for email verification)
- One active token per user per type
- Academic manuscript storage with HTML content
- Draft/published status workflow
- Version tracking and unique scroll IDs
- Metadata (title, authors, abstract, keywords)
- Academic discipline categorization
- Hierarchical organization for research areas
- Registration: User registers and receives verification email via Resend
- Verification: User clicks email link with time-limited token
- Access Control: Unverified users can view dashboard but cannot upload or export data
- Password Reset: Secure token-based password reset with 1-hour expiration
Users can export all their data in JSON format via the /user/export-data endpoint:
# Requires authentication (session cookie)
curl -X GET https://scroll.press/user/export-data \
-H "Cookie: session_id=YOUR_SESSION_ID"Exported data includes:
- User profile (email, display name, verification status, timestamps)
- All scrolls (published and drafts) with complete metadata
- Active sessions with expiration times
Security:
- Requires authentication (401 if not logged in)
- Users can only export their own data
- Returns structured JSON for portability
Scroll Press includes comprehensive testing with both unit/integration tests and end-to-end browser tests.
# Run all tests
just test
# Run with coverage
just test-cov
# Run specific test file
uv run pytest tests/test_main.py -vE2E tests use Playwright to verify complete user journeys in real browsers.
# Install e2e dependencies (one time)
uv run playwright install chromium firefox
# Start development server
just dev
# Run e2e tests (in another terminal)
just test-e2e
# Or run directly
./scripts/run-e2e-tests.sh- Registration → Upload → Public Access: Verifies scrolls remain publicly accessible
- Registration → Upload → Account Deletion → Public Access: Verifies scroll persistence after user deletion
- License Selection: Tests CC BY 4.0 and All Rights Reserved license workflows
- Mobile Responsive: Validates mobile upload and interaction flows
- Search & Discovery: Tests content search and subject browsing
See E2E Testing Documentation for detailed information.
- Run all checks:
just check(includes lint, unit tests, and e2e tests) - Run tests only:
just test - Run e2e tests only:
just test-e2e - Follow existing patterns: Session-based auth, macro components, async/await
- Write tests: All new features should include test coverage
This project is licensed under the MIT License - see the LICENSE file for details.
For issues and questions, please use the GitHub issue tracker.