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,109 @@
<?php
namespace App\Http\Controllers\Web\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreRequest;
use App\Models\AiAgent;
use App\Models\Store;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
/**
* Administrative store management (admin only).
*
* Provides the server-rendered CRUD screens where administrators create
* and edit stores, defining their exact 3D placement in the virtual city
* through a Three.js position picker.
*/
class StoreAdminController extends Controller
{
/**
* Paginated listing of every store with search and type filtering.
*/
public function index(Request $request): View
{
$query = Store::query()->with(['owner:id,name', 'agent:id,name'])->orderBy('name');
if ($search = $request->query('search')) {
$query->where('name', 'like', "%{$search}%");
}
if ($type = $request->query('type')) {
$query->where('type', $type);
}
$types = Store::query()->select('type')->distinct()->orderBy('type')->pluck('type');
return view('admin.stores.index', [
'stores' => $query->paginate(12)->withQueryString(),
'types' => $types,
'currentSearch' => $search,
'currentType' => $type,
]);
}
/**
* Show the store creation form with the 3D position picker.
*/
public function create(): View
{
return view('admin.stores.form', [
'store' => new Store(['is_open' => true, 'pos_x' => 0, 'pos_y' => 0, 'pos_z' => 0, 'width' => 8, 'height' => 6, 'depth' => 8]),
'owners' => User::query()->orderBy('name')->get(['id', 'name', 'email']),
'agents' => AiAgent::query()->orderBy('name')->get(['id', 'name', 'type']),
'isEdit' => false,
]);
}
/**
* Persist a newly created store.
*/
public function store(StoreRequest $request): RedirectResponse
{
$store = Store::create($request->validated());
return redirect()
->route('admin.stores.edit', $store)
->with('status', __('admin.stores.created'));
}
/**
* Show the store edit form pre-filled with the current 3D placement.
*/
public function edit(Store $store): View
{
return view('admin.stores.form', [
'store' => $store,
'owners' => User::query()->orderBy('name')->get(['id', 'name', 'email']),
'agents' => AiAgent::query()->orderBy('name')->get(['id', 'name', 'type']),
'isEdit' => true,
]);
}
/**
* Update an existing store.
*/
public function update(StoreRequest $request, Store $store): RedirectResponse
{
$store->update($request->validated());
return redirect()
->route('admin.stores.index')
->with('status', __('admin.stores.updated'));
}
/**
* Delete a store from the city registry.
*/
public function destroy(Store $store): RedirectResponse
{
$store->delete();
return redirect()
->route('admin.stores.index')
->with('status', __('stores.deleted'));
}
}