Add notification system with Rsx_Throttle utility

Add action log feature, fix Bootstrap variables and DataGrid styling

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-01-29 17:44:31 +00:00
parent b8a454bca0
commit f48cda006a
21 changed files with 444 additions and 121 deletions

View File

@@ -33,7 +33,7 @@ use App\RSpade\Core\Models\User_Model;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: _api_keys
*
* @property int $id
@@ -53,7 +53,7 @@ use App\RSpade\Core\Models\User_Model;
* @mixin \Eloquent
*/
class Api_Key_Model extends Rsx_System_Model_Abstract
{
{
protected $table = '_api_keys';
public static $enums = [];

View File

@@ -30,42 +30,50 @@ use App\RSpade\Core\Files\File_Storage_Model;
* provides the basic structure for categorizing uploaded files.
*/
/**
* _AUTO_GENERATED_
* @property integer $id
* @property string $key
* @property integer $file_storage_id
* @property string $file_name
* @property string $file_extension
* @property integer $file_type_id
* @property integer $width
* @property integer $height
* @property integer $duration
* @property boolean $is_animated
* @property integer $frame_count
* @property integer $fileable_type
* @property integer $fileable_id
* @property string $fileable_category
* @property string $fileable_type_meta
* @property integer $fileable_order
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2026-01-29 08:25:50
* Table: _file_attachments
*
* @property int $id
* @property mixed $key
* @property int $file_storage_id
* @property mixed $file_name
* @property mixed $file_extension
* @property int $file_type_id
* @property int $width
* @property int $height
* @property int $duration
* @property bool $is_animated
* @property int $frame_count
* @property int $fileable_type
* @property int $fileable_id
* @property mixed $fileable_category
* @property mixed $fileable_type_meta
* @property int $fileable_order
* @property string $fileable_meta
* @property integer $site_id
* @property string $session_id
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property integer $created_by
* @property integer $updated_by
* @method static mixed file_type_id_enum()
* @method static mixed file_type_id_enum_select()
* @method static mixed file_type_id_enum_ids()
* @property-read mixed $file_type_id_constant
* @property-read mixed $file_type_id_label
* @property int $site_id
* @property mixed $session_id
* @property string $created_at
* @property string $updated_at
* @property int $created_by
* @property int $updated_by
*
* @property-read string $file_type_id__label
* @property-read string $file_type_id__constant
*
* @method static array file_type_id__enum() Get all enum definitions with full metadata
* @method static array file_type_id__enum_select() Get selectable items for dropdowns
* @method static array file_type_id__enum_labels() Get simple id => label map
* @method static array file_type_id__enum_ids() Get array of all valid enum IDs
*
* @mixin \Eloquent
*/
class File_Attachment_Model extends Rsx_Site_Model_Abstract
{
/** __AUTO_GENERATED: */
{
/**
* _AUTO_GENERATED_ Enum constants
*/
const FILE_TYPE_IMAGE = 1;
const FILE_TYPE_ANIMATED_IMAGE = 2;
const FILE_TYPE_VIDEO = 3;
@@ -73,6 +81,9 @@ class File_Attachment_Model extends Rsx_Site_Model_Abstract
const FILE_TYPE_TEXT = 5;
const FILE_TYPE_DOCUMENT = 6;
const FILE_TYPE_OTHER = 7;
/** __AUTO_GENERATED: */
/** __/AUTO_GENERATED */
/**

View File

@@ -16,7 +16,7 @@ use App\RSpade\Core\Database\Models\Rsx_Model_Abstract;
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: _file_storage
*
* @property int $id
@@ -30,7 +30,7 @@ use App\RSpade\Core\Database\Models\Rsx_Model_Abstract;
* @mixin \Eloquent
*/
class File_Storage_Model extends Rsx_Model_Abstract
{
{
// Required static properties from parent abstract class
public static $enums = [];
public static $rel = [];

View File

@@ -14,7 +14,7 @@ use App\RSpade\Core\Models\Region_Model;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: countries
*
* @property int $id
@@ -32,7 +32,7 @@ use App\RSpade\Core\Models\Region_Model;
* @mixin \Eloquent
*/
class Country_Model extends Rsx_Model_Abstract
{
{
public static $enums = [];
protected $table = 'countries';

View File

@@ -12,7 +12,7 @@ use App\RSpade\Core\Database\Models\Rsx_System_Model_Abstract;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: ip_addresses
*
* @property int $id
@@ -30,7 +30,7 @@ use App\RSpade\Core\Database\Models\Rsx_System_Model_Abstract;
* @mixin \Eloquent
*/
class Ip_Address_Model extends Rsx_System_Model_Abstract
{
{
/**
* Enum field definitions
* @var array

View File

@@ -24,7 +24,7 @@ use App\RSpade\Core\Session\Session;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: login_users
*
* @property int $id
@@ -61,7 +61,7 @@ class Login_User_Model extends Rsx_Model_Abstract implements
\Illuminate\Contracts\Auth\Authenticatable,
\Illuminate\Contracts\Auth\Access\Authorizable,
\Illuminate\Contracts\Auth\CanResetPassword
{
{
/**
* _AUTO_GENERATED_ Enum constants
*/

View File

@@ -14,7 +14,7 @@ use App\RSpade\Core\Models\Country_Model;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: regions
*
* @property int $id
@@ -31,7 +31,7 @@ use App\RSpade\Core\Models\Country_Model;
* @mixin \Eloquent
*/
class Region_Model extends Rsx_Model_Abstract
{
{
public static $enums = [];
protected $table = 'regions';

View File

@@ -14,12 +14,13 @@ use App\RSpade\Core\Models\User_Model;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: sites
*
* @property int $id
* @property mixed $slug
* @property mixed $name
* @property mixed $timezone
* @property bool $is_enabled
* @property string $deleted_at
* @property string $created_at
@@ -31,7 +32,7 @@ use App\RSpade\Core\Models\User_Model;
* @mixin \Eloquent
*/
class Site_Model extends Rsx_Model_Abstract
{
{
use SoftDeletes;
/**

View File

@@ -12,7 +12,7 @@ use App\RSpade\Core\Database\Models\Rsx_Site_Model_Abstract;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: user_invites
*
* @property int $id
@@ -28,7 +28,7 @@ use App\RSpade\Core\Database\Models\Rsx_Site_Model_Abstract;
* @mixin \Eloquent
*/
class User_Invite_Model extends Rsx_Site_Model_Abstract
{
{
/**
* Enum field definitions
* @var array

View File

@@ -23,41 +23,46 @@ use App\RSpade\Core\Models\User_Profile_Model;
* See: php artisan rsx:man acls
*/
/**
* _AUTO_GENERATED_
* @property integer $id
* @property integer $login_user_id
* @property integer $site_id
* @property string $first_name
* @property string $last_name
* @property string $phone
* @property integer $role_id
* @property boolean $is_enabled
* @property integer $user_role_id
* @property string $email
* @property \Carbon\Carbon $deleted_at
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property integer $created_by
* @property integer $updated_by
* @property integer $deleted_by
* @property string $invite_code
* @property \Carbon\Carbon $invite_accepted_at
* @property \Carbon\Carbon $invite_expires_at
* @method static mixed role_id_enum()
* @method static mixed role_id_enum_select()
* @method static mixed role_id_enum_ids()
* @property-read mixed $role_id_constant
* @property-read mixed $role_id_label
* @property-read mixed $role_id_permissions
* @property-read mixed $role_id_can_admin_roles
* @property-read mixed $role_id_selectable
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2026-01-29 08:25:50
* Table: users
*
* @property int $id
* @property int $login_user_id
* @property int $site_id
* @property mixed $first_name
* @property mixed $last_name
* @property mixed $phone
* @property int $role_id
* @property bool $is_enabled
* @property int $user_role_id
* @property mixed $email
* @property string $deleted_at
* @property string $created_at
* @property string $updated_at
* @property int $created_by
* @property int $updated_by
* @property int $deleted_by
* @property mixed $invite_code
* @property string $invite_accepted_at
* @property string $invite_expires_at
*
* @property-read string $role_id__label
* @property-read string $role_id__constant
*
* @method static array role_id__enum() Get all enum definitions with full metadata
* @method static array role_id__enum_select() Get selectable items for dropdowns
* @method static array role_id__enum_labels() Get simple id => label map
* @method static array role_id__enum_ids() Get array of all valid enum IDs
*
* @mixin \Eloquent
*/
class User_Model extends Rsx_Site_Model_Abstract
{
/** __AUTO_GENERATED: */
{
/**
* _AUTO_GENERATED_ Enum constants
*/
const ROLE_DEVELOPER = 100;
const ROLE_ROOT_ADMIN = 200;
const ROLE_SITE_OWNER = 300;
@@ -66,6 +71,9 @@ class User_Model extends Rsx_Site_Model_Abstract
const ROLE_USER = 600;
const ROLE_VIEWER = 700;
const ROLE_DISABLED = 800;
/** __AUTO_GENERATED: */
/** __/AUTO_GENERATED */
// =========================================================================

View File

@@ -7,7 +7,7 @@ use App\RSpade\Core\Models\User_Model;
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: user_permissions
*
* @property int $id
@@ -22,7 +22,7 @@ use App\RSpade\Core\Models\User_Model;
* @mixin \Eloquent
*/
class User_Permission_Model extends Rsx_Model_Abstract
{
{
protected $table = 'user_permissions';
protected $fillable = []; // No mass assignment - always explicit

View File

@@ -35,7 +35,7 @@ use App\RSpade\Core\Models\User_Model;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: user_profiles
*
* @property int $id
@@ -51,7 +51,7 @@ use App\RSpade\Core\Models\User_Model;
* @mixin \Eloquent
*/
class User_Profile_Model extends Rsx_Model_Abstract
{
{
/**
* The table associated with the model
*

View File

@@ -11,33 +11,44 @@ use App\RSpade\Core\Database\Models\Rsx_Model_Abstract;
* and two-factor authentication via email or SMS.
*/
/**
* _AUTO_GENERATED_
* @property integer $id
* @property string $email
* @property string $verification_code
* @property integer $verification_type_id
* @property \Carbon\Carbon $verified_at
* @property \Carbon\Carbon $expires_at
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property integer $created_by
* @property integer $updated_by
* @method static mixed verification_type_id_enum()
* @method static mixed verification_type_id_enum_select()
* @method static mixed verification_type_id_enum_ids()
* @property-read mixed $verification_type_id_constant
* @property-read mixed $verification_type_id_label
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2026-01-29 08:25:50
* Table: user_verifications
*
* @property int $id
* @property mixed $email
* @property mixed $verification_code
* @property int $verification_type_id
* @property string $verified_at
* @property string $expires_at
* @property string $created_at
* @property string $updated_at
* @property int $created_by
* @property int $updated_by
*
* @property-read string $verification_type_id__label
* @property-read string $verification_type_id__constant
*
* @method static array verification_type_id__enum() Get all enum definitions with full metadata
* @method static array verification_type_id__enum_select() Get selectable items for dropdowns
* @method static array verification_type_id__enum_labels() Get simple id => label map
* @method static array verification_type_id__enum_ids() Get array of all valid enum IDs
*
* @mixin \Eloquent
*/
class User_Verification_Model extends Rsx_Model_Abstract
{
/** __AUTO_GENERATED: */
{
/**
* _AUTO_GENERATED_ Enum constants
*/
const VERIFICATION_TYPE_EMAIL = 1;
const VERIFICATION_TYPE_SMS = 2;
const VERIFICATION_TYPE_EMAIL_RECOVERY = 3;
const VERIFICATION_TYPE_SMS_RECOVERY = 4;
/** __AUTO_GENERATED: */
/** __/AUTO_GENERATED */
/**

View File

@@ -17,11 +17,11 @@ use App\RSpade\Core\Database\Models\Rsx_Site_Model_Abstract;
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: _search_indexes
*
* @property int $id
* @property mixed $indexable_type
* @property int $indexable_type
* @property int $indexable_id
* @property string $content
* @property array $metadata
@@ -37,7 +37,7 @@ use App\RSpade\Core\Database\Models\Rsx_Site_Model_Abstract;
* @mixin \Eloquent
*/
class Search_Index_Model extends Rsx_Site_Model_Abstract
{
{
// Required static properties from parent abstract class
public static $enums = [];
public static $rel = [];

View File

@@ -41,7 +41,7 @@ use App\RSpade\Core\Session\User_Agent;
*/
/**
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2025-12-26 02:43:29
* Generated on: 2026-01-29 08:25:50
* Table: _sessions
*
* @property int $id
@@ -63,7 +63,7 @@ use App\RSpade\Core\Session\User_Agent;
* @mixin \Eloquent
*/
class Session extends Rsx_System_Model_Abstract
{
{
// Enum definitions (required by abstract parent)
public static $enums = [];

View File

@@ -0,0 +1,105 @@
<?php
namespace App\RSpade\Core\Throttle;
use Illuminate\Support\Facades\DB;
use App\RSpade\Core\Session\Session;
/**
* Rsx_Throttle - Rate limiting utility for periodic per-user actions
*
* Provides atomic check-and-execute functionality for throttling operations.
* Database implementation is hidden - API exposes only check().
*
* Use cases:
* - Periodic maintenance tasks (notification expiry, cache refresh)
* - Rate-limited background calculations
* - Any operation that should run at most once per interval per user
*
* @example
* if (Rsx_Throttle::check('NOTIFICATION_EXPIRE_CHECK', $user_id, minutes: 30)) {
* Notification::expire_old();
* }
*/
class Rsx_Throttle
{
/**
* Check if action should run and atomically mark as executed
*
* First-time calls return true and create record.
* Subsequent calls return true only after interval has passed.
*
* @param string $action_key Unique action identifier (use SCREAMING_SNAKE_CASE)
* @param int $user_id Login user ID (authentication identity)
* @param int $minutes Throttle interval in minutes (default 30)
* @return bool True if action should execute now, false if throttled
*/
public static function check(string $action_key, int $user_id, int $minutes = 30): bool
{
$site_id = Session::get_site_id();
$threshold = now()->subMinutes($minutes);
// TODO: Future optimization - use row-level locking with SELECT ... FOR UPDATE
// instead of table lock for better concurrency per user/site
// Atomic check-and-update using table lock
DB::statement('LOCK TABLES _throttle WRITE');
try {
$record = DB::table('_throttle')
->where('site_id', $site_id)
->where('user_id', $user_id)
->where('action_key', $action_key)
->first();
if (!$record) {
// First time - create record and return true
DB::table('_throttle')->insert([
'site_id' => $site_id,
'user_id' => $user_id,
'action_key' => $action_key,
'last_executed_at' => now(),
'created_at' => now(),
'updated_at' => now(),
]);
return true;
}
if ($record->last_executed_at < $threshold) {
// Interval passed - update and return true
DB::table('_throttle')
->where('id', $record->id)
->update([
'last_executed_at' => now(),
'updated_at' => now(),
]);
return true;
}
// Still within throttle interval
return false;
} finally {
DB::statement('UNLOCK TABLES');
}
}
/**
* Force reset throttle for an action
*
* Useful for testing or forcing immediate re-execution.
*
* @param string $action_key Action identifier to reset
* @param int $user_id Login user ID
*/
public static function reset(string $action_key, int $user_id): void
{
$site_id = Session::get_site_id();
DB::table('_throttle')
->where('site_id', $site_id)
->where('user_id', $user_id)
->where('action_key', $action_key)
->delete();
}
}

View File

@@ -5,29 +5,41 @@ namespace App\RSpade\Lib\Flash;
use App\RSpade\Core\Database\Models\Rsx_Model_Abstract;
/**
* _AUTO_GENERATED_
* @property integer $id
* @property integer $session_id
* @property integer $type_id
* _AUTO_GENERATED_ Database type hints - do not edit manually
* Generated on: 2026-01-29 08:25:50
* Table: _flash_alerts
*
* @property int $id
* @property int $session_id
* @property int $type_id
* @property string $message
* @property \Carbon\Carbon $created_at
* @property integer $created_by
* @property integer $updated_by
* @property \Carbon\Carbon $updated_at
* @method static mixed type_id_enum()
* @method static mixed type_id_enum_select()
* @method static mixed type_id_enum_ids()
* @property-read mixed $type_id_constant
* @property-read mixed $type_id_label
* @property string $created_at
* @property int $created_by
* @property int $updated_by
* @property string $updated_at
*
* @property-read string $type_id__label
* @property-read string $type_id__constant
*
* @method static array type_id__enum() Get all enum definitions with full metadata
* @method static array type_id__enum_select() Get selectable items for dropdowns
* @method static array type_id__enum_labels() Get simple id => label map
* @method static array type_id__enum_ids() Get array of all valid enum IDs
*
* @mixin \Eloquent
*/
class Flash_Alert_Model extends Rsx_Model_Abstract
{
/** __AUTO_GENERATED: */
{
/**
* _AUTO_GENERATED_ Enum constants
*/
const TYPE_SUCCESS = 1;
const TYPE_ERROR = 2;
const TYPE_INFO = 3;
const TYPE_WARNING = 4;
/** __AUTO_GENERATED: */
/** __/AUTO_GENERATED */
// Enum constants (auto-generated by rsx:migrate:document_models)