LaravelPackages.net
Acme Inc.
Toggle sidebar
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.

45
0
v1.1.0
About univpancasila/storage-up

univpancasila/storage-up is a Laravel package for 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.. It currently has 0 GitHub stars and 45 downloads on Packagist (latest version v1.1.0). Install it with composer require univpancasila/storage-up. Discover more Laravel packages by univpancasila or browse all Laravel packages to compare alternatives.

Last updated

StorageUp - Laravel File Storage Package

Latest Version on Packagist Total Downloads License

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." — call apiKey() before upload()
  • "Model not set. Use for() method first." — call for() before upload()
  • "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 [email protected] instead of the issue tracker.

Credits

License

MIT — see LICENSE.

Comments