Files
rspade_system/app/RSpade/man/file_upload_examples.txt
root 77b4d10af8 Refactor filename naming system and apply convention-based renames
Standardize settings file naming and relocate documentation files
Fix code quality violations from rsx:check
Reorganize user_management directory into logical subdirectories
Move Quill Bundle to core and align with Tom Select pattern
Simplify Site Settings page to focus on core site information
Complete Phase 5: Multi-tenant authentication with login flow and site selection
Add route query parameter rule and synchronize filename validation logic
Fix critical bug in UpdateNpmCommand causing missing JavaScript stubs
Implement filename convention rule and resolve VS Code auto-rename conflict
Implement js-sanitizer RPC server to eliminate 900+ Node.js process spawns
Implement RPC server architecture for JavaScript parsing
WIP: Add RPC server infrastructure for JS parsing (partial implementation)
Update jqhtml terminology from destroy to stop, fix datagrid DOM preservation
Add JQHTML-CLASS-01 rule and fix redundant class names
Improve code quality rules and resolve violations
Remove legacy fatal error format in favor of unified 'fatal' error type
Filter internal keys from window.rsxapp output
Update button styling and comprehensive form/modal documentation
Add conditional fly-in animation for modals
Fix non-deterministic bundle compilation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 19:10:02 +00:00

435 lines
14 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();
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
model.txt - Model system documentation
routing.txt - Route and endpoint documentation
VERSION
RSpade Framework 1.0
Last Updated: 2025-11-02