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>
435 lines
14 KiB
Plaintext
Executable File
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
|