univpancasila / storage-up
A Laravel package developed by the Internal Organization of the University of Pancasila, designed to simplify file storage management. This package provides an intuitive facade for uploading, retrieving, and managing files across organizational applications through a unified API.
Fund package maintenance!
Requires
- php: ^8.1|^8.2|^8.3|^8.4
- guzzlehttp/guzzle: ^7.0
- illuminate/contracts: ^9.0|^10.0|^11.0|^12.0|^13.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0|^2.9
- laravel/pint: ^1.14
- nunomaduro/collision: ^9.0|^8.1.1
- orchestra/testbench: ^10.0|^9.0.0|^8.22.0
- pestphp/pest: ^3.0|^2.34
- pestphp/pest-plugin-arch: ^3.0|^2.7
- pestphp/pest-plugin-laravel: ^3.0|^2.3
- phpstan/extension-installer: ^1.3
- phpstan/phpstan-deprecation-rules: ^2.0|^1.1
- phpstan/phpstan-phpunit: ^2.0|^1.3
- spatie/laravel-ray: ^1.35
This package is auto-updated.
Last update: 2026-05-04 09:00:22 UTC
README
A Laravel package by the Internal Organization of the University of Pancasila. Provides a clean facade for uploading, retrieving, and deleting files via a unified storage API.
Features
- Simple facade interface with fluent method chaining
- Polymorphic relations — attach files to any Eloquent model
- Named collections for organizing files
- Automatic HTTP retry (configurable)
- Database tracking of all file metadata
- Composite indexes for fast lookups
- 54 tests with comprehensive coverage
Requirements
- PHP 8.1, 8.2, 8.3, or 8.4
- Laravel 9.x, 10.x, 11.x, 12.x, or 13.x
- Guzzle HTTP Client 7.0+
Installation
1. Install via Composer
composer require univpancasila/storage-up
2. Publish configuration
php artisan vendor:publish --tag=storageup-config
3. Publish and run migrations
php artisan vendor:publish --tag=storageup-migrations php artisan migrate
4. Configure environment
STORAGE_UP_API_URL=https://storage.univpancasila.ac.id STORAGE_UP_API_KEY=your-api-key-here # Optional STORAGE_UP_UPLOAD_ENDPOINT=/api/v1/storage/upload STORAGE_UP_DELETE_ENDPOINT=/api/v1/storage/delete STORAGE_UP_UPLOAD_RETRY=3 STORAGE_UP_DELETE_RETRY=10 STORAGE_UP_MAX_SIZE=10240
Basic Usage
Upload a File
use Univpancasila\StorageUp\Facades\StorageUp; $file = StorageUp::apiKey(config('storageup.api_keys.default')) ->for($user) ->collection('documents') ->upload($request->file('document')); // $file is a StorageFile model: // id, original_name, filename, file_id, url, url_thumbnail, // collection_name, model_type, model_id, timestamps
Retrieve Files
// All files in a collection $documents = StorageUp::getFile($user, 'documents'); // Latest file only $latest = StorageUp::getFile($user, 'documents', latest: true);
Delete Files
// Delete a specific file StorageUp::deleteFile($file); // Delete all files in a collection StorageUp::deleteAllFiles($user, 'documents'); // Delete all files for a model StorageUp::deleteAllFiles($user);
Advanced Usage
Custom API Configuration
$file = StorageUp::apiKey('custom-key') ->apiUrl('https://custom-storage.example.com') ->for($user) ->collection('profile-pictures') ->upload($request->file('avatar'));
Any Eloquent Model
$project = Project::find(1); StorageUp::apiKey(config('storageup.api_keys.default')) ->for($project) ->collection('blueprints') ->upload($request->file('blueprint'));
Model Integration
Add a relationship to any model that needs file storage:
use Univpancasila\StorageUp\Models\StorageFile; class User extends Model { public function storageFiles() { return $this->morphMany(StorageFile::class, 'model'); } }
Usage:
// Eager load to avoid N+1 $users = User::with('storageFiles')->get(); // Filter by collection $documents = $user->storageFiles() ->where('collection_name', 'documents') ->get();
Controller Example
use Univpancasila\StorageUp\Facades\StorageUp; use Univpancasila\StorageUp\Models\StorageFile; class FileController extends Controller { public function upload(Request $request) { $request->validate([ 'file' => 'required|file|max:10240', 'collection' => 'required|string', ]); $file = StorageUp::apiKey(config('storageup.api_keys.default')) ->for(auth()->user()) ->collection($request->collection) ->upload($request->file('file')); return response()->json([ 'id' => $file->id, 'name' => $file->original_name, 'url' => $file->url, 'thumbnail' => $file->url_thumbnail, ]); } public function index(Request $request) { $user = auth()->user(); $collection = $request->get('collection'); $files = $collection ? StorageUp::getFile($user, $collection) : $user->storageFiles; return response()->json($files); } public function destroy($id) { $file = StorageFile::findOrFail($id); abort_if($file->model_id !== auth()->id(), 403); StorageUp::deleteFile($file); return response()->json(['message' => 'File deleted']); } }
API Reference
Facade Methods
| Method | Description |
|---|---|
apiKey(string $apiKey): self |
Set API key |
apiUrl(string $url): self |
Set custom API URL |
collection(string $name): self |
Set collection name |
for(Model $model): self |
Bind to an Eloquent model |
upload(UploadedFile $file, ?string $type = null): StorageFile |
Upload file |
getFile(Model $model, string $collectionName, bool $latest = false) |
Retrieve files |
deleteFile(StorageFile $file): ?bool |
Delete a file |
deleteAllFiles(Model $model, ?string $collectionName = null): void |
Bulk delete |
StorageFile Attributes
$file->id $file->model_type // polymorphic type $file->model_id // polymorphic id $file->collection_name $file->original_name $file->filename $file->file_id // remote storage ID $file->url $file->url_thumbnail $file->created_at $file->updated_at
StorageFile Methods
// Delete file from remote storage and database $file->deleteFile(?string $apiKey = null, ?string $apiUrl = null): bool; // Static bulk delete StorageFile::deleteAllFiles(string $modelType, $modelId, ?string $collectionName = null): void;
Configuration
config/storageup.php:
return [ 'api_url' => env('STORAGE_UP_API_URL', 'https://storage.univpancasila.ac.id'), 'api_keys' => [ 'default' => env('STORAGE_UP_API_KEY'), ], 'endpoints' => [ 'upload' => env('STORAGE_UP_UPLOAD_ENDPOINT', '/api/v1/storage/upload'), 'delete' => env('STORAGE_UP_DELETE_ENDPOINT', '/api/v1/storage/delete'), ], 'retry' => [ 'upload' => env('STORAGE_UP_UPLOAD_RETRY', 3), 'delete' => env('STORAGE_UP_DELETE_RETRY', 10), ], 'validation' => [ 'max_size' => env('STORAGE_UP_MAX_SIZE', 10240), // KB 'allowed_mimes' => ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'doc', 'docx', 'xls', 'xlsx', 'txt', 'csv', 'zip', 'rar'], ], ];
Use multiple API keys:
StorageUp::apiKey(config('storageup.api_keys.admin'))->...
Apply validation in controllers:
$request->validate([ 'file' => [ 'required', 'file', 'max:' . config('storageup.validation.max_size'), 'mimes:' . implode(',', config('storageup.validation.allowed_mimes')), ], ]);
Error Handling
try { $file = StorageUp::apiKey(config('storageup.api_keys.default')) ->for($user) ->collection('documents') ->upload($request->file('document')); } catch (\Exception $e) { Log::error('File upload failed', ['error' => $e->getMessage()]); throw $e; }
Possible exception messages:
"API key not set. Use apiKey() method first."— callapiKey()beforeupload()"Model not set. Use for() method first."— callfor()beforeupload()"Failed to upload file to storage service."— network or API error
Testing
composer test # Run all tests composer test-coverage # With coverage composer analyse # PHPStan static analysis composer format # Pint code style
Mocking in your application tests:
use Illuminate\Support\Facades\Http; Http::fake([ '*/api/v1/storage/upload' => Http::response([ 'status' => 'success', 'data' => [ 'fileName' => 'test.pdf', 'fileId' => 'file-123', 'link' => 'https://storage.example.com/test.pdf', ], ], 200), ]);
Blade Example
@forelse($user->storageFiles as $file) <div> @if($file->url_thumbnail) <img src="{{ $file->url_thumbnail }}" alt="{{ $file->original_name }}"> @endif <p>{{ $file->original_name }}</p> <p>{{ $file->collection_name ?? 'Default' }}</p> <a href="{{ $file->url }}" target="_blank">View</a> <form action="{{ route('files.destroy', $file->id) }}" method="POST"> @csrf @method('DELETE') <button type="submit">Delete</button> </form> </div> @empty <p>No files uploaded yet.</p> @endforelse
Performance Tips
Cache frequently accessed URLs:
$url = Cache::remember("user.{$user->id}.avatar", now()->addHours(24), fn() => StorageUp::getFile($user, 'avatars', latest: true)?->url );
Eager load relationships:
$users = User::with('storageFiles')->get(); // Or scoped to a collection $users = User::with(['storageFiles' => fn($q) => $q->where('collection_name', 'documents') ])->get();
Troubleshooting
Facade not found
php artisan config:clear && php artisan package:discover && composer dump-autoload
Files not in database
php artisan vendor:publish --tag=storageup-migrations && php artisan migrate
Upload fails — verify API key, URL reachability, and file size against STORAGE_UP_MAX_SIZE.
Changelog
See CHANGELOG for recent changes.
Contributing
Pull requests are welcome. For development setup:
git clone https://github.com/univpancasila/storageup-facade.git cd storageup-facade composer install composer test
Security
Report security issues to abdan@univpancasila.ac.id instead of the issue tracker.
Credits
License
MIT — see LICENSE.