Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.log
/.phpunit.cache
/node_modules

Expand Down
81 changes: 4 additions & 77 deletions app/Console/Commands/MigratePhotos.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,17 @@

namespace App\Console\Commands;

use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Str;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;

#[Signature('photos:migrate {--dry-run : Only show actions without moving or deleting files and/or folders}')]
#[Description('Migrate old photo folder structure (photos, photos-096, photos-384) into the new unified structure with original + variants')]
class MigratePhotos extends Command
{
/**
* Photo Migration Command
*
* This command migrates photos from an old folder structure to a new unified structure.
* It reorganizes photo files from three separate folders (photos, photos-096, photos-384)
* into a single unified photos folder with originals and WebP variants.
*
* Usage: php artisan photos:migrate [--dry-run]
*
* OLD STRUCTURE:
* storage/app/public/
* ├── photos/ (largest size - WebP)
* └── {teamId}/
* ├── {personId}_{index}_{timestamp}.webp
* ├── photos-096/ (small size - WebP)
* └── {teamId}/
* ├── {personId}_{index}_{timestamp}.webp
* └── photos-384/ (medium size - WebP)
* └── {teamId}/
* ├── {personId}_{index}_{timestamp}.webp
*
* NEW STRUCTURE:
* storage/app/public/photos/
* └── {teamId}/
* └── {personId}/
* ├── {personId}_{index}_{timestamp}.webp (original - from photos/)
* ├── {personId}_{index}_{timestamp}_large.webp (1920x1080 - copy of original)
* ├── {personId}_{index}_{timestamp}_medium.webp (384px - from photos-384/)
* └── {personId}_{index}_{timestamp}_small.webp (96px - from photos-096/)
*
* MIGRATION STRATEGY:
* 1. Files from "photos/" become the "original" (no suffix) AND are duplicated as "_large.webp"
* 2. Files from "photos-096/" become "_small.webp" variants
* 3. Files from "photos-384/" become "_medium.webp" variants
* 4. All files are kept as WebP format
* 5. Files that don't exist in all three folders are ignored
*
* OPERATIONS:
* 1. Creates backup of existing folders (photos, photos-096, photos-384) with timestamp
* 2. Checks if migration has already been run (photos-096 and photos-384 must exist)
* 3. Scans "photos/" folder to identify all available photos
* 4. For each photo found in "photos/":
* a. Copies as original (no suffix)
* b. Copies as _large.webp variant
* c. Copies from photos-384/ as _medium.webp (if exists)
* d. Copies from photos-096/ as _small.webp (if exists)
* 5. Creates new directory structure (photos/{teamId}/{personId}/)
* 6. Copies files to new locations and deletes originals
* 7. Cleans up empty folders
*
* SAFETY FEATURES:
* - Creates timestamped backups before migration
* - Prevents multiple runs by checking if old folders still exist
* - Dry-run mode (--dry-run) shows actions without executing
* - Skips .gitignore files during processing
* - Error handling for unexpected file structures
* - Ensures target directories exist before copying
* - Ignores photos that don't have all variants
*
* EXAMPLE:
* photos/1/560_001_20250816113838.webp → photos/1/560/560_001_20250816113838.webp (original)
* → photos/1/560/560_001_20250816113838_large.webp
* photos-384/1/560_001_20250816113838.webp → photos/1/560/560_001_20250816113838_medium.webp
* photos-096/1/560_001_20250816113838.webp → photos/1/560/560_001_20250816113838_small.webp
*
* PURPOSE:
* Refactors photo storage system for better organization by team and person while
* maintaining different image sizes in a structured way for easier management.
* This improves performance and allows migrating disks to other storage solutions.
*
* WARNING:
* This command is intended to be run ONLY ONCE during the migration process!!
*/
protected $signature = 'photos:migrate {--dry-run : Only show actions without moving or deleting files and/or folders}';

protected $description = 'Migrate old photo folder structure (photos, photos-096, photos-384) into the new unified structure with original + variants';

public function handle(): int
{
$dryRun = $this->option('dry-run');
Expand Down
64 changes: 42 additions & 22 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@
"type": "project",
"description": "A free and open-source family tree application to record family members and their relationships.",
"keywords": [
"php",
"laravel",
"livewire",
"tailwindcss",
"tallstack",
"genealogy",
"family-tree",
"ancestry",
"gedcom",
"lineage",
"pedigree",
"open-source",
"family-history",
"laravel-application"
"php",
"laravel",
"livewire",
"tailwindcss",
"tallstack",
"genealogy",
"family-tree",
"ancestry",
"gedcom",
"lineage",
"pedigree",
"open-source",
"family-history",
"laravel-application"
],
"homepage": "https://github.com/MGeurts/genealogy",
"support": {
"source": "https://github.com/MGeurts/genealogy",
"demo": "https://genealogy.kreaweb.be",
"docs": "https://genealogy.kreaweb.be/help"
"source": "https://github.com/MGeurts/genealogy",
"demo": "https://genealogy.kreaweb.be",
"docs": "https://genealogy.kreaweb.be/help"
},
"license": "MIT",
"authors": [
Expand All @@ -37,12 +37,12 @@
"filament/tables": "^5.3",
"intervention/image": "^3.11",
"korridor/laravel-has-many-merged": "^1.2",
"laravel/framework": "^12.54",
"laravel/framework": "^13.0",
"laravel/jetstream": "^5.5",
"laravel/sanctum": "^4.3",
"laravel/tinker": "^2.11",
"laravel/tinker": "^3.0",
"livewire/livewire": "~4.2",
"opcodesio/log-viewer": "^3.23",
"opcodesio/log-viewer": "^3.24",
"secondnetwork/blade-tabler-icons": "^3.40",
"spatie/laravel-activitylog": "^4.12",
"spatie/laravel-backup": "^10.0",
Expand All @@ -54,7 +54,7 @@
"require-dev": {
"alisalehi/laravel-lang-files-translator": "^1.0",
"barryvdh/laravel-debugbar": "^4.1",
"barryvdh/laravel-ide-helper": "^3.6",
"barryvdh/laravel-ide-helper": "^3.7",
"fakerphp/faker": "^1.24",
"larastan/larastan": "^3.9",
"laravel/boost": "^2.3",
Expand All @@ -65,7 +65,8 @@
"nunomaduro/collision": "^8.9",
"pestphp/pest": "^4.4",
"pestphp/pest-plugin-livewire": "^4.1",
"spatie/laravel-ignition": "^2.11"
"spatie/laravel-ignition": "^2.11",
"laravel/pail": "^1.2.2"
},
"autoload": {
"psr-4": {
Expand Down Expand Up @@ -98,6 +99,25 @@
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
],
"setup": [
"composer install",
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
"@php artisan key:generate",
"@php artisan migrate --force",
"npm install",
"npm run build"
],
"test": [
"@php artisan config:clear --ansi",
"@php artisan test"
],
"pre-package-uninstall": [
"Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
]
},
"extra": {
Expand Down
33 changes: 28 additions & 5 deletions config/cache.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
| well as their drivers. You may even define multiple stores for the
| same cache driver to group types of items stored in your caches.
|
| Supported drivers: "apc", "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane", "null"
| Supported drivers: "array", "database", "file", "memcached",
| "redis", "dynamodb", "octane",
| "failover", "null"
|
*/

Expand All @@ -42,9 +43,10 @@

'database' => [
'driver' => 'database',
'connection' => env('DB_CACHE_CONNECTION'),
'table' => env('DB_CACHE_TABLE', 'cache'),
'connection' => env('DB_CACHE_CONNECTION', null),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION', null),
'lock_connection' => env('DB_CACHE_LOCK_CONNECTION'),
'lock_table' => env('DB_CACHE_LOCK_TABLE'),
],

'file' => [
Expand Down Expand Up @@ -91,6 +93,14 @@
'driver' => 'octane',
],

'failover' => [
'driver' => 'failover',
'stores' => [
'database',
'array',
],
],

],

/*
Expand All @@ -104,6 +114,19 @@
|
*/

'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'Genealogy'), '_') . '_cache_'),
'prefix' => env('CACHE_PREFIX', Str::slug((string) env('APP_NAME', 'laravel')) . '-cache-'),

/*
|--------------------------------------------------------------------------
| Serializable Classes
|--------------------------------------------------------------------------
|
| This value determines the classes that can be unserialized from cache
| storage. By default, no PHP classes will be unserialized from your
| cache to prevent gadget chain attacks if your APP_KEY is leaked.
|
*/

'serializable_classes' => false,

];
Loading
Loading