Skip to content

isAdamBailey/shudderfly

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1,212 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Laravel Forge Site Deployment Status Tests

🦋 Shudderfly

A Safe Digital Space for Families

Colin's Shudderfly is a secure, private content management system designed to create a safe digital environment for children. Zero ads, no social media links, no external tracking — just your curated content in a beautiful, modern interface.

Why Shudderfly?

🛡️ Child-Safe by Design - Complete control over all content with role-based permissions
🎨 Beautiful & Responsive - Modern UI that works seamlessly on phones, tablets, and desktops
📚 Digital Photo Albums - Organize memories into themed books with categories and geolocation
🎵 Distraction-Free Music - YouTube integration without recommendations or ads
🖼️ PDF Collage Generator - Create printable photo books from your digital collections
🎮 Accessible Games - Fun, self-contained games with no ads, no tracking, and full keyboard/screen-reader support
🔍 Lightning-Fast Search - Powered by Meilisearch with typo-tolerance and instant results
🚀 Production-Ready - Built on Laravel 12 with enterprise-grade security and scalability

Content Types

  • 📖 Books: Digital photo albums with categories, geolocation tags, and read tracking
  • 📸 Photos: Standalone image galleries with infinite scroll and bulk management
  • 🎵 Music: YouTube videos presented as audio tracks with custom thumbnails
  • 🎨 Collages: Generate beautiful PDF photo books for printing
  • 🎮 Games: Accessible, ad-free games playable within the authenticated app (Poop Boom, Cockroach Fart, Big Poop)
  • 💬 Messages: Internal communication system with reactions and threading

🏗️ Technical Stack

Backend

  • Framework: Laravel 12 (PHP 8.3+)
  • Database: MySQL 8.0 with Eloquent ORM
  • Authentication: Laravel Sanctum with role-based permissions via Spatie Laravel Permission
  • Search: Meilisearch via Laravel Scout for fast, typo-tolerant search
  • Media Processing:
    • Images: Intervention Image (automatic WebP conversion with 30% quality compression)
    • Videos: FFmpeg integration via pbmedia/laravel-ffmpeg (H.264 encoding with poster generation)
    • PDF Generation: DomPDF for collage exports
  • Storage: AWS S3 with CloudFront CDN support
  • Queue System: Amazon SQS for asynchronous media processing jobs
  • Real-time: Laravel Echo with Pusher for live notifications and reactions
  • Testing: PHPUnit with Laravel Nightwatch for debugging

Frontend

  • Framework: Vue 3 with Composition API and <script setup>
  • Routing: Inertia.js for SPA experience without REST API overhead
  • Styling: Tailwind CSS 3 with custom themes (Christmas, Halloween, Fireworks)
  • Rich Text: TipTap editor with link support for content management
  • File Uploads: FilePond with drag-and-drop, image preview, and MIME validation
  • Icons: RemixIcon (4,000+ icons)
  • Maps: Leaflet.js with geocoding for location features
  • Build Tool: Vite 6 with hot module replacement
  • Testing: Vitest with Vue Test Utils and jsdom

Infrastructure

  • Containerization: Docker via Laravel Sail for local development
  • CI/CD: GitHub Actions for automated testing
  • Deployment: Laravel Forge with zero-downtime deployments
  • Monitoring: Laravel Nightwatch for error tracking

Key Features

Content Management

  • Role-Based Access Control: Three permission levels (viewer, editor, admin)
  • Media Optimization Pipeline:
    • Images automatically converted to WebP format with compression
    • Videos processed with FFmpeg for web-optimized playback
    • Asynchronous job processing with retry logic and exponential backoff
    • Automatic thumbnail generation for videos
  • Categories & Taxonomy: Hierarchical organization with slug-based routing
  • Read Tracking: Analytics for book and song engagement
  • Bulk Operations: Mass edit, delete, or organize content

