Files
dockerank2/install_ank2_complete.php
2026-09-01 07:49:48 -03:00

1944 lines
65 KiB
PHP
Executable File

<?php
/**
* Annunk Complete Application Installer
*
* This script creates the complete Annunk application structure.
*/
$basePath = __DIR__;
function ensureDirectory(string $path): void
{
if (!is_dir($path)) {
mkdir($path, 0755, true);
}
}
function writeFile(string $path, string $content): void
{
file_put_contents($path, $content);
}
// Create directories
$directories = [
'app/Http/Controllers/Admin',
'app/Http/Controllers/SuperAdmin',
'app/Http/Controllers/Api',
'app/Http/Livewire',
'app/Services',
'app/Console/Commands',
'resources/views/admin',
'resources/views/superadmin',
'resources/views/dashboard',
'resources/views/auth',
'resources/views/layouts',
'resources/views/components',
'database/seeders',
'lang/en',
'lang/pt_BR',
'lang/es',
'routes',
];
foreach ($directories as $dir) {
ensureDirectory($basePath . '/' . $dir);
}
echo "Directory structure created.\n";
// Update User model
$userModel = <<<'PHP'
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
/**
* @property int $id
* @property string $name
* @property string $email
* @property \Illuminate\Support\Carbon|null $email_verified_at
* @property string $password
* @property string|null $remember_token
* @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at
* @property string|null $phone
* @property string $locale
* @property bool $is_active
* @property bool $is_approved
* @property-read Collection<int, UserSubject> $userSubjects
* @property-read Collection<int, Insight> $insights
* @property-read Collection<int, InsightSchedule> $insightSchedules
* @property-read Collection<int, TelegramUser> $telegramUsers
* @property-read Collection<int, TokenUsage> $tokenUsages
* @property-read Collection<int, Subject> $subjects
*/
class User extends Authenticatable
{
/** @use HasFactory<\Database\Factories\UserFactory> */
use HasFactory, Notifiable, HasRoles;
/** @var array<int, string> */
protected $fillable = [
'name',
'email',
'password',
'phone',
'locale',
'is_active',
'is_approved',
];
/** @var array<string, string> */
protected $casts = [
'email_verified_at' => 'datetime',
'password' => 'hashed',
'is_active' => 'boolean',
'is_approved' => 'boolean',
];
/** @var array<int, string> */
protected $hidden = [
'password',
'remember_token',
];
/**
* Get the user subjects for the user.
*/
public function userSubjects(): HasMany
{
return $this->hasMany(UserSubject::class);
}
/**
* Get the insights for the user.
*/
public function insights(): HasMany
{
return $this->hasMany(Insight::class);
}
/**
* Get the insight schedules for the user.
*/
public function insightSchedules(): HasMany
{
return $this->hasMany(InsightSchedule::class);
}
/**
* Get the telegram users for the user.
*/
public function telegramUsers(): HasMany
{
return $this->hasMany(TelegramUser::class);
}
/**
* Get the token usages for the user.
*/
public function tokenUsages(): HasMany
{
return $this->hasMany(TokenUsage::class);
}
/**
* Get the subjects created by the user.
*/
public function subjects(): HasMany
{
return $this->hasMany(Subject::class);
}
}
PHP;
writeFile($basePath . '/app/Models/User.php', $userModel);
// Create controllers
$controllers = [
'app/Http/Controllers/Controller.php' => <<<'PHP'
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;
/**
* Base controller for Annunk application.
*/
class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
}
PHP,
'app/Http/Controllers/Auth/AuthenticatedSessionController.php' => <<<'PHP'
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
/**
* Handles authenticated session management.
*/
class AuthenticatedSessionController extends Controller
{
/**
* Display the login view.
*/
public function create(): View
{
return view('auth.login');
}
/**
* Handle an incoming authentication request.
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'email' => ['required', 'string', 'email'],
'password' => ['required', 'string'],
]);
if (!Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
return back()->withErrors([
'email' => __('auth.failed'),
])->onlyInput('email');
}
$request->session()->regenerate();
return redirect()->intended(route('dashboard', absolute: false));
}
/**
* Destroy an authenticated session.
*/
public function destroy(Request $request): RedirectResponse
{
Auth::guard('web')->logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
}
PHP,
'app/Http/Controllers/Auth/RegisteredUserController.php' => <<<'PHP'
<?php
namespace App\Http\Controllers\Auth;
use App\Models\User;
use App\Http\Controllers\Controller;
use Illuminate\Auth\Events\Registered;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules;
use Illuminate\View\View;
/**
* Handles user registration requests.
*/
class RegisteredUserController extends Controller
{
/**
* Display the registration view.
*/
public function create(): View
{
return view('auth.register');
}
/**
* Handle an incoming registration request.
*/
public function store(Request $request): RedirectResponse
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
'password' => ['required', 'confirmed', Rules\Password::defaults()],
]);
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'is_approved' => false,
]);
event(new Registered($user));
Auth::login($user);
return redirect(route('dashboard', absolute: false));
}
}
PHP,
];
foreach ($controllers as $file => $content) {
writeFile($basePath . '/' . $file, $content);
}
echo "Controllers created.\n";
// Create routes
$webRoutes = <<<'PHP'
<?php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\Admin\SubjectController;
use App\Http\Controllers\Admin\InsightController;
use App\Http\Controllers\Admin\TelegramController;
use App\Http\Controllers\SuperAdmin\UserController;
use App\Http\Controllers\SuperAdmin\SystemController;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\RegisteredUserController;
Route::get('/', function () {
return view('welcome');
})->name('home');
Route::middleware('guest')->group(function () {
Route::get('login', [AuthenticatedSessionController::class, 'create'])->name('login');
Route::post('login', [AuthenticatedSessionController::class, 'store']);
Route::get('register', [RegisteredUserController::class, 'create'])->name('register');
Route::post('register', [RegisteredUserController::class, 'store']);
});
Route::middleware('auth')->group(function () {
Route::post('logout', [AuthenticatedSessionController::class, 'destroy'])->name('logout');
Route::get('/dashboard', function () {
return view('dashboard.index');
})->name('dashboard');
Route::middleware('verified')->group(function () {
Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update');
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
});
});
Route::middleware(['auth', 'verified', 'role:admin|superadmin'])->prefix('admin')->name('admin.')->group(function () {
Route::get('/subjects', [SubjectController::class, 'index'])->name('subjects.index');
Route::post('/subjects', [SubjectController::class, 'store'])->name('subjects.store');
Route::put('/subjects/{subject}', [SubjectController::class, 'update'])->name('subjects.update');
Route::delete('/subjects/{subject}', [SubjectController::class, 'destroy'])->name('subjects.destroy');
Route::get('/insights', [InsightController::class, 'index'])->name('insights.index');
Route::post('/insights', [InsightController::class, 'store'])->name('insights.store');
Route::get('/telegram', [TelegramController::class, 'index'])->name('telegram.index');
Route::post('/telegram/connect', [TelegramController::class, 'connect'])->name('telegram.connect');
Route::post('/telegram/disconnect', [TelegramController::class, 'disconnect'])->name('telegram.disconnect');
});
Route::middleware(['auth', 'verified', 'role:superadmin'])->prefix('superadmin')->name('superadmin.')->group(function () {
Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::post('/users/{user}/approve', [UserController::class, 'approve'])->name('users.approve');
Route::post('/users/{user}/reject', [UserController::class, 'reject'])->name('users.reject');
Route::delete('/users/{user}', [UserController::class, 'destroy'])->name('users.destroy');
Route::get('/system', [SystemController::class, 'index'])->name('system.index');
Route::post('/system/subjects', [SystemController::class, 'createSubject'])->name('system.subjects.store');
Route::put('/system/subjects/{subject}', [SystemController::class, 'updateSubject'])->name('system.subjects.update');
Route::delete('/system/subjects/{subject}', [SystemController::class, 'destroySubject'])->name('system.subjects.destroy');
});
PHP;
writeFile($basePath . '/routes/web.php', $webRoutes);
// Create language files
$langFiles = [
'lang/en/auth.php' => <<<'PHP'
<?php
return [
'failed' => 'These credentials do not match our records.',
'password' => 'The provided password is incorrect.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
];
PHP,
'lang/en/dashboard.php' => <<<'PHP'
<?php
return [
'title' => 'Dashboard',
'subjects' => 'My Subjects',
'insights' => 'Insights',
'token_usage' => 'Token Usage',
'telegram' => 'Telegram',
'settings' => 'Settings',
];
PHP,
'lang/pt_BR/auth.php' => <<<'PHP'
<?php
return [
'failed' => 'Estas credenciais não correspondem aos nossos registros.',
'password' => 'A senha fornecida está incorreta.',
'throttle' => 'Muitas tentativas de login. Por favor, tente novamente em :seconds segundos.',
];
PHP,
'lang/pt_BR/dashboard.php' => <<<'PHP'
<?php
return [
'title' => 'Painel de Controle',
'subjects' => 'Meus Assuntos',
'insights' => 'Insights',
'token_usage' => 'Uso de Tokens',
'telegram' => 'Telegram',
'settings' => 'Configurações',
];
PHP,
'lang/es/auth.php' => <<<'PHP'
<?php
return [
'failed' => 'Estas credenciales no coinciden con nuestros registros.',
'password' => 'La contraseña proporcionada es incorrecta.',
'throttle' => 'Demasiados intentos de inicio de sesión. Por favor, inténtelo de nuevo en :seconds segundos.',
];
PHP,
'lang/es/dashboard.php' => <<<'PHP'
<?php
return [
'title' => 'Panel de Control',
'subjects' => 'Mis Temas',
'insights' => 'Ideas',
'token_usage' => 'Uso de Tokens',
'telegram' => 'Telegram',
'settings' => 'Configuración',
];
PHP,
];
foreach ($langFiles as $file => $content) {
writeFile($basePath . '/' . $file, $content);
}
echo "Language files created.\n";
// Create seeders
$databaseSeeder = <<<'PHP'
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\User;
use App\Models\Subject;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
/**
* Main database seeder for Annunk application.
*/
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
// Create roles
$superAdminRole = Role::firstOrCreate(['name' => 'superadmin']);
$adminRole = Role::firstOrCreate(['name' => 'admin']);
// Create SuperAdmin user
$superAdmin = User::firstOrCreate(
['email' => 'marcocosta@gmx.com'],
[
'name' => 'Super Admin',
'password' => bcrypt('superadmin123456'),
'is_active' => true,
'is_approved' => true,
]
);
$superAdmin->assignRole('superadmin');
// Create system subjects
$subjects = [
['name' => 'Aprendizado e Treinamento de Inglês', 'description' => 'Learn English language', 'is_system' => true],
['name' => 'Aprendizado e Treinamento de Flight Simulator X', 'description' => 'Learn Flight Simulator X', 'is_system' => true],
['name' => 'Aprendizado e Treinamento de Administração de Pequenos Negócios', 'description' => 'Learn small business administration', 'is_system' => true],
['name' => 'Aprendizado e Treinamento de Estoicismo', 'description' => 'Learn Stoicism', 'is_system' => true],
['name' => 'Aprendizado e Treinamento de Eletrônica', 'description' => 'Learn Electronics', 'is_system' => true],
['name' => 'Aprendizado e Treinamento de Mandarim', 'description' => 'Learn Mandarin', 'is_system' => true],
['name' => 'Aprendizado e Treinamento de As Leis do Triunfo', 'description' => 'Learn The Laws of Success by Napoleon Hill', 'is_system' => true],
['name' => 'Aprendizado e Treinamento de Gurus dos Negócios', 'description' => 'Learn from business gurus: ideas, quotes, negotiation and management strategies', 'is_system' => true],
['name' => 'Aprendizado e Treinamento de Gurus dos Negócios e Riqueza', 'description' => 'Ideias, frases, modos de negociar, administrar, conquistar bem-estar financeiro, riqueza, mente milionária, arte de negociar e lei da atração.', 'is_system' => true],
];
foreach ($subjects as $subjectData) {
Subject::firstOrCreate(array_merge($subjectData, ['user_id' => null]));
}
}
}
PHP;
writeFile($basePath . '/database/seeders/DatabaseSeeder.php', $databaseSeeder);
// Create services
$openRouterService = <<<'PHP'
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
/**
* Service for interacting with OpenRouter.ai API.
*/
class OpenRouterService
{
/**
* Generate an insight using OpenRouter.ai.
*/
public function generateInsight(string $subject, string $userMessage, array $context = []): array
{
$apiKey = config('services.openrouter.api_key');
$model = config('services.openrouter.model', 'google/gemini-2.0-flash-exp:free');
if (!$apiKey) {
return [
'success' => false,
'error' => 'OpenRouter API key not configured',
];
}
try {
$response = Http::withHeaders([
'Authorization' => 'Bearer ' . $apiKey,
'Content-Type' => 'application/json',
'HTTP-Referer' => config('app.url'),
'X-Title' => config('app.name'),
])->post('https://openrouter.ai/api/v1/chat/completions', [
'model' => $model,
'messages' => [
['role' => 'system', 'content' => "You are an expert learning assistant for the subject: {$subject}. Provide insightful, educational content."],
['role' => 'user', 'content' => $userMessage],
],
'max_tokens' => 500,
]);
$data = $response->json();
if (isset($data['choices'][0]['message']['content'])) {
return [
'success' => true,
'content' => $data['choices'][0]['message']['content'],
'input_tokens' => $data['usage']['prompt_tokens'] ?? 0,
'output_tokens' => $data['usage']['completion_tokens'] ?? 0,
];
}
return [
'success' => false,
'error' => $data['error']['message'] ?? 'Unknown error',
];
} catch (\Exception $e) {
Log::error('OpenRouter API error: ' . $e->getMessage());
return [
'success' => false,
'error' => $e->getMessage(),
];
}
}
}
PHP;
writeFile($basePath . '/app/Services/OpenRouterService.php', $openRouterService);
$telegramService = <<<'PHP'
<?php
namespace App\Services;
use Illuminate\Support\Facades\Log;
use Telegram\Bot\Api;
/**
* Service for interacting with Telegram Bot API.
*/
class TelegramService
{
protected ?Api $telegram = null;
/**
* Initialize the Telegram bot.
*/
public function init(): void
{
$token = config('services.telegram.bot_token');
if ($token) {
$this->telegram = new Api($token);
}
}
/**
* Send a message to a Telegram user.
*/
public function sendMessage(string $chatId, string $message): bool
{
if (!$this->telegram) {
$this->init();
}
if (!$this->telegram) {
return false;
}
try {
$this->telegram->sendMessage([
'chat_id' => $chatId,
'text' => $message,
'parse_mode' => 'HTML',
]);
return true;
} catch (\Exception $e) {
Log::error('Telegram send message error: ' . $e->getMessage());
return false;
}
}
/**
* Set webhook for the bot.
*/
public function setWebhook(string $url): bool
{
if (!$this->telegram) {
$this->init();
}
if (!$this->telegram) {
return false;
}
try {
$this->telegram->setWebhook([
'url' => $url,
'allowed_updates' => ['message', 'callback_query'],
]);
return true;
} catch (\Exception $e) {
Log::error('Telegram set webhook error: ' . $e->getMessage());
return false;
}
}
}
PHP;
writeFile($basePath . '/app/Services/TelegramService.php', $telegramService);
// Create Insight Scheduler Command
$insightScheduler = <<<'PHP'
<?php
namespace App\Console\Commands;
use App\Models\InsightSchedule;
use App\Models\Subject;
use App\Models\User;
use App\Services\OpenRouterService;
use App\Services\TelegramService;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
/**
* Send scheduled insights to users.
*/
class SendScheduledInsights extends Command
{
/**
* The name and signature of the console command.
*/
protected $signature = 'insights:send
{--user= : Send insights for a specific user}
{--subject= : Send insights for a specific subject}';
/**
* The console command description.
*/
protected $description = 'Send scheduled insights to users';
/**
* Execute the console command.
*/
public function handle(OpenRouterService $openRouter, TelegramService $telegram): int
{
$query = InsightSchedule::where('is_active', true)
->whereHas('subject', function ($q) {
$q->where('is_active', true);
});
if ($this->option('user')) {
$query->where('user_id', $this->option('user'));
}
if ($this->option('subject')) {
$query->where('subject_id', $this->option('subject'));
}
$schedules = $query->get();
$sentCount = 0;
foreach ($schedules as $schedule) {
if ($this->shouldSend($schedule)) {
$this->sendInsight($schedule, $openRouter, $telegram);
$sentCount++;
}
}
$this->info("Sent {$sentCount} insights.");
return 0;
}
/**
* Determine if an insight should be sent based on schedule.
*/
private function shouldSend(InsightSchedule $schedule): bool
{
if (!$schedule->last_sent_at) {
return true;
}
$lastSent = $schedule->last_sent_at;
$now = now();
return match ($schedule->frequency) {
'daily' => $lastSent->lt($now->subDay()),
'weekly' => $lastSent->lt($now->subWeek()),
'biweekly' => $lastSent->lt($now->subWeeks(2)),
'monthly' => $lastSent->lt($now->subMonth()),
default => true,
};
}
/**
* Send an insight to a user.
*/
private function sendInsight(InsightSchedule $schedule, OpenRouterService $openRouter, TelegramService $telegram): void
{
$user = $schedule->user;
$subject = $schedule->subject;
$result = $openRouter->generateInsight(
$subject->name,
"Generate an insightful learning tip for {$subject->name}"
);
if (!$result['success']) {
$this->error("Failed to generate insight for user {$user->id}: " . $result['error']);
return;
}
DB::transaction(function () use ($user, $subject, $result, $schedule) {
// Save insight
\App\Models\Insight::create([
'user_id' => $user->id,
'subject_id' => $subject->id,
'content' => $result['content'],
'input_tokens' => $result['input_tokens'],
'output_tokens' => $result['output_tokens'],
'sent_at' => now(),
]);
// Save token usage
\App\Models\TokenUsage::create([
'user_id' => $user->id,
'input_tokens' => $result['input_tokens'],
'output_tokens' => $result['output_tokens'],
'total_tokens' => $result['input_tokens'] + $result['output_tokens'],
'model' => config('services.openrouter.model'),
'provider' => 'openrouter',
]);
// Update schedule
$schedule->update(['last_sent_at' => now()]);
// Send via Telegram if connected
$telegramUser = $user->telegramUsers()->where('is_active', true)->first();
if ($telegramUser) {
$telegram->sendMessage(
$telegramUser->telegram_user_id,
"📚 *{$subject->name}*\n\n{$result['content']}"
);
}
});
$this->info("Sent insight to user {$user->id} for subject {$subject->id}");
}
}
PHP;
writeFile($basePath . '/app/Console/Commands/SendScheduledInsights.php', $insightScheduler);
// Create Admin controllers
$adminControllers = [
'app/Http/Controllers/Admin/SubjectController.php' => <<<'PHP'
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Subject;
use App\Models\UserSubject;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Manages subjects for admin users.
*/
class SubjectController extends Controller
{
/**
* Display a listing of available subjects.
*/
public function index(Request $request)
{
$user = Auth::user();
$systemSubjects = Subject::where('is_system', true)
->where('is_active', true)
->get();
$userSubjects = Subject::where('user_id', $user->id)
->where('is_active', true)
->get();
$mySubjects = UserSubject::where('user_id', $user->id)
->with('subject')
->get();
return view('admin.subjects.index', compact('systemSubjects', 'userSubjects', 'mySubjects'));
}
/**
* Store a newly created subject.
*/
public function store(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'introduction' => ['nullable', 'string'],
]);
Subject::create([
'user_id' => Auth::id(),
'name' => $request->name,
'description' => $request->description,
'introduction' => $request->introduction,
'is_system' => false,
'is_active' => true,
]);
return back()->with('success', 'Subject created successfully.');
}
/**
* Update the specified subject.
*/
public function update(Request $request, Subject $subject)
{
$this->authorize('update', $subject);
$request->validate([
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'introduction' => ['nullable', 'string'],
'is_active' => ['boolean'],
]);
$subject->update($request->only('name', 'description', 'introduction', 'is_active'));
return back()->with('success', 'Subject updated successfully.');
}
/**
* Remove the specified subject.
*/
public function destroy(Subject $subject)
{
$this->authorize('delete', $subject);
$subject->delete();
return back()->with('success', 'Subject deleted successfully.');
}
}
PHP,
'app/Http/Controllers/Admin/InsightController.php' => <<<'PHP'
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\Insight;
use App\Models\Subject;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Manages insights for admin users.
*/
class InsightController extends Controller
{
/**
* Display a listing of insights.
*/
public function index(Request $request)
{
$user = Auth::user();
$insights = Insight::where('user_id', $user->id)
->with('subject')
->orderByDesc('created_at')
->paginate(20);
return view('admin.insights.index', compact('insights'));
}
/**
* Store a newly created insight.
*/
public function store(Request $request)
{
$request->validate([
'subject_id' => ['required', 'exists:subjects,id'],
'content' => ['required', 'string'],
]);
Insight::create([
'user_id' => Auth::id(),
'subject_id' => $request->subject_id,
'content' => $request->content,
'type' => 'manual',
]);
return back()->with('success', 'Insight created successfully.');
}
}
PHP,
'app/Http/Controllers/Admin/TelegramController.php' => <<<'PHP'
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\TelegramUser;
use App\Services\TelegramService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Manages Telegram integration for admin users.
*/
class TelegramController extends Controller
{
protected TelegramService $telegram;
public function __construct(TelegramService $telegram)
{
$this->telegram = $telegram;
}
/**
* Display Telegram settings.
*/
public function index()
{
$user = Auth::user();
$telegramUser = $user->telegramUsers()->first();
return view('admin.telegram.index', compact('telegramUser'));
}
/**
* Connect Telegram account.
*/
public function connect(Request $request)
{
$request->validate([
'telegram_user_id' => ['required', 'string'],
'telegram_username' => ['nullable', 'string'],
'telegram_first_name' => ['nullable', 'string'],
'telegram_last_name' => ['nullable', 'string'],
]);
TelegramUser::updateOrCreate(
['user_id' => Auth::id()],
[
'telegram_user_id' => $request->telegram_user_id,
'telegram_username' => $request->telegram_username,
'telegram_first_name' => $request->telegram_first_name,
'telegram_last_name' => $request->telegram_last_name,
'is_active' => true,
]
);
return back()->with('success', 'Telegram connected successfully.');
}
/**
* Disconnect Telegram account.
*/
public function disconnect()
{
Auth::user()->telegramUsers()->delete();
return back()->with('success', 'Telegram disconnected successfully.');
}
}
PHP,
];
foreach ($adminControllers as $file => $content) {
writeFile($basePath . '/' . $file, $content);
}
echo "Admin controllers created.\n";
// Create SuperAdmin controllers
$superAdminControllers = [
'app/Http/Controllers/SuperAdmin/UserController.php' => <<<'PHP'
<?php
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Manages users for SuperAdmin.
*/
class UserController extends Controller
{
/**
* Display a listing of users.
*/
public function index(Request $request)
{
$users = User::where('id', '!=', Auth::id())
->orderByDesc('created_at')
->paginate(20);
return view('superadmin.users.index', compact('users'));
}
/**
* Approve a user.
*/
public function approve(User $user)
{
$user->update(['is_approved' => true]);
return back()->with('success', 'User approved successfully.');
}
/**
* Reject a user.
*/
public function reject(User $user)
{
$user->update(['is_approved' => false]);
return back()->with('success', 'User rejected successfully.');
}
/**
* Remove the specified user.
*/
public function destroy(User $user)
{
$user->delete();
return back()->with('success', 'User deleted successfully.');
}
}
PHP,
'app/Http/Controllers/SuperAdmin/SystemController.php' => <<<'PHP'
<?php
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use App\Models\Subject;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* Manages system settings for SuperAdmin.
*/
class SystemController extends Controller
{
/**
* Display system dashboard.
*/
public function index()
{
$stats = [
'total_users' => \App\Models\User::count(),
'active_users' => \App\Models\User::where('is_active', true)->count(),
'pending_users' => \App\Models\User::where('is_approved', false)->count(),
'total_subjects' => Subject::count(),
'total_insights' => \App\Models\Insight::count(),
];
return view('superadmin.system.index', compact('stats'));
}
/**
* Create a new system subject.
*/
public function createSubject(Request $request)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'introduction' => ['nullable', 'string'],
]);
Subject::create([
'user_id' => null,
'name' => $request->name,
'description' => $request->description,
'introduction' => $request->introduction,
'is_system' => true,
'is_active' => true,
]);
return back()->with('success', 'System subject created successfully.');
}
/**
* Update the specified system subject.
*/
public function updateSubject(Request $request, Subject $subject)
{
$request->validate([
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'introduction' => ['nullable', 'string'],
'is_active' => ['boolean'],
]);
$subject->update($request->only('name', 'description', 'introduction', 'is_active'));
return back()->with('success', 'System subject updated successfully.');
}
/**
* Remove the specified system subject.
*/
public function destroySubject(Subject $subject)
{
$subject->delete();
return back()->with('success', 'System subject deleted successfully.');
}
}
PHP,
];
foreach ($superAdminControllers as $file => $content) {
writeFile($basePath . '/' . $file, $content);
}
echo "SuperAdmin controllers created.\n";
// Create views
$views = [
'resources/views/layouts/app.blade.php' => <<<'BLADE'
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>@yield('title', config('app.name', 'Annunk'))</title>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- Scripts -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="font-sans antialiased bg-gray-50">
<div class="min-h-screen">
@include('layouts.navigation')
<!-- Page Heading -->
@isset($header)
<header class="bg-white shadow">
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
{{ $header }}
</div>
</header>
@endisset
<!-- Page Content -->
<main>
@yield('content')
</main>
</div>
@stack('scripts')
</body>
</html>
BLADE,
'resources/views/layouts/navigation.blade.php' => <<<'BLADE'
<nav x-data="{ open: false }" class="bg-white border-b border-gray-100">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<div class="shrink-0 flex items-center">
<a href="{{ route('dashboard') }}" class="text-xl font-bold text-indigo-600">
{{ config('app.name') }}
</a>
</div>
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
<a href="{{ route('dashboard') }}" class="inline-flex items-center px-1 pt-1 border-b-2 {{ request()->routeIs('dashboard') ? 'border-indigo-500 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700' }}">
{{ __('Dashboard') }}
</a>
@can('view admin panels')
<a href="{{ route('admin.subjects.index') }}" class="inline-flex items-center px-1 pt-1 border-b-2 {{ request()->routeIs('admin.subjects.*') ? 'border-indigo-500 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700' }}">
{{ __('Subjects') }}
</a>
@endcan
@can('view superadmin panels')
<a href="{{ route('superadmin.users.index') }}" class="inline-flex items-center px-1 pt-1 border-b-2 {{ request()->routeIs('superadmin.users.*') ? 'border-indigo-500 text-gray-900' : 'border-transparent text-gray-500 hover:text-gray-700' }}">
{{ __('Users') }}
</a>
@endcan
</div>
</div>
<div class="hidden sm:flex sm:items-center sm:ms-6">
<div class="ms-3 relative">
<div x-data="{ open: false }" class="relative">
<button @click="open = !open" class="flex text-sm border-2 border-transparent rounded-full focus:outline-none transition duration-150 ease-in-out">
<span class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 bg-white hover:text-gray-700 focus:outline-none">
{{ Auth::user()->name }}
</span>
</button>
<div x-show="open" @click.away="open = false" class="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5">
<a href="{{ route('profile.edit') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">{{ __('Profile') }}</a>
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="w-full text-left block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
{{ __('Log Out') }}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</nav>
BLADE,
'resources/views/auth/login.blade.php' => <<<'BLADE'
<x-guest-layout>
<x-authentication-card>
<x-slot name="logo">
<a href="/">
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
</a>
</x-slot>
<x-validation-errors class="mb-4" />
@if (session('status'))
<div class="mb-4 font-medium text-sm text-green-600">
{{ session('status') }}
</div>
@endif
<form method="POST" action="{{ route('login') }}">
@csrf
<div>
<x-label for="email" value="{{ __('Email') }}" />
<x-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autofocus autocomplete="username" />
</div>
<div class="mt-4">
<x-label for="password" value="{{ __('Password') }}" />
<x-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="current-password" />
</div>
<div class="block mt-4">
<label for="remember_me" class="flex items-center">
<x-checkbox id="remember_me" name="remember" />
<span class="ms-2 text-sm text-gray-600">{{ __('Remember me') }}</span>
</label>
</div>
<div class="flex items-center justify-end mt-4">
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('register') }}">
{{ __('Need an account? Register') }}
</a>
<x-button class="ms-4">
{{ __('Log in') }}
</x-button>
</div>
</form>
</x-authentication-card>
</x-guest-layout>
BLADE,
'resources/views/auth/register.blade.php' => <<<'BLADE'
<x-guest-layout>
<x-authentication-card>
<x-slot name="logo">
<a href="/">
<x-application-logo class="w-20 h-20 fill-current text-gray-500" />
</a>
</x-slot>
<x-validation-errors class="mb-4" />
<form method="POST" action="{{ route('register') }}">
@csrf
<div>
<x-label for="name" value="{{ __('Name') }}" />
<x-input id="name" class="block mt-1 w-full" type="text" name="name" :value="old('name')" required autofocus autocomplete="name" />
</div>
<div class="mt-4">
<x-label for="email" value="{{ __('Email') }}" />
<x-input id="email" class="block mt-1 w-full" type="email" name="email" :value="old('email')" required autocomplete="username" />
</div>
<div class="mt-4">
<x-label for="password" value="{{ __('Password') }}" />
<x-input id="password" class="block mt-1 w-full" type="password" name="password" required autocomplete="new-password" />
</div>
<div class="mt-4">
<x-label for="password_confirmation" value="{{ __('Confirm Password') }}" />
<x-input id="password_confirmation" class="block mt-1 w-full" type="password" name="password_confirmation" required autocomplete="new-password" />
</div>
<div class="flex items-center justify-end mt-4">
<a class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" href="{{ route('login') }}">
{{ __('Already registered?') }}
</a>
<x-button class="ms-4">
{{ __('Register') }}
</x-button>
</div>
</form>
</x-authentication-card>
</x-guest-layout>
BLADE,
'resources/views/dashboard/index.blade.php' => <<<'BLADE'
@extends('layouts.app')
@section('title', __('Dashboard'))
@section('content')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<h2 class="text-2xl font-bold mb-6">{{ __('Welcome to Annunk') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="bg-indigo-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-indigo-900">{{ __('My Subjects') }}</h3>
<p class="text-3xl font-bold text-indigo-600 mt-2">{{ auth()->user()->userSubjects()->count() }}</p>
</div>
<div class="bg-green-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-green-900">{{ __('Total Insights') }}</h3>
<p class="text-3xl font-bold text-green-600 mt-2">{{ auth()->user()->insights()->count() }}</p>
</div>
<div class="bg-blue-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-blue-900">{{ __('Token Usage') }}</h3>
<p class="text-3xl font-bold text-blue-600 mt-2">
{{ number_format(auth()->user()->tokenUsages()->sum('total_tokens')) }}
</p>
</div>
</div>
<div class="mt-8">
<h3 class="text-xl font-semibold mb-4">{{ __('Recent Insights') }}</h3>
@forelse(auth()->user()->insights()->with('subject')->latest()->take(5)->get() as $insight)
<div class="border-b border-gray-200 pb-4 mb-4">
<div class="flex justify-between items-start">
<div>
<span class="text-sm text-indigo-600 font-medium">{{ $insight->subject->name }}</span>
<p class="mt-1 text-gray-900">{{ $insight->content }}</p>
</div>
<span class="text-sm text-gray-500">{{ $insight->created_at->diffForHumans() }}</span>
</div>
</div>
@empty
<p class="text-gray-500">{{ __('No insights yet. Start by selecting subjects!') }}</p>
@endforelse
</div>
</div>
</div>
</div>
</div>
@endsection
BLADE,
];
foreach ($views as $file => $content) {
writeFile($basePath . '/' . $file, $content);
}
echo "Views created.\n";
// Create additional view files
$adminViews = [
'resources/views/admin/subjects/index.blade.php' => <<<'BLADE'
@extends('layouts.app')
@section('title', __('My Subjects'))
@section('content')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">{{ __('My Subjects') }}</h2>
<button onclick="document.getElementById('createSubjectModal').classList.remove('hidden')"
class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700">
{{ __('Create Subject') }}
</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
@foreach($mySubjects as $userSubject)
<div class="border border-gray-200 rounded-lg p-6 hover:shadow-lg transition">
<div class="flex justify-between items-start mb-4">
<h3 class="text-lg font-semibold">{{ $userSubject->subject->name }}</h3>
<span class="px-2 py-1 text-xs rounded-full {{ $userSubject->is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800' }}">
{{ $userSubject->is_active ? __('Active') : __('Inactive') }}
</span>
</div>
<p class="text-gray-600 text-sm mb-4">{{ $userSubject->subject->description }}</p>
<div class="flex justify-between items-center">
<span class="text-sm text-gray-500">{{ __('Every') }} {{ $userSubject->frequency_days }} {{ __('days') }}</span>
<form method="POST" action="{{ route('admin.subjects.destroy', $userSubject->subject) }}" class="inline">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-800 text-sm">
{{ __('Remove') }}
</button>
</form>
</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>
<!-- Create Subject Modal -->
<div id="createSubjectModal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full">
<div class="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
<div class="mt-3">
<h3 class="text-lg font-medium text-gray-900 mb-4">{{ __('Create New Subject') }}</h3>
<form method="POST" action="{{ route('admin.subjects.store') }}">
@csrf
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">{{ __('Name') }}</label>
<input type="text" name="name" class="w-full px-3 py-2 border border-gray-300 rounded-md" required>
</div>
<div class="mb-4">
<label class="block text-gray-700 text-sm font-bold mb-2">{{ __('Description') }}</label>
<textarea name="description" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md"></textarea>
</div>
<div class="flex justify-end gap-2">
<button type="button" onclick="document.getElementById('createSubjectModal').classList.add('hidden')"
class="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400">
{{ __('Cancel') }}
</button>
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
{{ __('Create') }}
</button>
</div>
</form>
</div>
</div>
</div>
@endsection
BLADE,
];
foreach ($adminViews as $file => $content) {
writeFile($basePath . '/' . $file, $content);
}
echo "Admin views created.\n";
// Create SuperAdmin views
$superAdminViews = [
'resources/views/superadmin/users/index.blade.php' => <<<'BLADE'
@extends('layouts.app')
@section('title', __('User Management'))
@section('content')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<h2 class="text-2xl font-bold mb-6">{{ __('User Management') }}</h2>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Name') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Email') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Status') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Actions') }}</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($users as $user)
<tr>
<td class="px-6 py-4 whitespace-nowrap">{{ $user->name }}</td>
<td class="px-6 py-4 whitespace-nowrap">{{ $user->email }}</td>
<td class="px-6 py-4 whitespace-nowrap">
@if($user->is_approved)
<span class="px-2 py-1 text-xs rounded-full bg-green-100 text-green-800">{{ __('Approved') }}</span>
@else
<span class="px-2 py-1 text-xs rounded-full bg-yellow-100 text-yellow-800">{{ __('Pending') }}</span>
@endif
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
@if(!$user->is_approved)
<form method="POST" action="{{ route('superadmin.users.approve', $user) }}" class="inline">
@csrf
<button type="submit" class="text-green-600 hover:text-green-900 mr-2">
{{ __('Approve') }}
</button>
</form>
@endif
<form method="POST" action="{{ route('superadmin.users.destroy', $user) }}" class="inline" onsubmit="return confirm('{{ __('Are you sure?') }}')">
@csrf
@method('DELETE')
<button type="submit" class="text-red-600 hover:text-red-900">
{{ __('Delete') }}
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="mt-4">
{{ $users->links() }}
</div>
</div>
</div>
</div>
</div>
@endsection
BLADE,
'resources/views/superadmin/system/index.blade.php' => <<<'BLADE'
@extends('layouts.app')
@section('title', __('System Management'))
@section('content')
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 text-gray-900">
<h2 class="text-2xl font-bold mb-6">{{ __('System Management') }}</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8">
<div class="bg-indigo-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-indigo-900">{{ __('Total Users') }}</h3>
<p class="text-3xl font-bold text-indigo-600 mt-2">{{ $stats['total_users'] }}</p>
</div>
<div class="bg-green-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-green-900">{{ __('Active Users') }}</h3>
<p class="text-3xl font-bold text-green-600 mt-2">{{ $stats['active_users'] }}</p>
</div>
<div class="bg-yellow-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-yellow-900">{{ __('Pending Users') }}</h3>
<p class="text-3xl font-bold text-yellow-600 mt-2">{{ $stats['pending_users'] }}</p>
</div>
<div class="bg-blue-50 p-6 rounded-lg">
<h3 class="text-lg font-semibold text-blue-900">{{ __('Total Insights') }}</h3>
<p class="text-3xl font-bold text-blue-600 mt-2">{{ $stats['total_insights'] }}</p>
</div>
</div>
<div class="border-t border-gray-200 pt-6">
<h3 class="text-xl font-semibold mb-4">{{ __('System Subjects') }}</h3>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
@foreach(\App\Models\Subject::where('is_system', true)->get() as $subject)
<div class="border border-gray-200 rounded-lg p-4">
<h4 class="font-semibold">{{ $subject->name }}</h4>
<p class="text-sm text-gray-600 mt-1">{{ $subject->description }}</p>
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
@endsection
BLADE,
];
foreach ($superAdminViews as $file => $content) {
writeFile($basePath . '/' . $file, $content);
}
echo "SuperAdmin views created.\n";
// Create config/services.php additions
$servicesConfig = <<<'PHP'
<?php
return [
'openrouter' => [
'api_key' => env('OPENROUTER_API_KEY'),
'model' => env('OPENROUTER_MODEL', 'google/gemini-2.0-flash-exp:free'),
],
'telegram' => [
'bot_token' => env('TELEGRAM_BOT_TOKEN'),
'webhook_url' => env('TELEGRAM_WEBHOOK_URL'),
],
];
PHP;
$currentServices = file_get_contents($basePath . '/config/services.php');
if (strpos($currentServices, "'openrouter'") === false) {
writeFile($basePath . '/config/services.php', $currentServices . "\n" . $servicesConfig);
}
// Update Kernel.php to add middleware
$kernelPath = $basePath . '/app/Http/Kernel.php';
if (file_exists($kernelPath)) {
$kernel = file_get_contents($kernelPath);
if (strpos($kernel, "'role'") === false) {
$kernel = str_replace(
"protected \$routeMiddleware = [\n 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,",
"protected \$routeMiddleware = [\n 'role' => \Spatie\Permission\Middleware\RoleMiddleware::class,\n 'permission' => \Spatie\Permission\Middleware\PermissionMiddleware::class,\n 'auth' => \Illuminate\Auth\Middleware\Authenticate::class,",
$kernel
);
writeFile($kernelPath, $kernel);
}
}
// Update bootstrap/app.php for middleware aliases
$bootstrapApp = file_get_contents($basePath . '/bootstrap/app.php');
if (strpos($bootstrapApp, 'Spatie\\Permission\\PermissionServiceProvider') === false) {
$bootstrapApp = str_replace(
"->withRouting(\n __DIR__.'/../routes/web.php',",
"->withRouting(\n __DIR__.'/../routes/web.php',",
$bootstrapApp
);
$providersPath = $basePath . '/bootstrap/providers.php';
if (file_exists($providersPath)) {
$providers = file_get_contents($providersPath);
if (strpos($providers, 'Spatie\\Permission\\PermissionServiceProvider') === false) {
$providers = str_replace(
"return [\n //",
"return [\n \\Spatie\\Permission\\PermissionServiceProvider::class,\n //",
$providers
);
writeFile($providersPath, $providers);
}
}
}
// Create welcome view
$welcomeView = <<<'BLADE'
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Annunk - Learning Insights Platform</title>
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="font-sans antialiased bg-gray-50">
<div class="min-h-screen">
<nav class="bg-white border-b border-gray-100">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex items-center">
<a href="/" class="text-2xl font-bold text-indigo-600">Annunk</a>
</div>
<div class="flex items-center space-x-4">
@if (Route::has('login'))
@auth
<a href="{{ route('dashboard') }}" class="text-gray-900 hover:text-indigo-600">Dashboard</a>
@else
<a href="{{ route('login') }}" class="text-gray-900 hover:text-indigo-600">Log in</a>
@if (Route::has('register'))
<a href="{{ route('register') }}" class="bg-indigo-600 text-white px-4 py-2 rounded-lg hover:bg-indigo-700">Register</a>
@endif
@endauth
@endif
</div>
</div>
</div>
</nav>
<main>
<div class="bg-white">
<div class="max-w-7xl mx-auto py-16 px-4 sm:py-24 sm:px-6 lg:px-8">
<div class="text-center">
<h1 class="text-4xl font-extrabold text-gray-900 sm:text-5xl md:text-6xl">
<span class="block">Learning Insights</span>
<span class="block text-indigo-600">Powered by AI</span>
</h1>
<p class="mt-3 max-w-md mx-auto text-base text-gray-500 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
Annunk delivers personalized learning insights and training content through your dashboard and Telegram bot.
</p>
<div class="mt-5 max-w-md mx-auto sm:flex sm:justify-center md:mt-8">
<div class="rounded-md shadow">
<a href="{{ route('register') }}" class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 md:py-4 md:text-lg">
Get Started
</a>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
</body>
</html>
BLADE;
writeFile($basePath . '/resources/views/welcome.blade.php', $welcomeView);
echo "All views created.\n";
// Create additional necessary files
$additionalFiles = [
'app/Policies/SubjectPolicy.php' => <<<'PHP'
<?php
namespace App\Policies;
use App\Models\Subject;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
/**
* Policy for Subject model.
*/
class SubjectPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*/
public function viewAny(User $user): bool
{
return true;
}
/**
* Determine whether the user can view the model.
*/
public function view(User $user, Subject $subject): bool
{
return $subject->is_system || $subject->user_id === $user->id;
}
/**
* Determine whether the user can create models.
*/
public function create(User $user): bool
{
return true;
}
/**
* Determine whether the user can update the model.
*/
public function update(User $user, Subject $subject): bool
{
return $subject->user_id === $user->id || $user->hasRole('superadmin');
}
/**
* Determine whether the user can delete the model.
*/
public function delete(User $user, Subject $subject): bool
{
return $subject->user_id === $user->id || $user->hasRole('superadmin');
}
}
PHP,
];
foreach ($additionalFiles as $file => $content) {
writeFile($basePath . '/' . $file, $content);
}
echo "Additional files created.\n";
// Update AuthServiceProvider
$authServiceProvider = <<<'PHP'
<?php
namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use App\Models\Subject;
use App\Policies\SubjectPolicy;
/**
* Application authentication and authorization service provider.
*/
class AuthServiceProvider extends ServiceProvider
{
/**
* The model to policy mappings for the application.
*/
protected $policies = [
Subject::class => SubjectPolicy::class,
];
/**
* Register any authentication / authorization services.
*/
public function boot(): void
{
$this->registerPolicies();
Gate::before(function ($user, $ability) {
if ($user->hasRole('superadmin')) {
return true;
}
});
}
}
PHP;
writeFile($basePath . '/app/Providers/AuthServiceProvider.php', $authServiceProvider);
echo "AuthServiceProvider created.\n";
// Create routes for API and channels
$apiRoutes = <<<'PHP'
<?php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
Route::get('/health', function () {
return response()->json(['status' => 'ok']);
});
PHP;
writeFile($basePath . '/routes/api.php', $apiRoutes);
$channelsRoutes = <<<'PHP'
<?php
use Illuminate\Support\Facades\Broadcast;
/*
|--------------------------------------------------------------------------
| Broadcast Channels
|--------------------------------------------------------------------------
|
| Here you may register all of the event broadcasting channels that your
| application supports. The given channel authorization callbacks are
| used to check if an authenticated user can listen to the channel.
|
*/
Broadcast::channel('App.Models.User.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
PHP;
writeFile($basePath . '/routes/channels.php', $channelsRoutes);
echo "Routes created.\n";
// Create composer.json additions if needed
$composer = json_decode(file_get_contents($basePath . '/composer.json'), true);
if (!isset($composer['autoload']['psr-4']['App\\Policies\\'])) {
$composer['autoload']['psr-4']['App\\'] = 'app/';
file_put_contents($basePath . '/composer.json', json_encode($composer, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
echo "\n=== Annunk Application Structure Created Successfully ===\n";
echo "Next steps:\n";
echo "1. Run: docker exec dockerank2 bash -c 'cd /var/www/html/ank2 && composer dump-autoload'\n";
echo "2. Run: docker exec dockerank2 bash -c 'cd /var/www/html/ank2 && php artisan migrate --force'\n";
echo "3. Run: docker exec dockerank2 bash -c 'cd /var/www/html/ank2 && php artisan db:seed --force'\n";
echo "4. Access: http://localhost:16790\n";
echo "5. SuperAdmin login: marcocosta@gmx.com / superadmin123456\n";