Laravel 12 API-only backend for a content/learning platform, showcasing device-scoped authentication, secure offline delivery (DRM-lite), and a queue-driven media pipeline running on Docker, PostgreSQL, Redis, and MinIO.
This repository is intentionally structured to read like a production-grade backend sample for technical screeners (mid/senior Laravel).
At a high level, this service:
- Exposes a JSON HTTP API for students, professors, and admins (no coupled frontend).
- Uses Sanctum + per-device sessions to control access to high-value media.
- Offloads heavy work (FFmpeg transcoding, manifest rewriting) to background jobs on a dedicated
mediaqueue. - Delivers lessons as encrypted HLS streams and offline bundles, using JWT “licenses” and encrypted keys.
- Runs in Docker with PostgreSQL, Redis, MinIO, a queue worker, and Laravel Octane for high-concurrency traffic.
- Framework: Laravel 12 (PHP 8.2), API-only with Laravel Octane
- Auth: Laravel Sanctum, device-bound sessions, email verification, password reset
- Database: PostgreSQL
- Caching & Queues: Redis (verification codes, queues), long-running
mediaqueue for transcoding - Storage: MinIO/S3 via Flysystem for private lesson assets and HLS segments
- Media Pipeline: FFmpeg via queued jobs for encrypted HLS generation
- Security: JWT-based licenses (
firebase/php-jwt), encrypted media keys (libsodium / Laravel Crypt) - Runtime: Dockerized dev & prod, separate queue worker, Octane for high concurrency
High-level architecture:
- HTTP API layer: Auth, catalog, quizzes, admin authoring, media/DRM endpoints.
- Domain layer: Eloquent models and services for modules, lessons, offers, payments, device sessions, downloads, quizzes.
- Async layer: Laravel queues (Redis/database) for
TranscodeLessonand queued notifications. - Storage layer: PostgreSQL for relational data, MinIO/S3 for media, Redis for cache/queues.
- Ops tooling: Docker Compose, environment-specific
.envpresets, deploy docs, and GitHub Actions workflow for deployment.
- Per-device auth: Each login associates a Sanctum token with a
DeviceSessionrecord (device info, IP, UA, rooted/emulator flags). - Middleware enforcement:
EnsureDeviceSessionIsValidensures every authenticated request has a valid device session, revoking tokens and logging security events when sessions are invalid. - Role model: Roles (
student,professor,admin) are enforced in middleware and controllers, backing separate catalog, professor, and admin experiences. - Security logging: Auth and device events are funneled through
LoggingServiceand persisted toActivityLogfor auditability.
- Transcoding job:
TranscodeLessonis a long-running queued job that:- Downloads source video from MinIO/S3.
- Invokes FFmpeg to produce encrypted HLS segments and a playlist.
- Rewrites HLS manifests so all segments are served through API routes, not directly from storage.
- Uploads segments and manifests back to object storage and updates
LessonAssetmetadata.
- Key management:
LessonAssetstores AES keys encrypted with a master key (libsodium / Laravel Crypt), never in plaintext at rest. - License service:
LessonDownloadServiceissues and validates JWT “license” tokens bound to(user, device, lesson)with TTL and revocation, and tracksStudentDownloadrecords per device. - API-level DRM: Manifest and segment endpoints validate both device session and license before proxying media from MinIO, so raw objects remain private.
- Queues: Heavy work (FFmpeg) is moved to a dedicated
mediaqueue; notifications and other async tasks can use the default queue. - Workers: A separate queue worker service runs
php artisan queue:work --queue=media,defaultin Docker. - Timeouts & failure handling: Jobs declare explicit timeouts and bubble failures to Laravel’s failed-jobs table, while logging performance metrics via
LoggingService. - Octane: The API runs under Laravel Octane to improve throughput and latency for concurrent clients.
- Authentication: Registration, login, logout with Laravel Sanctum
- Email Verification: 6-digit codes (4-digit in development with bypass)
- Password Reset: Secure code-based password reset
- Redis Caching: Fast verification code storage
- Docker: Containerized development and production environments
- Catalog Management: Modules → lessons → resources with admin authoring tooling
- Student Catalog: Published module browsing, enrollments, recent progress, and offer discovery
- Quiz System: Admin quiz creation with questions/options, student attempts with automatic scoring
- Offers & Seeding: Semester/annual bundles generated per level with strong seed data for testing
- Offline DRM Pipeline: Admin uploads → FFmpeg transcode job → encrypted HLS stored in MinIO with manifest rewriting and secure segment proxy
- API Documentation: Complete API documentation for Flutter integration and admin workflows
# Start development environment with bind mounts
./dev.sh start
# View logs
./dev.sh logs
# Stop environment
./dev.sh stop- Bind Mounts: File changes are instantly reflected in the container
- No Rebuilds: Edit PHP files directly without rebuilding Docker images
- Hot Reload: Laravel Octane automatically reloads on file changes
- Development Bypass: Use verification code
1234for any user
./dev.sh start # Start development environment
./dev.sh stop # Stop development environment
./dev.sh restart # Restart development environment
./dev.sh logs # Show application logs
./dev.sh exec bash # Access container shell
./dev.sh install # Install dependencies
./dev.sh migrate # Run database migrations
./dev.sh clear # Clear all caches
./dev.sh test # Run tests
./dev.sh status # Show container statusThe API is grouped into clear areas: authentication/profile, student catalog, quizzes, and admin authoring/operations. Full details live in docs/*.md and Postman collections under docs/*.json; below is a quick index of major endpoints.
POST /api/auth/loginPOST /api/auth/basic-infoPOST /api/auth/email-passwordPOST /api/auth/learning-objectivesPOST /api/auth/resend-verification-codePOST /api/auth/verify-emailGET /api/user
GET /api/catalog/modulesGET /api/catalog/modules/{module}GET /api/catalog/modules/{module}/offersGET /api/catalog/my-modulesGET /api/catalog/modules/recentGET /api/catalog/lessons/{lesson}
POST /api/quizzes/{quiz}/attemptsPOST /api/quizzes/{quiz}/attempts/{attempt}/answersPOST /api/quizzes/{quiz}/attempts/{attempt}/completeGET /api/quizzes/{quiz}/attempts/history
GET /api/admin/modulesPOST /api/admin/modulesPATCH /api/admin/modules/{module}DELETE /api/admin/modules/{module}- Nested lesson/resource endpoints under
/api/admin/modules/{module}/lessons - Quiz management:
/api/admin/quizzes*endpoints
- App: Laravel API on port 8002 (includes Octane and queue listeners)
- Queue Worker: Dedicated worker consuming the
media,defaultqueues for transcoding - Database: PostgreSQL on port 5432
- Redis: Redis cache + queue on port 6379
- MinIO: S3-compatible storage on ports 9000/9001 (lesson assets stay private)
APP_ENV=local
APP_DEBUG=true
DB_CONNECTION=pgsql
DB_HOST=db
DB_DATABASE=eazzy_backend
DB_USERNAME=postgres
DB_PASSWORD=password
CACHE_DRIVER=redis
REDIS_HOST=redis
REDIS_PORT=6379
LESSON_MASTER_KEY=base64:...
LICENSE_JWT_SECRET=base64:...
LESSON_ASSET_DISK=lesson_assets
LESSON_LICENSE_TTL_DAYS=7
LESSON_OFFLINE_GRACE_DAYS=7
APP_URL=http://localhost:8002./:/var/www/html- Project files (instant changes)vendor:/var/www/html/vendor- Composer dependenciescomposer_cache:/var/www/html/.composer- Composer cache
- Run
docker exec eazzy_backend-app-1 php artisan migrate:fresh --seedfor a curated dataset. - Creates
first_yearsemester catalogue (10 modules, priced offers), plus additional demo modules across other levels. - Demo accounts:
admin@example.com,professor@example.com,student@example.com(passwordpassword).
Use the included test.rest file with REST client:
- VS Code REST Client extension
- Postman
- Insomnia
In local/development environment:
- Use verification code
1234for any user - 4-digit codes instead of 6-digit
- Instant verification without email
This project includes a full CI/CD pipeline and production-ready Docker setup designed for a single VPS behind Traefik.
# Production build
docker-compose build --no-cache
docker-compose up -dAPP_ENV=production
APP_DEBUG=false
MAIL_MAILER=smtp
MAIL_HOST=your-smtp-host
MAIL_USERNAME=your-email
MAIL_PASSWORD=your-password- Runtime topology: A single VPS runs Docker Compose services for
app(Laravel Octane),queue(workers),db(PostgreSQL),redis,minio, andtraefik(reverse proxy + TLS). - Reverse proxy & TLS: Traefik terminates HTTPS for
eazzyedu.xyz, manages Let’s Encrypt certificates, and routes traffic to theappcontainer on port 8002. - Stateful services: PostgreSQL, Redis, MinIO, and Traefik certificates use named Docker volumes for persistence (
db_data,redis_data,minio_data,traefik_letsencrypt). - Environment separation:
.env.production.exampleandDEPLOYMENT.mddescribe production-only settings (strong credentials, MinIO bucket, manifests TTL, etc.), kept out of version control as real secrets.
Deployment is fully automated via GitHub Actions (.github/workflows/deploy.yml):
- Trigger: Any push to
mainstarts the “Deploy Backend” workflow. - Build & test stage:
- Builds two Docker images (
app,queue) withdocker/build-push-action. - Pushes images to GitHub Container Registry (GHCR), tagged as
latestand with the commit SHA. - Runs Laravel tests in CI: installs Composer dependencies, generates
APP_KEY, runs migrations (test DB), and executes PHPUnit; deployment stops if tests fail.
- Builds two Docker images (
- Deploy stage:
- Uses an SSH key (
VPS_SSH_KEY) and host (VPS_HOST) from GitHub Actions secrets to connect to your VPS. rsyncs only deployment configs (docker-compose.yml,traefik.yml,traefik-dynamic.yml,.env) to/root/eazzy_backendon the VPS (source code stays in the container images).- Logs into GHCR on the VPS, runs
docker compose pullanddocker compose up -dto roll forward to the new images. - Performs a health check against
https://eazzyedu.xyz/api/health; if it fails, a rollback step tears down the new containers and restarts the previous state.
- Uses an SSH key (
Supporting docs:
DEPLOYMENT.md: end-to-end explanation of the CI/CD pipeline (images, GHCR, Traefik, health checks, rollback).VPS_SETUP.md: one-time VPS bootstrap (Docker, Compose, firewall, .env, GHCR auth, log rotation, backups).DEPLOYMENT_CHECKLIST.md: detailed pre-flight checklist (tests, secrets, infra, health checks, monitoring).QUICK_REFERENCE.md: operational cheat sheet for common maintenance and debugging commands on the VPS.
Complete API documentation is available in docs/api.md with:
- Request/response formats
- Authentication flows
- Error handling
- Security features
- Module/offer reference and up-to-date Postman collections (see
docs/catalog-module-endpoints.mdanddocs/catalog-postman-collection.json). - Quiz system documentation (see
docs/quiz-api-endpoints.mdanddocs/quiz-postman-collection.json). - DRM + offline download integration (
docs/offline-video-drm-plan.md,docs/flutter-drm-integration.md).
- Configure Secrets
- Set
LESSON_MASTER_KEY(32-byte base64) andLICENSE_JWT_SECRETin.env. - Point
APP_URLto the externally reachable host so manifests embed the correct segment proxy URL.
- Set
- Start Services
The
docker compose up -d db redis minio docker compose up -d app queue
queueservice runsphp artisan queue:work --queue=media,defaultso upload-triggered jobs transcode automatically. - Admin Upload Flow
- Admin dashboard calls
POST /api/admin/lessons/{lesson}/videowith multipart form data (videofile input). - Backend stores the raw source video on the
lesson_assetsdisk, creates/updates thelesson_assetsrow, and enqueues aTranscodeLessonjob. - Job runs FFmpeg to generate encrypted HLS, rewrites manifests to the
/api/lessons/{lesson}/segments/{file}proxy, and updates metadata (duration_seconds,segment_count,file_size_mb).
- Admin dashboard calls
- Student Download Flow
GET /api/catalog/lessons/{lesson}returns a download bundle with signed manifest URL + license token once the asset isready.GET /api/lessons/{lesson}/manifest?format=hlsrewrites the playlist so every segment is fetched through the Laravel proxy instead of MinIO.POST /api/lessons/{lesson}/licenseissues the AES key/IV for offline playback.- Manifest + segments always flow through authenticated endpoints to keep MinIO objects private.
You do not need the Flutter app to validate the backend. Every hop in the DRM-lite pipeline is exposed via HTTP, so Postman (or curl) can exercise the entire workflow:
- Authenticate via
/api/auth/loginwithdevice_idin the body. Save the returnedtokenand the samedevice_idas Postman variables. - Unlock the lesson (use seeded data or
POST /api/payments/mock-success). - Fetch
/api/catalog/lessons/{lesson}to copy thelicense_token, manifest URL, and byte counts. - Request
/api/lessons/{lesson}/manifest?format=hlswith headersAuthorization: Bearer {{token}}andX-Device-Id: {{device_id}}to inspect the rewritten playlist. - POST
/api/lessons/{lesson}/licensewith{ "license_token": "..." }to retrieve the AES key/IV; optionally store them in Postman environment variables for local decrypt tests. - Iterate
/api/lessons/{lesson}/segments/segment_000.ts(etc.) to fetch encrypted chunks. Postman can save them to disk for spot checks. - POST
/api/lessons/{lesson}/downloads/completewith the observed byte totals to close out the flow.
The updated Postman collection under docs/postman_collection.json already includes these requests and example headers so you can import and run them directly.
See docs/offline-video-drm-plan.md for architecture diagrams and docs/testing-guide.md for local verification steps.
- Single Token Policy: Only one active token per user
- Email Verification: Required before login
- Password Reset: 6-digit codes with 10-minute expiry
- Rate Limiting: Throttling on sensitive endpoints
- Token Revocation: All tokens revoked on password reset
- Email Enumeration Protection: Password reset always returns success
- Asynchronous & distributed processing: Clear separation between request/response APIs and heavy FFmpeg/HLS work via Laravel queues and dedicated workers.
- Applied security & access control: Device-scoped Sanctum auth, JWT-based licenses, encrypted media keys, and per-device session enforcement around high-value content.
- Media pipeline & DRM design: Practical implementation of a DRM-lite system with HLS transcoding, manifest rewriting, key management, and offline download workflows.
- Production-ready backend architecture: API-only Laravel 12 service using Octane, Redis, PostgreSQL, MinIO, and queues, wired together for realistic deployment and scaling.
- API design & documentation discipline: Well-structured endpoints, Markdown docs, and Postman collections that make the API approachable for both humans and tools.
- Containerized local and prod parity: Dockerized environments with supporting services (DB, cache, object storage, workers) for reproducible setups and easy onboarding.
MIT License