Files
rspade_system/app/RSpade/man/file_upload_examples.txt
root 1683df867b Extract Rsx_Droppable into its own file
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-15 07:53:49 +00:00

519 lines
16 KiB
Plaintext
Executable File

NAME
File Upload Examples - Practical examples for implementing file uploads
SYNOPSIS
Examples for implementing file upload endpoints and processing files
in RSX applications using the File_Attachment_Model factory methods.
DESCRIPTION
This document provides practical, copy-paste ready examples for common
file upload scenarios. All examples use the factory methods on
File_Attachment_Model which handle storage, deduplication, and metadata
automatically.
BASIC HTTP FILE UPLOAD
Simple file upload endpoint with validation:
Controller: /rsx/app/frontend/files/files_controller.php
<?php
namespace Rsx\Controllers;
use Illuminate\Http\Request;
use Rsx\Models\File_Attachment_Model;
use App\RSpade\Core\Controller\Rsx_Controller_Abstract;
class Files_Controller extends Rsx_Controller_Abstract
{
#[Route('/files/upload', method: 'POST')]
#[Auth('Permission::authenticated()')]
#[Ajax_Endpoint]
public static function upload(Request $request, array $params = [])
{
// Validate uploaded file
$request->validate([
'file' => 'required|file|max:10240', // 10MB max
]);
// Get site_id from authenticated user
$site_id = RsxAuth::session()->site_id;
// Create attachment
$attachment = File_Attachment_Model::create_from_upload(
$request->file('file'),
[
'site_id' => $site_id,
'fileable_category' => 'general'
]
);
return [
'success' => true,
'key' => $attachment->key,
'filename' => $attachment->file_name,
'size' => $attachment->file_storage->size,
'url' => $attachment->get_url()
];
}
}
USER AVATAR UPLOAD
Upload and attach to user model with category:
#[Route('/profile/avatar/upload', method: 'POST')]
#[Auth('Permission::authenticated()')]
#[Ajax_Endpoint]
public static function upload_avatar(Request $request, array $params = [])
{
$request->validate([
'avatar' => 'required|image|max:5120', // 5MB, images only
]);
$user = RsxAuth::user();
$site_id = RsxAuth::session()->site_id;
// Delete old avatar if exists
$old_avatar = File_Attachment_Model::where('fileable_type', 'User_Model')
->where('fileable_id', $user->id)
->where('fileable_category', 'avatar')
->first();
if ($old_avatar) {
$old_avatar->delete(); // Auto-cleanup handles storage
}
// Create new avatar
$attachment = File_Attachment_Model::create_from_upload(
$request->file('avatar'),
[
'site_id' => $site_id,
'fileable_type' => 'User_Model',
'fileable_id' => $user->id,
'fileable_category' => 'avatar',
'fileable_type_meta' => 'profile'
]
);
return [
'success' => true,
'url' => $attachment->get_url()
];
}
MULTIPLE FILE UPLOAD
Handle multiple files with ordering:
#[Route('/project/{id}/documents/upload', method: 'POST')]
#[Auth('Permission::authenticated()')]
#[Ajax_Endpoint]
public static function upload_documents(Request $request, array $params = [])
{
$project_id = $params['id'];
$site_id = RsxAuth::session()->site_id;
$request->validate([
'documents' => 'required|array',
'documents.*' => 'file|max:20480', // 20MB per file
]);
$attachments = [];
$order = 1;
foreach ($request->file('documents') as $file) {
$attachment = File_Attachment_Model::create_from_upload(
$file,
[
'site_id' => $site_id,
'fileable_type' => 'Project_Model',
'fileable_id' => $project_id,
'fileable_category' => 'document',
'fileable_order' => $order++
]
);
$attachments[] = [
'key' => $attachment->key,
'filename' => $attachment->file_name,
'url' => $attachment->get_url()
];
}
return [
'success' => true,
'files' => $attachments
];
}
GENERATED FILE EXPORT
Generate CSV and attach to report:
#[Route('/reports/{id}/export', method: 'POST')]
#[Auth('Permission::authenticated()')]
#[Ajax_Endpoint]
public static function export_report(Request $request, array $params = [])
{
$report_id = $params['id'];
$site_id = RsxAuth::session()->site_id;
// Generate CSV content
$data = Report_Model::find($report_id)->get_data();
$csv = "Name,Value,Date\n";
foreach ($data as $row) {
$csv .= "{$row->name},{$row->value},{$row->date}\n";
}
// Create attachment from string
$attachment = File_Attachment_Model::create_from_string(
$csv,
"report-{$report_id}-" . date('Y-m-d') . ".csv",
[
'site_id' => $site_id,
'fileable_type' => 'Report_Model',
'fileable_id' => $report_id,
'fileable_category' => 'export',
'fileable_meta' => [
'generated_at' => now()->toIso8601String(),
'generated_by' => RsxAuth::id()
]
]
);
return [
'success' => true,
'download_url' => $attachment->get_download_url()
];
}
IMPORT FROM URL
Download and import external file:
#[Route('/resources/import', method: 'POST')]
#[Auth('Permission::admin()')]
#[Ajax_Endpoint]
public static function import_from_url(Request $request, array $params = [])
{
$site_id = RsxAuth::session()->site_id;
$request->validate([
'url' => 'required|url',
]);
try {
$attachment = File_Attachment_Model::create_from_url(
$request->input('url'),
[
'site_id' => $site_id,
'fileable_category' => 'import',
'fileable_meta' => [
'source_url' => $request->input('url'),
'imported_at' => now()->toIso8601String(),
'imported_by' => RsxAuth::id()
]
]
);
return [
'success' => true,
'key' => $attachment->key,
'filename' => $attachment->file_name
];
} catch (\Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
}
BULK DISK IMPORT
Import files from directory:
#[Route('/admin/import/bulk', method: 'POST')]
#[Auth('Permission::admin()')]
#[Ajax_Endpoint]
public static function bulk_import(Request $request, array $params = [])
{
$site_id = RsxAuth::session()->site_id;
$request->validate([
'directory' => 'required|string',
]);
$directory = $request->input('directory');
if (!is_dir($directory)) {
return [
'success' => false,
'error' => 'Directory not found'
];
}
$files = glob($directory . '/*');
$imported = [];
$errors = [];
foreach ($files as $file_path) {
if (!is_file($file_path)) {
continue;
}
try {
$attachment = File_Attachment_Model::create_from_disk(
$file_path,
[
'site_id' => $site_id,
'fileable_category' => 'bulk_import',
'fileable_meta' => [
'original_path' => $file_path,
'imported_at' => now()->toIso8601String()
]
]
);
$imported[] = [
'filename' => $attachment->file_name,
'key' => $attachment->key
];
} catch (\Exception $e) {
$errors[] = [
'file' => basename($file_path),
'error' => $e->getMessage()
];
}
}
return [
'success' => true,
'imported' => count($imported),
'errors' => count($errors),
'files' => $imported,
'failed' => $errors
];
}
FILE VALIDATION EXAMPLES
Common validation rules:
// Images only, max 5MB
'file' => 'required|image|max:5120'
// PDFs only, max 10MB
'file' => 'required|mimes:pdf|max:10240'
// Documents (PDF, Word, Excel), max 20MB
'file' => 'required|mimes:pdf,doc,docx,xls,xlsx|max:20480'
// Videos, max 100MB
'file' => 'required|mimes:mp4,mov,avi|max:102400'
// Archives, max 50MB
'file' => 'required|mimes:zip,tar,gz|max:51200'
// Multiple files, each max 10MB
'files' => 'required|array',
'files.*' => 'file|max:10240'
ERROR HANDLING
All factory methods throw exceptions on failure:
try {
$attachment = File_Attachment_Model::create_from_upload(
$request->file('file'),
['site_id' => $site_id]
);
return ['success' => true, 'key' => $attachment->key];
} catch (\Exception $e) {
return [
'success' => false,
'error' => $e->getMessage()
];
}
Common exceptions:
- File not found (create_from_disk)
- Network timeout (create_from_url)
- Missing site_id parameter
- Disk write failure
- Invalid file type
RETRIEVING UPLOADED FILES
Get files attached to a model:
// Get all files for a project
$files = File_Attachment_Model::where('fileable_type', 'Project_Model')
->where('fileable_id', $project_id)
->get();
// Get specific category
$documents = File_Attachment_Model::where('fileable_type', 'Project_Model')
->where('fileable_id', $project_id)
->where('fileable_category', 'document')
->orderBy('fileable_order')
->get();
// Get by type meta
$avatar = File_Attachment_Model::where('fileable_type', 'User_Model')
->where('fileable_id', $user_id)
->where('fileable_type_meta', 'profile')
->first();
DELETING FILES
Simple deletion (auto-cleanup handles storage):
$attachment = File_Attachment_Model::find_by_key($key);
$attachment->delete();
// Storage automatically deleted if no other attachments reference it
Delete all files for a model:
File_Attachment_Model::where('fileable_type', 'Project_Model')
->where('fileable_id', $project_id)
->delete();
DRAG-AND-DROP UPLOADS WITH DROPPABLE
Use Droppable to enable drag-and-drop file uploads in JQHTML components.
See droppable.txt for full documentation.
Basic Integration:
Template (My_Uploader.jqhtml):
<Define:My_Uploader tag="div" class="rsx-droppable My_Uploader">
<div class="drop-hint">Drop files here or click to upload</div>
<input type="file" $sid="file_input" style="display: none" />
<ul $sid="file_list"></ul>
</Define:My_Uploader>
JavaScript (My_Uploader.js):
class My_Uploader extends Jqhtml_Component {
on_render() {
// Handle dropped files via Droppable
this.on('file-drop', (_, data) => {
this._upload_files(data.files);
});
// Handle click-to-upload
this.$.on('click', () => this.$sid('file_input').click());
this.$sid('file_input').on('change', (e) => {
this._upload_files(e.target.files);
});
}
async _upload_files(files) {
for (let file of files) {
// Validate
if (file.size > 10 * 1024 * 1024) {
Flash.error(`${file.name} exceeds 10 MB limit`);
continue;
}
// Upload
const formData = new FormData();
formData.append('file', file);
const response = await $.ajax({
url: '/_upload',
type: 'POST',
data: formData,
processData: false,
contentType: false
});
if (response.success) {
this._add_to_list(response.attachment);
}
}
}
_add_to_list(attachment) {
this.$sid('file_list').append(
`<li>${attachment.file_name}</li>`
);
}
}
SCSS (My_Uploader.scss):
.My_Uploader {
border: 2px dashed #ccc;
padding: 20px;
text-align: center;
cursor: pointer;
&.rsx-drop-active {
border-color: #007bff;
background: rgba(0, 123, 255, 0.05);
}
&.rsx-drop-target {
border-style: solid;
background: rgba(0, 123, 255, 0.15);
}
}
SECURITY CONSIDERATIONS
1. Always validate files:
- File type (MIME type validation)
- File size (prevent DoS)
- Filename sanitization (handled automatically)
2. Always require authentication:
- Use #[Auth('Permission::authenticated()')]
- Check user permissions for file access
3. Never trust user input:
- Validate all parameters
- Use Laravel validation rules
4. Rate limiting:
- Add rate limiting to upload endpoints
- Prevent abuse
Example with rate limiting:
#[Route('/files/upload', method: 'POST')]
#[Auth('Permission::authenticated()')]
#[Ajax_Endpoint]
public static function upload(Request $request, array $params = [])
{
// Rate limit: 10 uploads per minute
if (RateLimiter::tooManyAttempts('file-upload:' . RsxAuth::id(), 10)) {
return [
'success' => false,
'error' => 'Too many uploads. Please try again later.'
];
}
RateLimiter::hit('file-upload:' . RsxAuth::id(), 60);
// ... rest of upload logic
}
SEE ALSO
file_upload.txt - Complete file upload system documentation
droppable.txt - Global drag-and-drop file interception system
model.txt - Model system documentation
routing.txt - Route and endpoint documentation
VERSION
RSpade Framework 1.0
Last Updated: 2026-01-15