# File Management

This guide explains how to handle file uploads in the application, including temporary file storage and moving files to permanent storage using Spatie Media Library.

## Overview

The application uses a **two-phase upload pattern**:
1. **Temporary Upload:** Files are uploaded to a temporary location and stored in the `temporary_files` table
2. **Permanent Storage:** When the parent entity is saved, files are moved to permanent storage via Media Library

## Architecture Components

### Temporary File Model

**Location:** `app/Domains/Core/TemporaryFile/Models/TemporaryFile.php`

```php
class TemporaryFile extends Model
{
    protected $fillable = ['folder', 'filename', 'type'];

    // File type rules configuration
    public static array $rules = [
        'tasks' => [
            'multiple_files' => true,
            'extensions' => null,  // All extensions allowed
            'max_size' => 10240,   // 10MB in KB
        ],
        'products' => [
            'multiple_files' => true,
            'extensions' => ['jpg', 'jpeg', 'png', 'webp'],
            'max_size' => 5120,
        ],
        // ... more type definitions
    ];
}
```

### Storage Configuration

**Location:** `config/filesystems.php`

```php
'disks' => [
    'public' => [
        'driver' => 'local',
        'root' => storage_path('app/public'),
        'url' => env('APP_URL').'/storage',
        'visibility' => 'public',
    ],
],
```

**Temporary files path:** `storage/app/files/tmp/{unique_folder}/{filename}`
**Permanent files path:** `storage/app/public/{hash}/{filename}`

## Implementing File Uploads

### Step 1: Define File Type Rules

Add your file type to `TemporaryFile::$rules`:

```php
// In TemporaryFile model
public static array $rules = [
    // ... existing rules

    'your_entity' => [
        'multiple_files' => true,           // Allow multiple files
        'extensions' => ['pdf', 'jpg', 'png'], // Allowed extensions (null = all)
        'max_size' => 10240,                 // Max size in KB (10MB)
    ],
];
```

### Step 2: Upload Temporary Files (Frontend)

The frontend uploads files to the temporary endpoint:

```
POST /api/v1/upload-file
Content-Type: multipart/form-data

{
    "type": "your_entity",
    "file": <file>           // Single file
    // OR
    "files[]": [<files>]     // Multiple files
}
```

**Response:**
```json
{
    "data": [
        {
            "id": 1,
            "folder": "65a1b2c3d4e5f-1704067200",
            "filename": "document.pdf",
            "type": "your_entity"
        }
    ]
}
```

### Step 3: Configure Model for Media Library

Add Media Library support to your model:

```php
<?php

namespace App\Domains\YourDomain\Models;

use App\Models\BaseModel;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;

class YourEntity extends BaseModel implements HasMedia
{
    use InteractsWithMedia;

    protected static bool $applyCompanyScope = true;

    protected $fillable = ['name', 'company_id'];

    /**
     * Register media collections
     */
    public function registerMediaCollections(): void
    {
        // Single file collection
        $this->addMediaCollection('document')
            ->singleFile();

        // Multiple files collection
        $this->addMediaCollection('attachments');
    }
}
```

### Step 4: Move Files in Service

**Location:** Your service class

```php
<?php

namespace App\Domains\YourDomain\Services;

use App\Domains\Core\TemporaryFile\Services\TemporaryFileServices;
use App\Domains\YourDomain\Models\YourEntity;
use Illuminate\Support\Facades\DB;

class YourEntityService
{
    public function __construct(
        private TemporaryFileServices $temporaryFileServices
    ) {}

    public function store(YourEntityDTO $dto): YourEntity
    {
        return DB::transaction(function () use ($dto) {
            // 1. Create the entity
            $entity = YourEntity::create([
                'name' => $dto->name,
                'company_id' => company()->id,
            ]);

            // 2. Move temporary files to permanent storage
            if (!empty($dto->temporary_folders)) {
                $this->temporaryFileServices->moveTemporaryFilesToMedia(
                    $dto->temporary_folders,  // Array of folder IDs from upload
                    $entity,                   // The model instance
                    'your_entity',            // Type name (matches $rules key)
                    'attachments'             // Media collection name
                );
            }

            return $entity->fresh();
        });
    }
}
```

### Step 5: Handle in Request/DTO

**Request Validation:**

```php
public function rules(): array
{
    return [
        'name' => 'required|string|max:255',
        'temporary_folders' => 'nullable|array',
        'temporary_folders.*' => 'string',
    ];
}
```

**DTO:**

```php
class YourEntityDTO
{
    public function __construct(
        public string $name,
        public ?array $temporary_folders = null,
    ) {}

    public static function fromRequest(array $data): self
    {
        return new self(
            name: $data['name'],
            temporary_folders: $data['temporary_folders'] ?? null,
        );
    }
}
```

## Advanced Patterns

### Pattern 1: Single File Upload

