Skip to content
Merged
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
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,33 @@ public function boot()

**Note:** By default, users have an `isLibraryAdmin()` method that returns `false`. You can override this in your User model for custom logic.

## Events

The package dispatches events your application can listen for to extend behavior (for example, search indexing, webhooks after uploads, ...).

### `LibraryFileStored`

[`Tapp\FilamentLibrary\Events\LibraryFileStored`](src/Events/LibraryFileStored.php) is fired after a new file is stored on a library item: when Spatie Media Library creates a `Media` record for a `LibraryItem` whose `type` is `file`.

The event exposes:

- `$libraryItem` — the [`LibraryItem`](src/Models/LibraryItem.php) the file was attached to
- `$media` — the new [`Media`](https://github.com/spatie/laravel-medialibrary) model instance, or `null` if not available in edge cases

Example listener registration in `AppServiceProvider`:

```php
use Illuminate\Support\Facades\Event;
use Tapp\FilamentLibrary\Events\LibraryFileStored;

public function boot(): void
{
Event::listen(LibraryFileStored::class, function (LibraryFileStored $event): void {
// e.g. dispatch a job here
});
}
```

## Testing

```bash
Expand Down
5 changes: 3 additions & 2 deletions database/seeders/LibrarySeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tapp\FilamentLibrary\Database\Seeders;

use App\Models\User;
use Illuminate\Database\Seeder;
use Tapp\FilamentLibrary\Models\LibraryItem;
use Tapp\FilamentLibrary\Models\LibraryItemPermission;
Expand All @@ -14,7 +15,7 @@ class LibrarySeeder extends Seeder
public function run(): void
{
// Get the first user as the creator
$user = \App\Models\User::first();
$user = User::first();

if (! $user) {
$this->command->warn('No users found. Please create a user first.');
Expand Down Expand Up @@ -325,7 +326,7 @@ public function run(): void
}

// Create some sample permissions if there are other users
$otherUsers = \App\Models\User::where('id', '!=', $user->id)->take(2)->get();
$otherUsers = User::where('id', '!=', $user->id)->take(2)->get();

if ($otherUsers->count() > 0) {
$this->command->info('Creating sample permissions...');
Expand Down
23 changes: 23 additions & 0 deletions src/Events/LibraryFileStored.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace Tapp\FilamentLibrary\Events;

use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Tapp\FilamentLibrary\Models\LibraryItem;

/**
* Fired after a file was stored on a {@see LibraryItem} of type "file" (new Spatie media row).
* Host applications may listen to run AI tagging, search indexing, etc.
*/
class LibraryFileStored
{
use Dispatchable;
use SerializesModels;

public function __construct(
public LibraryItem $libraryItem,
public ?Media $media = null
) {}
}
9 changes: 6 additions & 3 deletions src/FilamentLibraryPlugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

namespace Tapp\FilamentLibrary;

use App\Models\User;
use Filament\Contracts\Plugin;
use Filament\Navigation\NavigationItem;
use Filament\Panel;
use Illuminate\Contracts\Auth\Authenticatable;
use Tapp\FilamentLibrary\Models\LibraryItem;
use Tapp\FilamentLibrary\Resources\LibraryItemResource;

class FilamentLibraryPlugin implements Plugin
Expand All @@ -29,7 +32,7 @@ public static function setLibraryAdminCallback(callable $callback): void
/**
* Check if a user is a library admin.
*
* @param \Illuminate\Contracts\Auth\Authenticatable|null $user
* @param Authenticatable|null $user
*/
public static function isLibraryAdmin($user): bool
{
Expand Down Expand Up @@ -108,8 +111,8 @@ public function register(Panel $panel): void
public function boot(Panel $panel): void
{
// Ensure users have personal folders when they access the library
\App\Models\User::created(function ($user) {
\Tapp\FilamentLibrary\Models\LibraryItem::ensurePersonalFolder($user);
User::created(function ($user) {
LibraryItem::ensurePersonalFolder($user);
});
}

Expand Down
32 changes: 31 additions & 1 deletion src/FilamentLibraryServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@
use Spatie\LaravelPackageTools\Commands\InstallCommand;
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Tapp\FilamentLibrary\Commands\FilamentLibraryCommand;
use Tapp\FilamentLibrary\Commands\SeedLibraryCommand;
use Tapp\FilamentLibrary\Events\LibraryFileStored;
use Tapp\FilamentLibrary\Middleware\RedirectToCorrectEditPage;
use Tapp\FilamentLibrary\Models\LibraryItem;
use Tapp\FilamentLibrary\Policies\LibraryItemPolicy;

Expand Down Expand Up @@ -75,7 +78,7 @@ public function packageBooted(): void
FilamentIcon::register($this->getIcons());

// Register middleware
$this->app['router']->pushMiddlewareToGroup('web', \Tapp\FilamentLibrary\Middleware\RedirectToCorrectEditPage::class);
$this->app['router']->pushMiddlewareToGroup('web', RedirectToCorrectEditPage::class);

// Register the policy
$this->app['Illuminate\Contracts\Auth\Access\Gate']->policy(LibraryItem::class, LibraryItemPolicy::class);
Expand All @@ -92,6 +95,8 @@ public function packageBooted(): void
// Load views manually from src directory
$this->loadViewsFrom(__DIR__ . '/Resources/views', static::$viewNamespace);

$this->registerLibraryFileStoredEvent();

// Publish views manually (optional)
$this->publishes([
__DIR__ . '/Resources/views' => resource_path('views/vendor/filament-library'),
Expand Down Expand Up @@ -164,6 +169,31 @@ protected function getScriptData(): array
return [];
}

/**
* When a file is attached to a library item, notify host apps (e.g. for AI auto-tagging).
*/
protected function registerLibraryFileStoredEvent(): void
{
if (! class_exists(Media::class)) {
return;
}

Media::created(function (Media $media): void {
$model = $media->model;
if (! $model instanceof LibraryItem) {
return;
}

$item = $model;

if ($item->type !== 'file') {
return;
}

event(new LibraryFileStored($item, $media));
});
}

/**
* @return array<string>
*/
Expand Down
21 changes: 12 additions & 9 deletions src/Models/LibraryItem.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,20 @@

namespace Tapp\FilamentLibrary\Models;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Tapp\FilamentLibrary\FilamentLibraryPlugin;
use Tapp\FilamentLibrary\Models\Traits\BelongsToTenant;

/**
Expand All @@ -26,15 +29,15 @@
* @property string|null $external_url
* @property string|null $link_description
* @property string|null $general_access
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property Carbon|null $deleted_at
* @property-read LibraryItem|null $parent
* @property-read \Illuminate\Database\Eloquent\Collection<int, LibraryItem> $children
* @property-read \Illuminate\Database\Eloquent\Model $creator
* @property-read \Illuminate\Database\Eloquent\Model|null $updater
* @property-read \Illuminate\Database\Eloquent\Collection<int, LibraryItemPermission> $permissions
* @property-read \Illuminate\Database\Eloquent\Collection<int, LibraryItemTag> $tags
* @property-read Collection<int, LibraryItem> $children
* @property-read Model $creator
* @property-read Model|null $updater
* @property-read Collection<int, LibraryItemPermission> $permissions
* @property-read Collection<int, LibraryItemTag> $tags
*/
class LibraryItem extends Model implements HasMedia
{
Expand Down Expand Up @@ -414,7 +417,7 @@ public function getInheritedGeneralAccessDisplay(): ?string
public function hasPermission($user, string $permission): bool
{
// Admin users always have all permissions
if ($user && \Tapp\FilamentLibrary\FilamentLibraryPlugin::isLibraryAdmin($user)) {
if ($user && FilamentLibraryPlugin::isLibraryAdmin($user)) {
return true;
}

Expand Down
7 changes: 4 additions & 3 deletions src/Models/LibraryItemPermission.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Carbon;
use Tapp\FilamentLibrary\Models\Traits\BelongsToTenant;

/**
* @property int $id
* @property int $library_item_id
* @property int $user_id
* @property string $role
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property-read LibraryItem $libraryItem
* @property-read \Illuminate\Database\Eloquent\Model $user
* @property-read Model $user
*/
class LibraryItemPermission extends Model
{
Expand Down
9 changes: 6 additions & 3 deletions src/Models/Traits/BelongsToTenant.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Tapp\FilamentLibrary\Models\Traits;

use Filament\Facades\Filament;
use Illuminate\Database\Eloquent\Model;

trait BelongsToTenant
{
/**
Expand Down Expand Up @@ -34,8 +37,8 @@ function ($model) {
$tenantRelationshipName = static::getTenantRelationshipName();

// Try to get tenant from Filament context (Filament's standard method)
if (class_exists(\Filament\Facades\Filament::class)) {
$tenant = \Filament\Facades\Filament::getTenant();
if (class_exists(Filament::class)) {
$tenant = Filament::getTenant();

if ($tenant) {
// Use Laravel's associate() method on the BelongsTo relationship
Expand All @@ -51,7 +54,7 @@ function ($model) {
$parentItemId = $model->library_item_id;
$parentItemClass = get_class($model->libraryItem()->getRelated());

/** @var class-string<\Illuminate\Database\Eloquent\Model> $parentItemClass */
/** @var class-string<Model> $parentItemClass */
$parentItem = $parentItemClass::find($parentItemId);

if ($parentItem) {
Expand Down
21 changes: 13 additions & 8 deletions src/Resources/LibraryItemResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
use Filament\Actions\ForceDeleteBulkAction;
use Filament\Actions\RestoreAction;
use Filament\Actions\RestoreBulkAction;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\SpatieMediaLibraryFileUpload;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Resources\Resource;
use Filament\Schemas\Schema;
use Filament\Tables;
use Filament\Tables\Table;
use Tapp\FilamentLibrary\Models\LibraryItem;
use Tapp\FilamentLibrary\Resources\RelationManagers\LibraryItemPermissionsRelationManager;

class LibraryItemResource extends Resource
{
Expand Down Expand Up @@ -91,11 +96,11 @@ public static function form(Schema $schema): Schema
{
return $schema
->schema([
\Filament\Forms\Components\TextInput::make('name')
TextInput::make('name')
->required()
->maxLength(255),

\Filament\Forms\Components\Select::make('type')
Select::make('type')
->options([
'folder' => 'Folder',
'file' => 'File',
Expand All @@ -106,26 +111,26 @@ public static function form(Schema $schema): Schema
->afterStateUpdated(fn (callable $set) => $set('external_url', null)),

// Folder form fields
\Filament\Forms\Components\Textarea::make('link_description')
Textarea::make('link_description')
->label('Description')
->visible(fn (callable $get) => $get('type') === 'folder')
->rows(3),

// File form fields
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('files')
SpatieMediaLibraryFileUpload::make('files')
->label('File')
->visible(fn (callable $get) => $get('type') === 'file')
->required(fn (callable $get) => $get('type') === 'file')
->maxSize(512000), // 500MB

// Link form fields
\Filament\Forms\Components\TextInput::make('external_url')
TextInput::make('external_url')
->label('URL')
->url()
->visible(fn (callable $get) => $get('type') === 'link')
->required(fn (callable $get) => $get('type') === 'link'),

\Filament\Forms\Components\Textarea::make('link_description')
Textarea::make('link_description')
->label('Description')
->visible(fn (callable $get) => $get('type') === 'link')
->rows(3),
Expand All @@ -136,7 +141,7 @@ public static function folderForm(Schema $schema): Schema
{
return $schema
->schema([
\Filament\Forms\Components\TextInput::make('name')
TextInput::make('name')
->label('Folder Name')
->required()
->maxLength(255)
Expand Down Expand Up @@ -347,7 +352,7 @@ public static function table(Table $table): Table
public static function getRelations(): array
{
return [
\Tapp\FilamentLibrary\Resources\RelationManagers\LibraryItemPermissionsRelationManager::class,
LibraryItemPermissionsRelationManager::class,
];
}

Expand Down
6 changes: 4 additions & 2 deletions src/Resources/Pages/CreatedByMe.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
namespace Tapp\FilamentLibrary\Resources\Pages;

use Filament\Resources\Pages\ListRecords;
use Illuminate\Database\Eloquent\Builder;
use Tapp\FilamentLibrary\Models\LibraryItem;
use Tapp\FilamentLibrary\Resources\LibraryItemResource;

class CreatedByMe extends ListRecords
Expand All @@ -11,13 +13,13 @@ class CreatedByMe extends ListRecords

protected static ?string $title = 'Created by Me';

protected function getTableQuery(): \Illuminate\Database\Eloquent\Builder
protected function getTableQuery(): Builder
{
$query = parent::getTableQuery();

$user = auth()->user();
if ($user) {
$personalFolder = \Tapp\FilamentLibrary\Models\LibraryItem::getPersonalFolder($user);
$personalFolder = LibraryItem::getPersonalFolder($user);
$query->where('created_by', $user->id);

// Exclude personal folder if it exists
Expand Down
Loading