User Experience

  • Responsive Design: Mobile-first approach with Tailwind CSS
  • Dark Mode Support: System preference detection
  • Progress Indicators: Visual feedback during uploads and processing
  • Form Validation: Client and server-side validation with Vuelidate
  • Contact System: Email notifications to administrators
  • Weekly Stats: Automated engagement reports

Advanced Features

  • Video Snapshot Tool: Generate page snapshots from video content
  • PDF Collage Generator: Create printable photo books with custom layouts
  • YouTube Integration: Safe music playback via vue-lite-youtube-embed
  • Archive System: Soft delete and restore functionality for collages
  • Settings Management: Dynamic site configuration via database

🚀 Getting Started

Prerequisites

Before you begin, ensure you have the following installed:

  • PHP 8.3+ with extensions: mbstring, xml, curl, zip, gd, mysql

  • Composer (latest version)

  • Node.js 20+ and npm 10+

  • Docker Desktop (for Laravel Sail)

  • FFmpeg (for video processing)

    # macOS
    brew install ffmpeg
    
    # Ubuntu/Debian
    sudo apt-get install ffmpeg

Installation

1. Clone the Repository

git clone https://github.com/isAdamBailey/shudderfly.git
cd shudderfly

2. Install Dependencies

# Install PHP dependencies
composer install

# Install JavaScript dependencies
npm install

3. Environment Configuration

# Copy the example environment file
cp .env.example .env

# Generate application key
php artisan key:generate

4. Configure Environment Variables

Edit .env and configure the following sections:

Database (handled by Docker):

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=shudderfly
DB_USERNAME=sail
DB_PASSWORD=password

Queue System (use sync for local development):

QUEUE_CONNECTION=sync
# For production with SQS:
# QUEUE_CONNECTION=sqs
# AWS_ACCESS_KEY_ID=your-access-key
# AWS_SECRET_ACCESS_KEY=your-secret-key
# SQS_PREFIX=https://sqs.us-east-1.amazonaws.com/your-account-id
# SQS_QUEUE=your-queue-name

File Storage (use local for development):

FILESYSTEM_DISK=local
# For production with S3:
# FILESYSTEM_DISK=s3
# AWS_BUCKET=your-bucket-name
# CLOUDFRONT_URL=https://your-cloudfront-url

Meilisearch (handled by Docker):

SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://meilisearch:7700
MEILISEARCH_KEY=
FORWARD_MEILISEARCH_PORT=7700

Mail (optional for local development):

MAIL_MAILER=log
# For production with AWS SES:
# MAIL_MAILER=ses
# AWS_ACCESS_KEY_ID=your-access-key
# AWS_SECRET_ACCESS_KEY=your-secret-key

Pusher (optional for real-time features):

# Leave blank to disable real-time features locally
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_HOST=
PUSHER_APP_CLUSTER=mt1

Web Push Notifications (optional):

# Generate VAPID keys
npx web-push generate-vapid-keys

# Add the generated keys to .env
VAPID_PUBLIC_KEY=your-public-key
VAPID_PRIVATE_KEY=your-private-key

5. Start Docker Services with Laravel Sail

# Start all services (MySQL, Meilisearch, PHP)
./vendor/bin/sail up -d

# Create an alias for convenience (optional but recommended)
alias sail='./vendor/bin/sail'

6. Run Migrations and Seed Database

# Run migrations
sail artisan migrate

# Seed database with default roles, permissions, and sample data
sail artisan db:seed

# Or run both commands together
sail artisan migrate:fresh --seed

This creates:

  • 3 Permission Roles: Viewer, Editor, Admin
  • Default User: Check the seeder output for credentials
  • Sample Books, Pages, and Songs: Test data to explore features

7. Index Data in Meilisearch

sail artisan scout:import "App\Models\Book"
sail artisan scout:import "App\Models\Page"
sail artisan scout:import "App\Models\Song"

8. Build Frontend Assets

# Development mode with hot reload
npm run dev

# Or in a separate terminal if using Sail
sail npm run dev

# Production build
npm run build

9. Access the Application

