1944 lines
65 KiB
PHP
Executable File
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";
|