Files
rspade_system/app/RSpade/man/model_fetch.txt
root f6fac6c4bc Fix bin/publish: copy docs.dist from project root
Fix bin/publish: use correct .env path for rspade_system
Fix bin/publish script: prevent grep exit code 1 from terminating script

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-21 02:08:33 +00:00

345 lines
11 KiB
Plaintext
Executable File

NAME
model_fetch - RSX Ajax ORM with secure model fetching from JavaScript
SYNOPSIS
Secure, controlled access to ORM models from JavaScript with explicit opt-in
DESCRIPTION
The RSX model fetch system allows JavaScript to securely access ORM models
through an explicit opt-in mechanism. Unlike Laravel's default API routes
which expose all model data, RSX requires each model to implement its own
fetch() method with authorization and data filtering.
Key differences from Laravel:
- Laravel: API routes with resource controllers expose all fields
- RSX: Each model implements custom fetch() with authorization
Benefits:
- Explicit security control per model
- No mass exposure of sensitive data
- Individual authorization checks for each record
- Automatic JavaScript stub generation
SECURITY MODEL
Explicit Opt-In:
Models must deliberately implement fetch() with the attribute.
No models are fetchable by default.
Individual Authorization:
Each model controls who can fetch its records.
Authorization checked for every single record.
Data Filtering:
Models can filter sensitive fields before returning data.
Complete control over what data JavaScript receives.
No Mass Fetching:
Framework splits array requests into individual fetch calls.
Prevents bulk data extraction without individual authorization.
IMPLEMENTING FETCHABLE MODELS
Required Components:
1. Override static fetch() method
2. Add #[Ajax_Endpoint_Model_Fetch] attribute
3. Accept exactly one parameter: $id (single ID only)
4. Implement authorization checks
5. Return model object or false
Basic Implementation:
use Ajax_Endpoint_Model_Fetch;
class Product_Model extends Rsx_Model
{
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
// Authorization check
if (!RsxAuth::check()) {
return false;
}
// Fetch single record
$model = static::find($id);
return $model ?: false;
}
}
Advanced Authorization:
class Order_Model extends Rsx_Model
{
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
$user = RsxAuth::user();
if (!$user) {
return false;
}
$order = static::find($id);
if (!$order) {
return false;
}
// Only allow access to own orders or admin users
if ($order->user_id !== $user->id && !$user->is_admin) {
return false;
}
return $order;
}
}
Data Filtering:
class User_Model extends Rsx_Model
{
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
if (!RsxAuth::check()) {
return false;
}
$user = static::find($id);
if (!$user) {
return false;
}
// Remove sensitive fields
unset($user->password_hash);
unset($user->remember_token);
unset($user->email_verification_token);
return $user;
}
}
JAVASCRIPT USAGE
Single Record Fetching:
// Fetch single record
const product = await Product_Model.fetch(1);
if (product) {
console.log(product.name);
console.log(product.price);
}
// Handle fetch failure
const order = await Order_Model.fetch(999);
if (!order) {
console.log('Order not found or access denied');
}
Multiple Record Fetching:
// Framework automatically splits array into individual calls
const products = await Product_Model.fetch([1, 2, 3]);
products.forEach(product => {
if (product) {
console.log(product.name);
}
});
// Mixed results (some succeed, some fail authorization)
const orders = await Order_Model.fetch([101, 102, 103]);
const validOrders = orders.filter(order => order !== false);
Error Handling:
try {
const user = await User_Model.fetch(userId);
if (user) {
updateUserInterface(user);
} else {
showAccessDeniedMessage();
}
} catch (error) {
console.error('Fetch failed:', error);
showErrorMessage();
}
ARRAY HANDLING
Framework Behavior:
When JavaScript passes an array to fetch(), the framework:
1. Splits array into individual IDs
2. Calls fetch() once for each ID
3. Collects results maintaining array order
4. Returns array with same length (false for failed fetches)
Implementation Rules:
- NEVER use is_array($id) checks in fetch() method
- Always handle exactly one ID parameter
- Framework handles array splitting automatically
- Results maintain original array order
Example Results:
// JavaScript call
const results = await Product_Model.fetch([1, 2, 999]);
// Results array (999 not found or unauthorized)
[
{id: 1, name: "Product A"}, // Successful fetch
{id: 2, name: "Product B"}, // Successful fetch
false // Failed fetch
]
STUB GENERATION
Automatic JavaScript Stubs:
The framework automatically generates JavaScript stub classes
for models with #[Ajax_Endpoint_Model_Fetch] attributes.
Stub Class Generation:
// Generated stub for Product_Model
class Product_Model {
static async fetch(id) {
// Generated implementation calls PHP fetch() method
return await Rsx._internal_api_call('Product_Model', 'fetch', {id});
}
}
Bundle Integration:
Stubs are automatically included in JavaScript bundles when
models are discovered in the bundle's include paths.
AUTHORIZATION PATTERNS
User-Specific Access:
class User_Profile_Model extends Rsx_Model
{
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
$current_user = RsxAuth::user();
// Users can only fetch their own profile
if (!$current_user || $current_user->id != $id) {
return false;
}
return static::find($id);
}
}
Role-Based Access:
class Admin_Report_Model extends Rsx_Model
{
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
$user = RsxAuth::user();
// Only admin users can fetch reports
if (!$user || !$user->hasRole('admin')) {
return false;
}
return static::find($id);
}
}
Public Data Access:
class Public_Article_Model extends Rsx_Model
{
#[Ajax_Endpoint_Model_Fetch]
public static function fetch($id)
{
// Public articles can be fetched by anyone
$article = static::find($id);
// But only return published articles
if ($article && $article->status === 'published') {
return $article;
}
return false;
}
}
BASE MODEL PROTECTION
Default Behavior:
The base Rsx_Model::fetch() method throws an exception with
clear instructions on how to implement fetch() in your model.
Exception Message:
"Model MyModel does not implement fetch() method. To enable Ajax
fetching, add #[Ajax_Endpoint_Model_Fetch] attribute and implement
static fetch($id) method with authorization checks."
Purpose:
- Prevents accidental exposure of model data
- Forces developers to explicitly handle security
- Provides clear implementation guidance
- Ensures no models are fetchable by default
TESTING FETCH METHODS
PHP Testing:
// Test authorization
$user = User_Model::factory()->create();
RsxAuth::login($user);
$product = Product_Model::fetch(1);
$this->assertNotFalse($product);
RsxAuth::logout();
$product = Product_Model::fetch(1);
$this->assertFalse($product);
JavaScript Testing (via rsx:debug):
php artisan rsx:debug /demo --eval="Product_Model.fetch(1).then(r => console.log(r))"
COMMON PATTERNS
Soft Delete Handling:
public static function fetch($id)
{
// Only return non-deleted records
$model = static::where('id', $id)->whereNull('deleted_at')->first();
return $model ?: false;
}
Relationship Preloading:
public static function fetch($id)
{
$model = static::with(['category', 'tags'])->find($id);
return $model ?: false;
}
Conditional Field Removal:
public static function fetch($id)
{
$model = static::find($id);
if (!$model) return false;
$user = RsxAuth::user();
if (!$user || !$user->is_admin) {
// Remove admin-only fields for non-admin users
unset($model->internal_notes);
unset($model->cost_price);
}
return $model;
}
TROUBLESHOOTING
Model Not Fetchable:
- Verify #[Ajax_Endpoint_Model_Fetch] attribute present
- Check fetch() method is static and public
- Ensure method accepts single $id parameter
- Verify model included in bundle manifest
Authorization Failures:
- Check RsxAuth::check() and RsxAuth::user() values
- Verify authorization logic in fetch() method
- Test with different user roles and permissions
- Use rsx_dump_die() to debug authorization flow
JavaScript Stub Missing:
- Verify model discovered during manifest build
- Check bundle includes model's directory
- Ensure bundle compiles without errors
- Confirm JavaScript bundle loads in browser
Array Handling Issues:
- Never use is_array($id) in fetch() method
- Framework handles array splitting automatically
- Check for typos in model class names
- Verify all IDs in array are valid integers
SEE ALSO
controller - Internal API attribute patterns
manifest_api - Model discovery and stub generation
coding_standards - Security patterns and authorization