Running the Queue Worker

For processing media uploads (images/videos) and generating PDFs:

# Development (synchronous - processes immediately)
# Already configured with QUEUE_CONNECTION=sync

# Production (asynchronous with queue worker)
sail artisan queue:work --tries=3 --timeout=1800

Note: Video processing can take 15-30 minutes depending on file size. The StoreVideo job has a 30-minute timeout.

Development Workflow

# Start all services
sail up -d

# Watch for frontend changes (hot reload)
npm run dev

# Run tests
sail artisan test
npm run test

# Run linters
npm run lint
npm run format

# Stop all services
sail down

📊 Application Architecture

Database Schema

Core Models:

  • books - Photo album containers with categories and geolocation
  • pages - Individual photos/videos belonging to books
  • songs - YouTube music tracks with thumbnails
  • categories - Hierarchical organization for books
  • collages - Generated PDF collections
  • messages - Internal messaging system
  • users - Authentication with role-based permissions

Queue Jobs

  • StoreImage: Optimizes images to WebP format (30% quality), uploads to S3, cleans up old files
  • StoreVideo: Processes videos with FFmpeg (H.264 encoding), generates posters, uploads to S3 (30-minute timeout)
  • CreateVideoSnapshot: Captures video frames at specific timestamps for page creation
  • GenerateCollagePdf: Creates printable PDF collages from selected images, emails download link
  • IncrementBookReadCount / IncrementPageReadCount / IncrementSongReadCount: Tracks engagement analytics

Permission Levels

  1. Viewer: Browse books, pages, music; basic read access
  2. Editor: Create, edit, and delete content; manage books and pages
  3. Admin: Full system access including user management, settings, and permissions

Routes Structure

  • Public: /login, /register (registration requires secret token)
  • Authenticated (auth middleware): All content routes
  • Editor (can:edit pages): Content management routes
  • Admin (can:admin): User management, settings, system configuration

Storage Strategy

  • Development: Local filesystem (storage/app/public)
  • Production: AWS S3 with CloudFront CDN
  • Media Processing: Temporary files in system temp directory, cleaned up after upload
  • Automatic Cleanup: Old media deleted when pages are updated

🔍 Meilisearch Setup

This application uses Meilisearch via Laravel Scout for fast, typo-tolerant search with autocomplete functionality in the search bar.

Local Development

Meilisearch is included in the Docker Compose setup. When using Laravel Sail:

  1. Start the services (Meilisearch will start automatically):

    sail up -d
  2. Configure environment variables in .env:

    SCOUT_DRIVER=meilisearch
    MEILISEARCH_HOST=http://meilisearch:7700
    MEILISEARCH_KEY=
    FORWARD_MEILISEARCH_PORT=7700

    Note: For local development, MEILISEARCH_KEY can be left empty (Meilisearch runs without authentication in development mode).

  3. Index existing data: Already covered in step 7 of the installation guide above.

Production (Laravel Forge)

  1. Install Meilisearch on your server:

    sudo docker run -d \
      --name meilisearch \
      --restart unless-stopped \
      -p 7700:7700 \
      -v /opt/meilisearch/data:/meili_data \
      -e MEILI_MASTER_KEY="your-master-key-here" \
      -e MEILI_ENV="production" \
      getmeili/meilisearch:v1.5

    Generate a secure master key:

    openssl rand -base64 32
  2. Configure environment variables in Forge:

    SCOUT_DRIVER=meilisearch
    MEILISEARCH_HOST=http://localhost:7700
    MEILISEARCH_KEY=your-generated-master-key-here
  3. Index data manually (first time only):

    php artisan scout:import "App\Models\Book"
    php artisan scout:import "App\Models\Page"
    php artisan scout:import "App\Models\Song"

Searchable Models

The following models are automatically indexed when created or updated:

  • Book: Indexes title and excerpt
  • Page: Indexes content and related book title
  • Song: Indexes title and description

