Premier commit

This commit is contained in:
jeremy bayse
2026-02-09 11:27:21 +01:00
commit 89a369964d
114 changed files with 17837 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\LicenseLevel;
class LicenseLevelController extends Controller
{
public function index()
{
$levels = LicenseLevel::all();
return view('admin.license_levels.index', compact('levels'));
}
public function store(Request $request)
{
$request->validate(['name' => 'required|unique:license_levels,name']);
LicenseLevel::create($request->only('name'));
return back()->with('success', 'Niveau de licence ajouté.');
}
public function toggle(LicenseLevel $licenseLevel)
{
$licenseLevel->update(['is_active' => !$licenseLevel->is_active]);
return back()->with('success', 'Statut mis à jour.');
}
public function destroy(LicenseLevel $licenseLevel)
{
$licenseLevel->delete();
return back()->with('success', 'Niveau de licence supprimé.');
}
}

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Link;
use Illuminate\Http\Request;
class LinkController extends Controller
{
public function index()
{
$links = Link::orderBy('order', 'asc')->get();
return view('admin.links.index', compact('links'));
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'url' => 'required|url',
'icon' => 'nullable|string',
'color' => 'nullable|string',
'order' => 'integer',
]);
Link::create($validated);
return back()->with('success', 'Lien ajouté.');
}
public function edit(Link $link)
{
return view('admin.links.edit', compact('link'));
}
public function update(Request $request, Link $link)
{
$validated = $request->validate([
'title' => 'required|string|max:255',
'url' => 'required|url',
'icon' => 'nullable|string',
'color' => 'nullable|string',
'order' => 'integer',
]);
$link->update($validated);
return redirect()->route('admin.links.index')->with('success', 'Lien mis à jour.');
}
public function toggle(Link $link)
{
$link->update(['is_active' => !$link->is_active]);
return back()->with('success', 'Statut mis à jour.');
}
public function destroy(Link $link)
{
$link->delete();
return back()->with('success', 'Lien supprimé.');
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Municipality;
class MunicipalityController extends Controller
{
public function index()
{
$municipalities = Municipality::all();
return view('admin.municipalities.index', compact('municipalities'));
}
public function toggle(Municipality $municipality)
{
$municipality->update(['is_active' => !$municipality->is_active]);
return back()->with('success', 'Statut de la commune mis à jour.');
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Defaults\Password;
class AuthController extends Controller
{
public function showLogin()
{
return view('auth.login');
}
public function login(Request $request)
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials, $request->boolean('remember'))) {
$request->session()->regenerate();
if (!Auth::user()->is_active) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return back()->withErrors(['email' => 'Your account is pending approval by an administrator.']);
}
return redirect()->intended('/dashboard');
}
return back()->withErrors([
'email' => 'The provided credentials do not match our records.',
])->onlyInput('email');
}
public function showRegister()
{
return view('auth.register');
}
public function register(Request $request)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'confirmed', Password::defaults()],
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
'is_active' => false, // Require approval
'role' => 'reader', // Default
]);
Auth::login($user);
// Notify admin in real app
return redirect('/dashboard')->with('status', 'Account created. Wait for approval.');
}
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace App\Http\Controllers;
use App\Models\Contract;
use App\Models\ContractMeta;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
class ContractController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
$contracts = Contract::with('municipality')->latest()->paginate(10);
return view('contracts.index', compact('contracts'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
{
if (!auth()->user()->isManager()) {
abort(403);
}
$municipalities = \App\Models\Municipality::active()->orderBy('name')->get();
$licenseLevels = \App\Models\LicenseLevel::active()->orderBy('name')->get();
return view('contracts.create', compact('municipalities', 'licenseLevels'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
{
if (!auth()->user()->isManager()) {
abort(403);
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'reference' => 'nullable|string|unique:contracts',
'provider' => 'required|string',
'municipality_id' => 'nullable|exists:municipalities,id',
'start_date' => 'required|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'amount' => 'nullable|numeric',
'type' => 'required|string',
'meta' => 'nullable|array', // key-value pairs
]);
$contract = Contract::create($validated);
if ($request->has('meta')) {
foreach ($request->meta as $key => $value) {
if ($value) {
$contract->meta()->create(['key' => $key, 'value' => $value]);
}
}
}
return redirect()->route('contracts.index')
->with('success', 'Contract created successfully.');
}
/**
* Display the specified resource.
*/
public function show(Contract $contract)
{
$contract->load(['meta', 'documents', 'municipality']);
return view('contracts.show', compact('contract'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(Contract $contract)
{
if (!auth()->user()->isManager()) {
abort(403);
}
$contract->load('meta');
$municipalities = \App\Models\Municipality::active()->orderBy('name')->get();
$licenseLevels = \App\Models\LicenseLevel::active()->orderBy('name')->get();
return view('contracts.edit', compact('contract', 'municipalities', 'licenseLevels'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, Contract $contract)
{
if (!auth()->user()->isManager()) {
abort(403);
}
$validated = $request->validate([
'name' => 'required|string|max:255',
'reference' => 'nullable|string|unique:contracts,reference,' . $contract->id,
'provider' => 'required|string',
'municipality_id' => 'nullable|exists:municipalities,id',
'start_date' => 'required|date',
'end_date' => 'nullable|date|after_or_equal:start_date',
'amount' => 'nullable|numeric',
'status' => 'required|string',
]);
$contract->update($validated);
// Handle Meta Data Update
if ($request->has('meta')) {
foreach ($request->meta as $key => $value) {
if ($value) {
$contract->meta()->updateOrCreate(
['key' => $key],
['value' => $value]
);
} else {
// If value is empty, maybe delete? Or just leave null.
// Let's delete if empty to keep clean
$contract->meta()->where('key', $key)->delete();
}
}
}
return redirect()->route('contracts.index')
->with('success', 'Contract updated successfully.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(Contract $contract)
{
if (!auth()->user()->isAdmin()) { // Only admin can delete? Or manager?
abort(403);
}
$contract->delete();
return redirect()->route('contracts.index')
->with('success', 'Contract deleted.');
}
}

View File

@@ -0,0 +1,8 @@
<?php
namespace App\Http\Controllers;
abstract class Controller
{
//
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers;
use App\Services\CortexXdrService;
use Illuminate\Http\Request;
class CortexXdrController extends Controller
{
protected $cortex;
public function __construct(CortexXdrService $cortex)
{
$this->cortex = $cortex;
}
public function index()
{
// Return view effectively immediately with a loading state
return view('cortex.index');
}
public function getData(CortexXdrService $cortexService)
{
try {
$summary = $cortexService->getSummary();
return response()->json($summary);
} catch (\Exception $e) {
return response()->json(['error' => $e->getMessage()], 500);
}
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace App\Http\Controllers;
use App\Models\Contract;
use App\Models\AuditLog;
use App\Models\GlobalSetting;
use App\Models\Link;
use Illuminate\Http\Request;
class DashboardController extends Controller
{
/**
* Display the dashboard.
*/
public function index()
{
$contractStats = [
'total' => Contract::count(),
'active' => Contract::active()->count(),
'expiring_soon' => Contract::expiringSoon(30)->count(),
'expired' => Contract::where('status', 'expired')->count(),
'by_type' => Contract::select('type', \DB::raw('count(*) as count'))->groupBy('type')->get()->keyBy('type'),
];
// Recent logs
$recentLogs = AuditLog::with('user')->latest()->take(10)->get();
// Upcoming Contracts (Timeline)
$upcomingContracts = Contract::whereNotNull('end_date')
->whereDate('end_date', '>=', now())
->orderBy('end_date', 'asc')
->take(6)
->get();
// Dashboard Note
$dashboardNote = GlobalSetting::get('dashboard_note');
// External Links
$links = Link::active()->get();
return view('dashboard', compact('contractStats', 'recentLogs', 'upcomingContracts', 'dashboardNote', 'links'));
}
/**
* Update the dashboard note.
*/
public function updateNote(Request $request)
{
GlobalSetting::set('dashboard_note', $request->input('note'));
return back()->with('success', 'Pense-bête mis à jour.');
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Http\Controllers;
use App\Models\Contract;
use App\Models\Document;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class DocumentController extends Controller
{
/**
* Store a new document for a contract.
*/
public function store(Request $request, Contract $contract)
{
// Simple validation
$request->validate([
'file' => 'required|file|mimes:pdf,docx,jpg,png|max:10240', // 10MB limit
'description' => 'nullable|string|max:255',
]);
if ($request->hasFile('file')) {
$path = $request->file('file')->store('contracts/' . $contract->id, 'public');
$contract->documents()->create([
'filename' => $request->file('file')->getClientOriginalName(),
'path' => $path,
'mime_type' => $request->file('file')->getMimeType(),
'size' => $request->file('file')->getSize(),
'description' => $request->input('description'),
'uploaded_by' => auth()->id(),
]);
return back()->with('success', 'Document uploaded successfully.');
}
return back()->with('error', 'No file uploaded.');
}
/**
* Delete a document.
*/
public function destroy(Document $document)
{
// Check permission (manager or admin or uploader?)
if (!auth()->user()->isManager() && auth()->id() !== $document->uploaded_by) {
abort(403);
}
// Delete from storage
Storage::disk('public')->delete($document->path);
$document->delete();
return back()->with('success', 'Document deleted.');
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MunicipalityController extends Controller
{
public function index()
{
$municipalities = \App\Models\Municipality::active()->orderBy('name')->get();
return view('municipalities.index', compact('municipalities'));
}
public function show(\App\Models\Municipality $municipality)
{
if (!$municipality->is_active) {
abort(404);
}
$municipality->load(['contracts.meta']);
$contracts = $municipality->contracts;
// M365 Statistics
$m365Contracts = $contracts->where('type', 'microsoft_365');
// Fetch all active license levels to initialize stats keys
$licenseLevels = \App\Models\LicenseLevel::active()->pluck('name')->toArray();
$m365Stats = array_fill_keys($licenseLevels, 0);
$m365Stats['Autre'] = 0; // Fallback category
foreach ($m365Contracts as $contract) {
$level = $contract->meta->where('key', 'm365_license_level')->first()?->value;
$quantity = (int) $contract->meta->where('key', 'm365_quantity')->first()?->value ?? 0;
if ($level && array_key_exists($level, $m365Stats)) {
$m365Stats[$level] += $quantity;
} else {
$m365Stats['Autre'] += $quantity;
}
}
return view('municipalities.show', compact('municipality', 'contracts', 'm365Stats'));
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsActive
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (auth()->check() && !auth()->user()->is_active) {
auth()->logout();
return redirect()->route('login')->withErrors(['email' => 'Your account is pending approval by an administrator.']);
}
return $next($request);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureUserIsAdmin
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if (!$request->user() || !$request->user()->isAdmin()) {
abort(403, 'Unauthorized. Admin access only.');
}
return $next($request);
}
}