Phase 2 partial: Admin authentication foundation

Authentication System Complete (50% of Phase 2):
- Created Admin AuthController with login/logout
- Created AdminAuth middleware for session-based auth
- Registered middleware in bootstrap/app.php
- Configured all admin routes with protection
- Created 4 admin controller files (Dashboard, DocketEntry, Document, Subscriber)

Session-based authentication:
- Stores admin_id and admin_username in session
- Middleware checks for admin_id presence
- Separate from Laravel Breeze User auth
- Uses AdminUser model with auto-hashing passwords

Routes configured:
- Public: GET/POST /admin/login
- Protected: /admin/dashboard, /admin/docket-entries (CRUD), /admin/documents, /admin/subscribers

Remaining work (50%):
- Implement controller logic (7 methods for DocketEntry, etc.)
- Create 9 Vue admin pages (Login, Dashboard, CRUD forms)
- Create AdminSeeder for default admin user
- Configure file storage for PDF uploads
- Test all admin features

See cline_docs/phase2_progress.md for detailed next steps
This commit is contained in:
TheMaddax 2025-12-17 15:58:32 -07:00
parent 06a620406c
commit 844dcf3b73
10 changed files with 419 additions and 5 deletions

View file

@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AdminUser;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Session;
use Inertia\Inertia;
use Inertia\Response;
use Illuminate\Http\RedirectResponse;
class AuthController extends Controller
{
/**
* Show the admin login form.
*/
public function showLogin(): Response
{
return Inertia::render('Admin/Login');
}
/**
* Handle admin login.
*/
public function login(Request $request): RedirectResponse
{
$request->validate([
'username' => 'required|string',
'password' => 'required|string',
]);
$admin = AdminUser::where('username', $request->username)->first();
if (!$admin || !Hash::check($request->password, $admin->password)) {
return back()->withErrors([
'username' => 'The provided credentials do not match our records.',
])->onlyInput('username');
}
// Store admin ID in session
Session::put('admin_id', $admin->id);
Session::put('admin_username', $admin->username);
return redirect()->route('admin.dashboard');
}
/**
* Handle admin logout.
*/
public function logout(Request $request): RedirectResponse
{
Session::forget('admin_id');
Session::forget('admin_username');
Session::flush();
return redirect()->route('admin.login');
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
//
}

View file

@ -0,0 +1,65 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class DocketEntryController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*/
public function show(string $id)
{
//
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
{
//
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
{
//
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
{
//
}
}

View file

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class DocumentController extends Controller
{
//
}

View file

@ -0,0 +1,11 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class SubscriberController extends Controller
{
//
}

View file

@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
use Symfony\Component\HttpFoundation\Response;
class AdminAuth
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (!Session::has('admin_id')) {
return redirect()->route('admin.login');
}
return $next($request);
}
}

View file

@ -16,7 +16,10 @@ return Application::configure(basePath: dirname(__DIR__))
\Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class,
]);
//
// Register admin auth middleware
$middleware->alias([
'admin.auth' => \App\Http\Middleware\AdminAuth::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//

View file

@ -1,11 +1,11 @@
# Active Context - Current Work Status
## Current Task: v2.0 LARAVEL + INERTIA + VUE FOUNDATION COMPLETE ✅
**MAD Lawsuit Website v2.0 - Complete Technology Stack Migration**:
## Current Task: v2.0 PHASE 2 ADMIN DASHBOARD - IN PROGRESS 🚧
**MAD Lawsuit Website v2.0 - Admin Dashboard Development**:
- **v1.0 Status**: Fully operational at https://mad-lawsuit.org (Next.js + Express + React)
- **v2.0 Goal**: Rebuild with Laravel + Inertia + Vue + TypeScript
- **Current Phase**: Foundation complete, home page working, ready for features
- **Status**: TESTED LOCALLY - WEBSITE RENDERING CORRECTLY ✅
- **Current Phase**: Phase 2 - Admin Dashboard (50% complete)
- **Status**: AUTHENTICATION FOUNDATION COMPLETE, NEED CONTROLLERS & VIEWS ⏳
### v2.0 Technology Stack
**Backend**:

View file

@ -0,0 +1,204 @@
# Phase 2: Admin Dashboard Progress
## Status: 50% COMPLETE - Authentication Foundation Done
### ✅ Completed Components
#### 1. Admin Authentication System
**Files Created:**
- `app/Http/Controllers/Admin/AuthController.php` - Login/logout logic
- `app/Http/Middleware/AdminAuth.php` - Session-based auth middleware
- `bootstrap/app.php` - Middleware registered as 'admin.auth'
**AuthController Methods:**
- `showLogin()` - Renders Admin/Login Inertia page
- `login()` - Validates credentials, creates session
- `logout()` - Destroys session, redirects to login
**Session Storage:**
- `admin_id` - Stored in session after successful login
- `admin_username` - Stored for display purposes
- Middleware checks for `admin_id` presence
#### 2. Admin Routes Structure
**File:** `routes/web.php`
**Public Routes (no auth):**
- `GET /admin/login` - Show login form
- `POST /admin/login` - Process login
**Protected Routes (admin.auth middleware):**
- `GET /admin/dashboard` - Main admin dashboard
- `POST /admin/logout` - Logout
- `Resource /admin/docket-entries` - Full CRUD (index, create, store, show, edit, update, destroy)
- `POST /admin/docket-entries/{id}/documents` - Upload document
- `DELETE /admin/documents/{id}` - Delete document
- `GET /admin/subscribers` - List subscribers
- `DELETE /admin/subscribers/{id}` - Delete subscriber
#### 3. Admin Controller Files Created
**All controllers created but need implementation:**
- `app/Http/Controllers/Admin/DashboardController.php` - Empty, needs index() method
- `app/Http/Controllers/Admin/DocketEntryController.php` - Resource controller, needs all 7 methods
- `app/Http/Controllers/Admin/DocumentController.php` - Empty, needs store() and destroy()
- `app/Http/Controllers/Admin/SubscriberController.php` - Empty, needs index() and destroy()
### ⏳ Remaining Work
#### 1. Implement Controller Logic (2-3 hours)
**DashboardController:**
```php
public function index()
{
$stats = [
'total_entries' => DocketEntry::count(),
'total_documents' => Document::count(),
'total_subscribers' => Subscription::where('is_active', true)->count(),
'recent_entries' => DocketEntry::with('documents')->latest()->take(5)->get(),
];
return Inertia::render('Admin/Dashboard', $stats);
}
```
**DocketEntryController (7 methods):**
- `index()` - List all entries with pagination
- `create()` - Show create form
- `store()` - Save new entry
- `show()` - View single entry with documents
- `edit()` - Show edit form
- `update()` - Update entry
- `destroy()` - Delete entry
**DocumentController:**
- `store()` - Upload PDF, save to storage, create DB record
- `destroy()` - Delete file from storage, remove DB record
**SubscriberController:**
- `index()` - List all subscribers with pagination
- `destroy()` - Soft delete (set is_active = false)
#### 2. Create Vue Admin Pages (2-3 hours)
**Login Page:**
- File: `resources/js/Pages/Admin/Login.vue`
- Form with username/password fields
- CSRF token handling
- Error display
- Submit to POST /admin/login
**Dashboard Page:**
- File: `resources/js/Pages/Admin/Dashboard.vue`
- Stats cards (entries, documents, subscribers)
- Recent entries list
- Quick action buttons
- Logout button
**Docket Entry Pages:**
- `resources/js/Pages/Admin/DocketEntries/Index.vue` - List with edit/delete
- `resources/js/Pages/Admin/DocketEntries/Create.vue` - Create form
- `resources/js/Pages/Admin/DocketEntries/Edit.vue` - Edit form
- `resources/js/Pages/Admin/DocketEntries/Show.vue` - View with document upload
**Subscriber Page:**
- `resources/js/Pages/Admin/Subscribers/Index.vue` - List with delete
#### 3. Create Admin Seeder (15 min)
**File:** `database/seeders/AdminSeeder.php`
```php
AdminUser::create([
'username' => 'admin',
'password' => 'password', // Will be auto-hashed by model
]);
```
Run: `php artisan db:seed --class=AdminSeeder`
#### 4. File Storage Configuration (15 min)
**Update:** `config/filesystems.php`
- Ensure 'public' disk is configured
- Create storage link: `php artisan storage:link`
- Documents will be stored in `storage/app/public/documents/`
### Next Session Checklist
**Start Here:**
1. ✅ Read this file first
2. ✅ Review `routes/web.php` to understand route structure
3. ✅ Review `app/Http/Controllers/Admin/AuthController.php` for auth pattern
4. Implement DashboardController->index()
5. Create Admin/Dashboard.vue page
6. Test admin login flow
7. Implement DocketEntryController methods
8. Create docket entry Vue pages
9. Implement DocumentController methods
10. Implement SubscriberController methods
11. Create AdminSeeder
12. Test all admin features
13. Commit Phase 2 complete
### Key Design Decisions
**Authentication:**
- Session-based (not token-based)
- Separate from Laravel Breeze User auth
- AdminUser model with password hashing
- Middleware checks session for admin_id
**File Storage:**
- PDFs stored in `storage/app/public/documents/`
- Accessible via `/storage/documents/{filename}`
- Original filename preserved in DB
- Unique stored filename to prevent conflicts
**Admin UI:**
- Inertia.js for seamless SPA experience
- Vue 3 + TypeScript
- Tailwind CSS for styling
- Match v1.0 admin dashboard design
### Testing Commands
```bash
# Start dev servers
php artisan serve
npm run dev
# Access admin
http://127.0.0.1:8000/admin/login
# Create admin user
php artisan db:seed --class=AdminSeeder
# Test login
Username: admin
Password: password
```
### Files to Create Next Session
1. `app/Http/Controllers/Admin/DashboardController.php` - Implement index()
2. `resources/js/Pages/Admin/Login.vue`
3. `resources/js/Pages/Admin/Dashboard.vue`
4. `resources/js/Pages/Admin/DocketEntries/Index.vue`
5. `resources/js/Pages/Admin/DocketEntries/Create.vue`
6. `resources/js/Pages/Admin/DocketEntries/Edit.vue`
7. `resources/js/Pages/Admin/DocketEntries/Show.vue`
8. `resources/js/Pages/Admin/Subscribers/Index.vue`
9. `database/seeders/AdminSeeder.php`
### Estimated Time Remaining
- Controller implementation: 2-3 hours
- Vue pages: 2-3 hours
- Testing & fixes: 1 hour
- **Total: 5-7 hours**
### Current Git Status
- Phase 1 committed and pushed ✅
- Phase 2 authentication foundation ready to commit
- Next commit will be "Phase 2 partial: Admin authentication foundation"

View file

@ -18,6 +18,30 @@ Route::get('/api/unsubscribe/{token}', [SubscriptionController::class, 'unsubscr
// Document download route
Route::get('/api/documents/{id}/download', [DocumentController::class, 'download'])->name('api.documents.download');
// Admin routes
Route::prefix('admin')->name('admin.')->group(function () {
// Admin login routes (no auth required)
Route::get('/login', [\App\Http\Controllers\Admin\AuthController::class, 'showLogin'])->name('login');
Route::post('/login', [\App\Http\Controllers\Admin\AuthController::class, 'login'])->name('login.post');
// Protected admin routes
Route::middleware('admin.auth')->group(function () {
Route::get('/dashboard', [\App\Http\Controllers\Admin\DashboardController::class, 'index'])->name('dashboard');
Route::post('/logout', [\App\Http\Controllers\Admin\AuthController::class, 'logout'])->name('logout');
// Docket entry routes
Route::resource('docket-entries', \App\Http\Controllers\Admin\DocketEntryController::class);
// Document routes
Route::post('/docket-entries/{docketEntry}/documents', [\App\Http\Controllers\Admin\DocumentController::class, 'store'])->name('documents.store');
Route::delete('/documents/{document}', [\App\Http\Controllers\Admin\DocumentController::class, 'destroy'])->name('documents.destroy');
// Subscriber routes
Route::get('/subscribers', [\App\Http\Controllers\Admin\SubscriberController::class, 'index'])->name('subscribers.index');
Route::delete('/subscribers/{subscription}', [\App\Http\Controllers\Admin\SubscriberController::class, 'destroy'])->name('subscribers.destroy');
});
});
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');