Troubleshooting Meilisearch

  • Connection refused: Ensure Meilisearch is running (sudo docker ps | grep meilisearch)
  • Index not found: Run php artisan scout:import for the relevant model
  • Permission denied: Add your user to the docker group: sudo usermod -aG docker forge
  • Tests failing: Tests use SCOUT_DRIVER=null (configured in phpunit.xml) to avoid requiring Meilisearch in CI

🌐 Production Deployment

AWS Services Setup

S3 Bucket Configuration

  1. Create an S3 bucket for media storage
  2. Enable public access for uploaded media
  3. Configure CORS policy:
[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
    "AllowedOrigins": ["*"],
    "ExposeHeaders": []
  }
]

The bucket uses a flat top-level folder structure:

Folder Contents
books/{book-slug}/ Book page images and videos
collages/ Collage images and PDFs
sounds/ Sound effect audio files (MP3 / AAC)

Sounds Setup

Turn on sounds_enabled in Settings to show the Sounds page (audio from S3 / CloudFront). Edit pages can upload clips (FFmpeg converts uploads to M4A in a queue job — run a queue worker in production). Uploaded files are stored as sounds/{uuid}.m4a, not a fixed filename. Everyone can use the tile menu to hear a title read aloud; edit/delete stays editor-only. Games use the bundled public/fart.m4a for effects unless you point fartSoundUrl at a specific URL yourself.

CloudFront CDN (Optional but Recommended)

  1. Create a CloudFront distribution pointing to your S3 bucket
  2. Add CLOUDFRONT_URL to your .env
  3. Reduces latency and improves media loading speed
  4. The app automatically uses CloudFront in production and direct S3 in local dev

SQS Queue Setup

  1. Create an SQS queue for background jobs
  2. Set visibility timeout to at least 1900 seconds (for video processing)
  3. Configure dead-letter queue for failed jobs
  4. Add credentials to .env:
QUEUE_CONNECTION=sqs
SQS_PREFIX=https://sqs.us-east-1.amazonaws.com/your-account-id
SQS_QUEUE=shudderfly-production

SES Email Configuration

  1. Verify your domain in AWS SES
  2. Move out of sandbox mode for production sending
  3. Configure in .env:
MAIL_MAILER=ses
MAIL_FROM_ADDRESS=noreply@yourdomain.com

Laravel Forge Deployment

Server Requirements

  • Ubuntu 22.04 LTS
  • PHP 8.3 with required extensions
  • MySQL 8.0
  • FFmpeg installed
  • Sufficient disk space for temporary video processing

Deployment Script

Add to your Forge deployment script:

cd /home/forge/yourdomain.com

# Maintenance mode
php artisan down

# Pull latest code
git pull origin main

# Install dependencies
composer install --no-dev --optimize-autoloader

# Clear caches
php artisan cache:clear
php artisan config:clear
php artisan route:clear
php artisan view:clear

# Optimize
php artisan config:cache
php artisan route:cache
php artisan view:cache

# Build frontend assets
npm ci
npm run build

# Run migrations
php artisan migrate --force

# Exit maintenance mode
php artisan up

# Restart queue workers
php artisan queue:restart

Queue Workers Configuration

In Forge, set up daemon for queue processing:

php artisan queue:work sqs --tries=3 --timeout=1800 --sleep=3 --max-time=3600

Important: Set supervisor stopwaitsecs to at least 1900 seconds to allow video processing to complete.

Scheduled Tasks

Add to Forge scheduler (runs every minute):

php artisan schedule:run

This handles:

  • Weekly engagement statistics emails
  • Cleanup of old failed jobs
  • Cache warming

Environment Variables for Production

Critical variables to set in Forge:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://yourdomain.com

# Database
DB_HOST=localhost
DB_DATABASE=your_database
DB_USERNAME=your_user
DB_PASSWORD=secure_password

# AWS Services
AWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name
CLOUDFRONT_URL=https://d1234567890.cloudfront.net

