id(); $table->string('group_name', 255); // Google OU or group name $table->string('match_type', 20) // 'ou', 'group', 'email_domain', 'default' ->default('group'); $table->string('match_value', 255); // the value to match against $table->unsignedSmallInteger('vlan_id'); // VLAN to assign $table->unsignedTinyInteger('max_devices') // max concurrent devices ->default(1); $table->unsignedInteger('session_minutes') // portal session duration ->default(720); $table->unsignedSmallInteger('sort_order') ->default(0); $table->timestamps(); }); // Known MAC addresses → direct VLAN assignment (no portal needed) Schema::create('unifi_known_macs', function (Blueprint $table) { $table->id(); $table->string('mac_address', 17)->unique(); // aa:bb:cc:dd:ee:ff $table->string('device_name', 255)->nullable(); $table->string('device_type', 100)->nullable(); // chromebook, printer, phone, etc. $table->string('owner', 255)->nullable(); // who it belongs to $table->unsignedSmallInteger('vlan_id'); $table->text('notes')->nullable(); $table->timestamps(); }); // Active portal sessions (tracks who's connected via captive portal) Schema::create('unifi_portal_sessions', function (Blueprint $table) { $table->id(); $table->foreignId('user_id')->constrained()->cascadeOnDelete(); $table->string('mac_address', 17); $table->string('device_hostname', 255)->nullable(); $table->string('device_os', 100)->nullable(); // detected OS $table->string('device_type', 100)->nullable(); // detected device category $table->string('ssid', 100)->nullable(); $table->unsignedSmallInteger('vlan_id')->nullable(); $table->string('ap_mac', 17)->nullable(); $table->boolean('is_active')->default(true); $table->timestamp('authorized_at'); $table->timestamp('expires_at')->nullable(); $table->timestamps(); $table->index(['user_id', 'is_active']); $table->index('mac_address'); }); } public function down(): void { Schema::dropIfExists('unifi_portal_sessions'); Schema::dropIfExists('unifi_known_macs'); Schema::dropIfExists('unifi_vlan_mappings'); } };