```php
public function registerMediaCollections(): void
{
    $this->addMediaCollection('profile_image')
        ->singleFile()  // Only one file allowed, replaces existing
        ->acceptsMimeTypes(['image/jpeg', 'image/png']);
}
```

### Pattern 2: Multiple Collections

```php
public function registerMediaCollections(): void
{
    $this->addMediaCollection('documents');
    $this->addMediaCollection('images');
    $this->addMediaCollection('signature')->singleFile();
}
```

### Pattern 3: Direct Upload (Without Temporary)

For simple cases, upload directly:

```php
use App\Core\Helpers\UploadMedia\UploadMediaHelper;

public function storeWithDirectUpload(Request $request): YourEntity
{
    $entity = YourEntity::create([...]);

    if ($request->hasFile('image')) {
        UploadMediaHelper::upload(
            $request->file('image'),
            $entity,
            'profile_image'
        );
    }

    return $entity;
}
```

### Pattern 4: Updating Files

```php
public function update(YourEntity $entity, YourEntityDTO $dto): YourEntity
{
    return DB::transaction(function () use ($entity, $dto) {
        $entity->update([
            'name' => $dto->name,
        ]);

        // Add new files (for multi-file collections)
        if (!empty($dto->temporary_folders)) {
            $this->temporaryFileServices->moveTemporaryFilesToMedia(
                $dto->temporary_folders,
                $entity,
                'your_entity',
                'attachments'
            );
        }

        // For single file collections, singleFile() handles replacement automatically

        return $entity->fresh();
    });
}
```

### Pattern 5: Deleting Files

```php
// Delete specific media by ID
$entity->deleteMedia($mediaId);

// Clear entire collection
$entity->clearMediaCollection('attachments');

// Delete when entity is deleted (automatic if cascade is set)
```

## Accessing Files

### Get Media URLs

```php
// Get first media URL
$url = $entity->getFirstMediaUrl('attachments');

// Get all media URLs
$urls = $entity->getMedia('attachments')->map->getUrl();

// Get with conversions (if defined)
$thumbnailUrl = $entity->getFirstMediaUrl('images', 'thumbnail');
```

### Include in API Resources

```php
class YourEntityResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'document_url' => $this->getFirstMediaUrl('document'),
            'attachments' => $this->getMedia('attachments')->map(function ($media) {
                return [
                    'id' => $media->id,
                    'name' => $media->file_name,
                    'url' => $media->getUrl(),
                    'size' => $media->size,
                    'mime_type' => $media->mime_type,
                ];
            }),
        ];
    }
}
```

## File Type Determination

The system automatically determines file types from MIME types:

```php
// In services
private function determineFileType(string $mimeType): string
{
    return match (true) {
        str_starts_with($mimeType, 'image/') => 'image',
        str_starts_with($mimeType, 'video/') => 'video',
        str_starts_with($mimeType, 'audio/') => 'audio',
        $mimeType === 'application/pdf' => 'pdf',
        in_array($mimeType, ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']) => 'document',
        in_array($mimeType, ['application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet']) => 'spreadsheet',
        str_starts_with($mimeType, 'text/') => 'text',
        in_array($mimeType, ['application/zip', 'application/x-rar-compressed']) => 'archive',
        default => 'other',
    };
}
```

## Custom Path Generation

Files are stored with hashed paths for security:

**Location:** `app/Core/MediaPaths/CustomPathGenerator.php`

```php
class CustomPathGenerator implements PathGenerator
{
    public function getPath(Media $media): string
    {
        return md5($media->id . config('media-library.media_key')) . '/';
    }
}
```

## Cleanup

### Temporary Files Cleanup Command

**Location:** `app/Console/Commands/RemoveCashedFileCommand.php`

Run periodically to clean orphaned temporary files:

```bash
php artisan temporary:cleanup
```

## Best Practices

### DO:

1. **Always use temporary upload pattern** for user-facing forms
2. **Define file type rules** for validation consistency
3. **Use transactions** when creating entities with files
4. **Configure media collections** with appropriate constraints
5. **Include file metadata** in API responses

### DON'T:

1. **Don't store files without validation** - always validate type and size
2. **Don't expose internal paths** - use Media Library URLs
3. **Don't forget to clean up** temporary files
4. **Don't upload directly to public paths** - use storage with symbolic links

## Troubleshooting

### Files not accessible after upload

1. Ensure symbolic link exists:
   ```bash
   php artisan storage:link
   ```

2. Check file permissions on storage directory

3. Verify `APP_URL` is correct in `.env`

### Temporary files not moving

1. Check `TemporaryFile::$rules` has your type defined
2. Verify folder IDs are passed correctly from frontend
3. Check database for temporary file records

### Media not appearing on model

1. Ensure model implements `HasMedia` interface
2. Verify `InteractsWithMedia` trait is used
3. Check `registerMediaCollections()` is defined
