A web application that links a library of photos and videos to an interactive map and timeline, allowing users to explore their memories by location and date.
For the full backend + frontend + UX execution roadmap, see IMPLEMENTATION_WORK_PLAN.md. For per-workstream release tracking and quality-gate evidence, see releases/workstreams/INDEX.md.
- Interactive map – Browse all media on a Leaflet map, clustered by location
- Timeline view – Browse media grouped by day with a mini per-day map
- Media viewer – Full image/video viewer with EXIF metadata display
- Annotations – Add notes at map, day, location, or individual media level
- Multiple maps – Create as many Memories Maps as you like
- Colour themes – Apply one of 6 built-in themes (including a WCAG AAA high-contrast theme) per map
- Guest sharing – Invite viewers by email; randomly generated passwords they can change
- WCAG AAA – Full ARIA adherence, skip links, focus management, reduced-motion support
- LAMP deployable – PHP 8.2/Laravel 11 backend + compiled React/TypeScript frontend
| Layer | Technology |
|---|---|
| Backend API | PHP 8.2, Laravel 11, Laravel Sanctum |
| Database | MySQL 8.4 |
| Frontend | React 18, TypeScript, Vite, React-Leaflet |
| Maps | Leaflet.js + OpenStreetMap tiles |
| State | Zustand, TanStack Query |
| Media processing | PHP exif_read_data(), Intervention Image, ffprobe/ffmpeg |
| Local dev | Docker Compose (PHP-FPM + Nginx + MySQL) |
| Production | Apache 2.4, PHP-FPM, MySQL, Let's Encrypt TLS |
Run these commands from PowerShell in the project root:
# 1. Copy backend env
Copy-Item backend/.env.example backend/.env
# 2. Start services
docker compose up -d --build
# 3. First-time setup
docker compose exec app php /var/www/memories-map/backend/artisan key:generate --force
docker compose exec app php /var/www/memories-map/backend/artisan migrate --seed --forceApp URLs:
- Frontend: http://localhost:5173
- API: http://localhost:8080/api
If you are deciding what kind of change to make next, use one of these common tracks.
- Reproduce the issue in Docker first.
- Validate backend logic:
docker compose exec app php /var/www/memories-map/backend/artisan test- Validate frontend compile/lint (PowerShell):
npm.cmd --prefix frontend run build
npm.cmd --prefix frontend run lint- Add backend API changes first (controllers, policies, validation).
- Add frontend state/UI changes second.
- Re-run migrations and smoke test the map, timeline, upload, and share flows.
- Keep contrast at WCAG 2.2 AAA levels.
- Check keyboard navigation and visible focus states.
- Run frontend checks:
npm.cmd --prefix frontend run build- Update setup steps when scripts, routes, or deployment flow changes.
- Keep Windows PowerShell command variants where relevant.
- Build frontend artifacts before deploy:
npm.cmd --prefix frontend run build- Run backend migrations in the target environment:
php artisan migrate --force- Verify authentication, map loading, media upload, and shared-link access after release.
- Docker Desktop
- Node 20+ (for running frontend outside Docker, optional)
# 1. Copy backend env
cp backend/.env.example backend/.env
# Edit backend/.env – set DB_* values to match docker-compose.yml defaults
# DB_HOST=db DB_DATABASE=memories_map DB_USERNAME=memories_map_user DB_PASSWORD=secret
# 2. Start all services
docker compose up -d --build
# 3. First-time: generate app key and run migrations
docker compose exec app php /var/www/memories-map/backend/artisan key:generate --force
docker compose exec app php /var/www/memories-map/backend/artisan migrate --seed --force# 1. Copy backend env
Copy-Item backend/.env.example backend/.env
# 2. Start all services
docker compose up -d --build
# 3. First-time: generate app key and run migrations
docker compose exec app php /var/www/memories-map/backend/artisan key:generate --force
docker compose exec app php /var/www/memories-map/backend/artisan migrate --seed --force- Error:
could not open input file: artisan- Cause: running
php artisan ...from a working directory where theartisanfile is not present. - Fix: use the full container path shown above:
php /var/www/memories-map/backend/artisan ...
- Cause: running
| Service | URL |
|---|---|
| API (via Nginx) | http://localhost:8080/api |
| Frontend (Vite HMR) | http://localhost:5173 |
| MySQL | localhost:3306 |
Workstream A moves heavy media processing to the queue. In Docker, a dedicated queue_worker service now runs automatically.
Manual worker command (if needed):
docker compose exec app php /var/www/memories-map/backend/artisan queue:work --queue=media-processing,default --tries=5 --timeout=240 --backoff=10Useful queue operations:
docker compose exec app php /var/www/memories-map/backend/artisan queue:failed
docker compose exec app php /var/www/memories-map/backend/artisan queue:failed-replay --allMedia processing status endpoint:
GET /api/maps/{map}/media/{media}/processing-status
- Ubuntu 22.04 / Debian 12
- Apache 2.4 with
mod_ssl,mod_headers,mod_proxy_fcgi - PHP 8.2 + extensions:
pdo_mysql,mbstring,exif,gd,bcmath,pcntl,opcache - Composer 2
- Node 20 + npm (build only)
- MySQL 8.x
ffmpeg+ffprobe(for video thumbnails and metadata)- Certbot / Let's Encrypt
# 1. Clone the repo
git clone https://github.com/Hart365/The_Memories_Map.git /var/www/memories-map
# 2. Configure environment
cp /var/www/memories-map/backend/.env.example /var/www/memories-map/backend/.env
# Edit .env – set APP_URL, DB_*, MAIL_*, MEDIA_STORAGE_PATH, etc.
# 3. Run deployment script (run as root or with sudo)
bash /var/www/memories-map/deploy/deploy.sh
# 4. Enable Apache virtual host
cp /var/www/memories-map/deploy/apache/memories-map.conf /etc/apache2/sites-available/
a2ensite memories-map
a2enmod ssl headers proxy_fcgi rewrite
systemctl reload apache2
# 5. Generate TLS certificate
certbot --apache -d your-domain.comMedia files are stored outside the web root at the path set in MEDIA_STORAGE_PATH (default: /var/memories-map/media).
Ensure this directory is:
- Owned by
www-data chmod 750(no world read)- Not accessible via Apache (it is not under
DocumentRoot)
For shared hosting and standard LAMP with cPanel, use:
deploy/CPANEL_SOFTACULOUS_INSTALL.mddeploy/cpanel/post_install.shdeploy/cpanel/publish_public_html.shdeploy/cpanel/create_release_zip.shdeploy/cpanel/create_release_zip.ps1
Quick path:
- Upload repo under your home folder (outside
public_htmlwhen possible). - Configure
backend/.envwith production DB + app URL values. - Optional: create a cPanel-ready release archive locally:
bash deploy/cpanel/create_release_zip.shOn Windows:
powershell -ExecutionPolicy Bypass -File .\deploy\cpanel\create_release_zip.ps1- Run:
bash deploy/cpanel/post_install.sh- If your host does not allow document root to
backend/public, publish topublic_html:
PUBLIC_HTML_DIR=/home/CPANEL_USER/public_html BACKEND_DIR=/home/CPANEL_USER/memories-map/backend bash deploy/cpanel/publish_public_html.shThis provides an install flow that works with common cPanel/Softaculous constraints.
If you already have Memories Map deployed and want to upgrade safely via cPanel File Manager:
- Back up your current database (phpMyAdmin export) and copy your current
backend/.env. - Build or download the latest cPanel release zip (with vendor bundle).
- Upload the zip to your cPanel home folder and extract it to your existing app path (for example,
/home/CPANEL_USER/memories-map). - Upload
deploy/cpanel/memories-map-installer.phptopublic_html. - Open
https://your-domain.com/memories-map-installer.phpand run the wizard using your existing app path and database credentials. - Let the installer complete migrations and republish frontend/public assets.
- Delete
memories-map-installer.phpandmemories-map-installer.lockfrompublic_html.
Notes:
- The installer now preserves an existing
APP_KEYduring upgrades so encrypted data remains readable. - Do not run migration files directly; the installer runs Laravel migrations automatically.
- If you deploy manually without the installer, run
php artisan migrate --forceonce in the backend.
The_Memories_Map/
├── backend/ # Laravel 11 PHP API
│ ├── app/
│ │ ├── Http/Controllers/Api/
│ │ ├── Http/Middleware/
│ │ ├── Models/
│ │ ├── Policies/
│ │ └── Services/
│ ├── database/
│ │ ├── migrations/
│ │ └── seeders/
│ └── routes/api.php
├── frontend/ # React 18 + TypeScript + Vite
│ └── src/
│ ├── components/ # Shared UI components
│ ├── pages/ # Route-level pages
│ ├── store/ # Zustand auth store
│ ├── lib/ # Axios instance
│ ├── types/ # Shared TypeScript types
│ └── styles/ # Global & component CSS
├── docker/ # Local dev Docker config
│ ├── php/Dockerfile
│ └── nginx/default.conf
├── deploy/ # Production deployment
│ ├── apache/memories-map.conf
│ └── deploy.sh
└── docker-compose.yml
- All API routes require a Sanctum bearer token (30-day expiry)
- Guest tokens are random 64-character strings; validated in
GuestMapAccessmiddleware - Passwords: bcrypt with 14 rounds, minimum 12 chars + mixed case + numbers + symbols
- Guest access tokens validated per-map to prevent token reuse across maps
- Media served via PHP controller (never via direct Apache URL)
- Uploaded originals and generated thumbnails are encrypted at rest on disk (
MEDIA_ENCRYPTION_ENABLED=true) - Optional dedicated key supported via
MEDIA_ENCRYPTION_KEY(falls back toAPP_KEY) - Existing plaintext media can be migrated with
php artisan media:encrypt-existing - Media files are stored under per-map UUID subfolders to isolate tenant data on disk
- Deleting a map cascades to delete all associated media records and media files
- Sessions encrypted (
SESSION_ENCRYPT=true) - CSP, HSTS, X-Frame-Options, X-Content-Type-Options headers set in Apache config
- Modern TLS only (TLS 1.2/1.3)
- Soft deletes on all sensitive models
- All user input validated with Laravel's
validate()rules
- Skip-to-content link on every page
- All interactive elements reachable by keyboard;
:focus-visiblenever suppressed - ARIA
role,aria-label,aria-live,aria-required,aria-busy,aria-expandedthroughout - High-contrast theme available
prefers-reduced-motionrespected in CSS<video>elements include fallback text and nativecontrols- Images always have descriptive
alttext - Form errors linked to inputs with
aria-describedby - Toasts use
role="status"witharia-live="polite"
MIT © 2026 Hart365