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:
2026-10-17 20:46:15 -03:00
commit 79d12fa2f4
128 changed files with 17053 additions and 0 deletions
@@ -0,0 +1,120 @@
<?php
namespace App\Http\Controllers\Web;
use App\Http\Controllers\Controller;
use App\Http\Requests\ConsentRequest;
use App\Models\LegalDocument;
use App\Models\UserConsent;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\View\View;
/**
* Web controllers for legal documents and consent management.
*
* Renders the Terms of Use, Privacy Policy and Cookie Policy and records
* the auditable acceptance required by the Brazilian LGPD before the
* user can access the main platform features.
*/
class LegalController extends Controller
{
/**
* Show the current version of a legal document.
*/
public function show(string $type): View
{
$document = LegalDocument::query()
->where('type', $type)
->where('is_current', true)
->firstOrFail();
$locale = app()->getLocale();
return view('legal.show', [
'document' => $document,
'title' => $document->title[$locale] ?? ($document->title['pt_BR'] ?? $document->type),
'body' => $document->body[$locale] ?? ($document->body['pt_BR'] ?? ''),
]);
}
/**
* Show the first-access terms acceptance screen.
*/
public function acceptForm(Request $request): View|RedirectResponse
{
$user = $request->user();
$current = LegalDocument::query()->where('is_current', true)->orderBy('type')->get();
if ($current->isEmpty()) {
return redirect()->route('dashboard');
}
$acceptedIds = $user->consents()->pluck('legal_document_id');
$pending = $current->reject(fn (LegalDocument $doc) => $acceptedIds->contains($doc->id));
if ($pending->isEmpty()) {
return redirect()->route('dashboard');
}
$locale = app()->getLocale();
$documents = $pending->map(fn (LegalDocument $doc): array => [
'type' => $doc->type,
'version' => $doc->version,
'title' => $doc->title[$locale] ?? ($doc->title['pt_BR'] ?? $doc->type),
'body' => $doc->body[$locale] ?? ($doc->body['pt_BR'] ?? ''),
])->values();
return view('legal.accept', [
'documents' => $documents,
]);
}
/**
* Record the acceptance of all current legal documents.
*/
public function accept(ConsentRequest $request): RedirectResponse
{
$user = $request->user();
$documents = LegalDocument::query()->where('is_current', true)->get();
DB::transaction(function () use ($user, $documents, $request): void {
foreach ($documents as $document) {
UserConsent::updateOrCreate(
[
'user_id' => $user->id,
'legal_document_id' => $document->id,
],
[
'accepted_at' => now(),
'ip_address' => $request->ip(),
'user_agent' => (string) $request->userAgent(),
'source' => 'web_form',
],
);
}
});
return redirect()->route('dashboard')->with('status', __('legal.accepted'));
}
/**
* Display the consent audit history of the authenticated user.
*/
public function history(Request $request): View
{
$history = $request->user()
->consents()
->with('document:id,type,version')
->orderByDesc('accepted_at')
->get();
return view('legal.history', [
'history' => $history,
]);
}
}