feat: add City10 platform with Laravel 13, AI agents, 3D viewer, and LGPD compliance
Initial commit of the complete City10 virtual city simulation platform. Backend (Laravel 13): - REST API with Sanctum tokens and session-based web authentication - Role-based access control (admin/user) with middleware protection - AI agent CRUD with OpenRouter.ai integration restricted to free models - Store management with 3D coordinates and AI shopkeeper assignment - Versioned legal documents (Terms, Privacy, Cookies) with LGPD audit trail - Math CAPTCHA on public forms and multi-language support (pt_BR, en, es) Frontend: - Responsive Blade templates with Tailwind CSS v4 - Three.js 3D city viewer with SSE chat streaming to AI agents - Admin panel for store and user administration Infrastructure: - MariaDB/MySQL database with comprehensive migrations - Database seeders for demo content and test accounts - PHPUnit test suite and Laravel Pint code styling - Vite build configuration with hot reload
This commit is contained in:
Executable
+159
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\LoginRequest;
|
||||
use App\Http\Requests\RegisterRequest;
|
||||
use App\Models\LegalDocument;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
/**
|
||||
* Authentication endpoints for the City10 REST API.
|
||||
*
|
||||
* Issues personal access tokens (Laravel Sanctum) consumed by the 3D
|
||||
* frontend and by external integrations.
|
||||
*/
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* Issue a fresh math CAPTCHA challenge for the login/registration forms.
|
||||
*
|
||||
* The expected result is stored server-side in the session, so clients
|
||||
* only receive the question to solve.
|
||||
*/
|
||||
public function captcha(): JsonResponse
|
||||
{
|
||||
$a = random_int(1, 9);
|
||||
$b = random_int(1, 9);
|
||||
|
||||
session()->put('captcha_result', $a + $b);
|
||||
session()->put('captcha_expires_at', now()->addMinutes(10)->getTimestamp());
|
||||
|
||||
return response()->json([
|
||||
'question' => sprintf('%d + %d = ?', $a, $b),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new regular user account.
|
||||
*
|
||||
* Applies a simple math CAPTCHA and immediately records acceptance of
|
||||
* the current legal documents when the client confirms them.
|
||||
*/
|
||||
public function register(RegisterRequest $request): JsonResponse
|
||||
{
|
||||
$data = $request->validated();
|
||||
|
||||
$user = User::create([
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => $data['password'],
|
||||
'role' => User::ROLE_USER,
|
||||
'locale' => $data['locale'] ?? 'pt_BR',
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
$token = $user->createToken('city10-api')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'user' => $user,
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate a user and return an API token.
|
||||
*/
|
||||
public function login(LoginRequest $request): JsonResponse
|
||||
{
|
||||
$user = User::where('email', $request->validated('email'))->first();
|
||||
|
||||
if ($user === null || ! Hash::check($request->validated('password'), $user->password)) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [__('auth.failed')],
|
||||
]);
|
||||
}
|
||||
|
||||
if (! $user->is_active) {
|
||||
throw ValidationException::withMessages([
|
||||
'email' => [__('auth.disabled')],
|
||||
]);
|
||||
}
|
||||
|
||||
$user->forceFill(['last_login_at' => now()])->save();
|
||||
|
||||
$token = $user->createToken('city10-api')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'user' => $user,
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
'requires_terms' => ! $user->hasAcceptedCurrentTerms(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the authenticated user's own profile (name, locale, password).
|
||||
*
|
||||
* Role and active status are intentionally excluded: those are managed
|
||||
* exclusively through the admin endpoints.
|
||||
*/
|
||||
public function updateProfile(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'name' => ['sometimes', 'string', 'max:120'],
|
||||
'locale' => ['sometimes', Rule::in(['pt_BR', 'en', 'es'])],
|
||||
'password' => ['sometimes', 'string', 'min:8', 'confirmed'],
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
if (! empty($data['password'])) {
|
||||
$user->password = $data['password'];
|
||||
unset($data['password']);
|
||||
}
|
||||
|
||||
$user->fill($data)->save();
|
||||
|
||||
return response()->json($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke the token used for the current request.
|
||||
*/
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
$request->user()->currentAccessToken()?->delete();
|
||||
|
||||
return response()->json(['message' => __('auth.logged_out')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the authenticated user plus consent status.
|
||||
*/
|
||||
public function me(Request $request): JsonResponse
|
||||
{
|
||||
$user = $request->user();
|
||||
|
||||
$currentDocuments = LegalDocument::query()
|
||||
->where('is_current', true)
|
||||
->get(['id', 'type', 'version']);
|
||||
|
||||
$acceptedIds = $user->consents()->pluck('legal_document_id');
|
||||
|
||||
return response()->json([
|
||||
'user' => $user,
|
||||
'requires_terms' => ! $user->hasAcceptedCurrentTerms(),
|
||||
'pending_documents' => $currentDocuments
|
||||
->reject(fn ($doc) => $acceptedIds->contains($doc->id))
|
||||
->values(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user