Initialer Laravel Commit für BetiX
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

This commit is contained in:
2026-04-04 18:01:50 +02:00
commit 0280278978
374 changed files with 65210 additions and 0 deletions

View File

@@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Concerns\ProxiesBackend;
use App\Http\Controllers\Controller;
use App\Services\BackendHttpClient;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Inertia\Inertia;
use Inertia\Response;
class KycController extends Controller
{
use ProxiesBackend;
public function __construct(private readonly BackendHttpClient $client)
{
}
/**
* Show KYC center page with user's documents (from upstream)
*/
public function index(Request $request): Response
{
$docs = [];
try {
$res = $this->client->get($request, '/kyc/documents', [], retry: true);
if ($res->successful()) {
$j = $res->json() ?: [];
$docs = $j['data'] ?? $j['documents'] ?? $j;
}
} catch (\Throwable $e) {
// ignore; page can still render and show empty state
}
return Inertia::render('settings/Kyc', [
'documents' => $docs,
'accepted' => [
'identity' => ['passport','driver_license','id_card','other'],
'address' => ['bank_statement','utility_bill','other'],
'payment' => ['online_banking','other'],
],
'maxUploadMb' => 15,
]);
}
/**
* Upload a new KYC document via upstream (multipart)
*/
public function store(Request $request)
{
$validated = $request->validate([
'category' => ['required', Rule::in(['identity','address','payment'])],
'type' => ['required', Rule::in(['passport','driver_license','id_card','bank_statement','utility_bill','online_banking','other'])],
'file' => ['required','file','max:15360', 'mimetypes:image/jpeg,image/png,image/webp,application/pdf'],
]);
try {
$res = $this->client->postMultipart($request, '/kyc/documents', [
'category' => $validated['category'],
'type' => $validated['type'],
], $request->file('file'), 'file');
if ($res->successful()) {
return back()->with('status', 'Document uploaded');
}
if ($res->clientError()) {
$msg = data_get($res->json(), 'message', 'Invalid request');
return back()->withErrors(['kyc' => $msg]);
}
if ($res->serverError()) {
return back()->withErrors(['kyc' => 'Service temporarily unavailable']);
}
return back()->withErrors(['kyc' => 'API server not reachable']);
} catch (\Throwable $e) {
return back()->withErrors(['kyc' => 'API server not reachable']);
}
}
/**
* Delete a KYC document via upstream
*/
public function destroy(Request $request, int $docId)
{
try {
$res = $this->client->delete($request, "/kyc/documents/{$docId}");
if ($res->successful()) {
return back()->with('status', 'Document deleted');
}
if ($res->clientError()) {
$msg = data_get($res->json(), 'message', 'Invalid request');
return back()->withErrors(['kyc' => $msg]);
}
if ($res->serverError()) {
return back()->withErrors(['kyc' => 'Service temporarily unavailable']);
}
return back()->withErrors(['kyc' => 'API server not reachable']);
} catch (\Throwable $e) {
return back()->withErrors(['kyc' => 'API server not reachable']);
}
}
/**
* Download a document via upstream: prefer redirect to signed URL if provided
*/
public function download(Request $request, int $docId)
{
try {
$res = $this->client->get($request, "/kyc/documents/{$docId}/download", [], retry: false);
if ($res->successful()) {
$j = $res->json();
$url = $j['url'] ?? null;
if ($url) {
return redirect()->away($url);
}
// If upstream responds with binary directly, just passthrough headers/body
$content = $res->body();
$headers = [
'Content-Type' => $res->header('Content-Type', 'application/octet-stream'),
];
return response($content, 200, $headers);
}
if ($res->clientError()) {
$msg = data_get($res->json(), 'message', 'Invalid request');
return back()->withErrors(['kyc' => $msg]);
}
if ($res->serverError()) {
return back()->withErrors(['kyc' => 'Service temporarily unavailable']);
}
return back()->withErrors(['kyc' => 'API server not reachable']);
} catch (\Throwable $e) {
return back()->withErrors(['kyc' => 'API server not reachable']);
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\Settings\PasswordUpdateRequest;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
use Inertia\Response;
class PasswordController extends Controller
{
/**
* Show the user's password settings page.
*/
public function edit(): Response
{
return Inertia::render('settings/Password');
}
/**
* Update the user's password.
*/
public function update(PasswordUpdateRequest $request): RedirectResponse
{
$request->user()->update([
'password' => $request->password,
]);
return back();
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\Settings\ProfileDeleteRequest;
use App\Http\Requests\Settings\ProfileUpdateRequest;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Inertia\Response;
class ProfileController extends Controller
{
/**
* Show the user's profile settings page.
*/
public function edit(Request $request): Response
{
return Inertia::render('settings/Profile', [
'mustVerifyEmail' => $request->user() instanceof MustVerifyEmail,
'status' => $request->session()->get('status'),
]);
}
/**
* Update the user's profile information.
*/
public function update(ProfileUpdateRequest $request): RedirectResponse
{
$request->user()->fill($request->validated());
if ($request->user()->isDirty('email')) {
$request->user()->email_verified_at = null;
}
$request->user()->save();
return to_route('profile.edit');
}
/**
* Delete the user's profile.
*/
public function destroy(ProfileDeleteRequest $request): RedirectResponse
{
$user = $request->user();
Auth::logout();
$user->delete();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
use Inertia\Inertia;
use Inertia\Response;
class SecurityController extends Controller
{
/**
* Render the Security center page.
*/
public function index(Request $request): Response
{
// Provide a light payload; sessions loaded via separate endpoint
return Inertia::render('settings/Security', [
'twoFactorEnabled' => (bool) optional($request->user())->hasEnabledTwoFactorAuthentication(),
]);
}
/**
* List active sessions for the current user (from database sessions table).
*/
public function sessions(Request $request)
{
$userId = Auth::id();
$rows = DB::table('sessions')
->where('user_id', $userId)
->orderByDesc('last_activity')
->limit(100)
->get(['id', 'ip_address', 'user_agent', 'last_activity']);
// Format response
$data = $rows->map(function ($r) use ($request) {
$isCurrent = $request->session()->getId() === $r->id;
return [
'id' => $r->id,
'ip' => $r->ip_address,
'user_agent' => $r->user_agent,
'last_activity' => $r->last_activity,
'current' => $isCurrent,
];
})->values();
return response()->json(['data' => $data]);
}
/**
* Revoke a specific session by ID (current user's session only)
*/
public function revoke(Request $request, string $id)
{
$userId = Auth::id();
$session = DB::table('sessions')->where('id', $id)->first();
if (! $session || $session->user_id != $userId) {
abort(404);
}
// Prevent revoking current session via this endpoint to avoid lockouts
if ($request->session()->getId() === $id) {
return response()->json(['message' => 'Cannot revoke current session via API.'], 422);
}
DB::table('sessions')->where('id', $id)->delete();
return response()->json(['message' => 'Session revoked']);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\Settings\TwoFactorAuthenticationRequest;
use Illuminate\Routing\Controllers\HasMiddleware;
use Illuminate\Routing\Controllers\Middleware;
use Inertia\Inertia;
use Inertia\Response;
use Laravel\Fortify\Features;
class TwoFactorAuthenticationController extends Controller implements HasMiddleware
{
/**
* Get the middleware that should be assigned to the controller.
*/
public static function middleware(): array
{
return Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword')
? [new Middleware('password.confirm', only: ['show'])]
: [];
}
/**
* Show the user's two-factor authentication settings page.
*/
public function show(TwoFactorAuthenticationRequest $request): Response
{
$request->ensureStateIsValid();
return Inertia::render('settings/TwoFactor', [
'twoFactorEnabled' => $request->user()->hasEnabledTwoFactorAuthentication(),
'requiresConfirmation' => Features::optionEnabled(Features::twoFactorAuthentication(), 'confirm'),
]);
}
}