# Queue
QUEUE_CONNECTION=sqs
SQS_PREFIX=https://sqs.us-east-1.amazonaws.com/your-account-id
SQS_QUEUE=shudderfly-production

# Mail
MAIL_MAILER=ses
MAIL_FROM_ADDRESS=noreply@yourdomain.com

# Search
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://localhost:7700
MEILISEARCH_KEY=your_production_master_key

# Pusher (for real-time features)
PUSHER_APP_ID=your_app_id
PUSHER_APP_KEY=your_app_key
PUSHER_APP_SECRET=your_app_secret
PUSHER_HOST=your_pusher_host
PUSHER_APP_CLUSTER=your_cluster

# Web Push
VAPID_PUBLIC_KEY=your_public_key
VAPID_PRIVATE_KEY=your_private_key

# Registration Protection
REGISTRATION_SECRET=your_secret_token

🧪 Testing

Backend Tests

# Run all PHPUnit tests
sail artisan test

# Run specific test file
sail artisan test tests/Feature/BookTest.php

# Run with coverage
sail artisan test --coverage

Frontend Tests

# Run all Vitest tests
npm run test

# Watch mode for development
npm run test:watch

# Run with UI
npm run test:ui

# Run once (for CI)
npm run test:run

Code Quality

# Run ESLint
npm run lint

# Format code with Prettier
npm run format

# PHP CS Fixer (if configured)
./vendor/bin/pint

🐛 Troubleshooting

Common Issues

Video Upload Fails

Problem: Videos fail to process or timeout Solutions:

  • Check FFmpeg is installed: which ffmpeg
  • Increase PHP memory limit in php.ini: memory_limit = 512M
  • Increase queue timeout: QUEUE_CONNECTION=sync for local testing
  • Check video codec: FFmpeg requires H.264 compatible videos
  • Review logs: tail -f storage/logs/laravel.log

Images Not Displaying

Problem: Images upload but don't show in browser Solutions:

  • Check S3 bucket permissions (must be publicly readable)
  • Verify CLOUDFRONT_URL in .env matches your distribution
  • Check browser console for CORS errors
  • Verify S3 CORS policy is configured correctly
  • Test direct S3 URL access

Search Not Working

Problem: Search returns no results Solutions:

  • Verify Meilisearch is running: docker ps | grep meilisearch
  • Re-index models: sail artisan scout:import "App\Models\Book"
  • Check Meilisearch logs: docker logs meilisearch
  • Test Meilisearch directly: curl http://localhost:7700/health

Queue Jobs Stuck

Problem: Jobs remain in queue and don't process Solutions:

  • Restart queue worker: sail artisan queue:restart
  • Check failed jobs: sail artisan queue:failed
  • Retry failed jobs: sail artisan queue:retry all
  • For video processing, ensure timeout is sufficient (1800 seconds)

Permission Denied Errors

Problem: Cannot create/edit content Solutions:

  • Check user roles: sail artisan tinkerUser::with('roles')->get()
  • Verify permissions seeded: sail artisan db:seed --class=RolesAndPermissionsSeeder
  • Assign role to user in UI: Settings → Users → Edit User

Log Files

# Laravel application logs
tail -f storage/logs/laravel.log

# Queue worker logs (production)
tail -f storage/logs/worker.log

# Docker container logs
docker logs -f laravel.test

# Meilisearch logs
docker logs -f meilisearch

📄 License

This project is open-sourced software licensed under the MIT license.


👨‍💻 Author

Adam Bailey


🙏 Acknowledgments

Built with:

  • Laravel - The PHP Framework for Web Artisans
  • Vue.js - The Progressive JavaScript Framework
  • Inertia.js - Build single-page apps without building an API
  • Tailwind CSS - A utility-first CSS framework
  • Meilisearch - Lightning-fast search engine

⭐ If you find this project useful, please consider giving it a star on GitHub!

About

A Laravel application to manage and display media content through Amazon AWS.

Topics

Resources

Stars

Watchers

Forks

Contributors