First Commit

This commit is contained in:
marco
2026-09-01 07:49:48 -03:00
commit 28b1044038
399 changed files with 41659 additions and 0 deletions
+153
View File
@@ -0,0 +1,153 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use App\Models\Invoice;
use App\Models\SubscriptionPlan;
use App\Models\User;
use App\Models\UserNotification;
use App\Models\UserSubscription;
use Barryvdh\DomPDF\Facade\Pdf;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;
class BillingController extends Controller
{
public function index(): View
{
$stats = [
'total_plans' => SubscriptionPlan::count(),
'active_plans' => SubscriptionPlan::where('is_active', true)->count(),
'active_subscriptions' => UserSubscription::where('status', 'active')->count(),
'total_revenue' => Invoice::where('status', 'paid')->sum('amount'),
'pending_invoices' => Invoice::where('status', 'pending')->count(),
'expiring_soon' => UserSubscription::where('status', 'active')
->where('end_date', '<=', now()->addDays(7))
->count(),
];
$plans = SubscriptionPlan::withCount('userSubscriptions')->get();
$subscriptions = UserSubscription::with(['user', 'plan'])
->orderByDesc('created_at')
->paginate(20);
return view('superadmin.billing.index', compact('stats', 'plans', 'subscriptions'));
}
public function storePlan(Request $request): RedirectResponse
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'price' => ['required', 'numeric', 'min:0'],
'currency' => ['required', 'string', 'size:3'],
'duration_days' => ['required', 'integer', 'min:1'],
'token_limit' => ['nullable', 'integer', 'min:0'],
]);
SubscriptionPlan::create($validated + ['is_active' => true]);
return back()->with('success', __('Plan created successfully.'));
}
public function updatePlan(Request $request, SubscriptionPlan $plan): RedirectResponse
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'price' => ['required', 'numeric', 'min:0'],
'currency' => ['required', 'string', 'size:3'],
'duration_days' => ['required', 'integer', 'min:1'],
'token_limit' => ['nullable', 'integer', 'min:0'],
'is_active' => ['boolean'],
]);
$plan->update($validated);
return back()->with('success', __('Plan updated successfully.'));
}
public function approvePayment(UserSubscription $subscription): RedirectResponse
{
$subscription->update(['payment_approved' => true, 'status' => 'active']);
// Create invoice
$lastInvoice = Invoice::orderByDesc('id')->first();
$nextNumber = $lastInvoice ? (int)filter_var($lastInvoice->invoice_number, FILTER_SANITIZE_NUMBER_INT) + 1 : 1001;
$invoice = Invoice::create([
'user_id' => $subscription->user_id,
'user_subscription_id' => $subscription->id,
'invoice_number' => 'INV-' . $nextNumber . '/' . now()->year,
'amount' => $subscription->plan->price,
'currency' => $subscription->plan->currency,
'status' => 'paid',
'payment_method' => 'manual',
'paid_at' => now(),
]);
// Generate PDF
$this->generateInvoicePdf($invoice);
// Notify user
UserNotification::create([
'user_id' => $subscription->user_id,
'type' => 'payment_received',
'title' => __('Payment Approved'),
'message' => __('Your payment of :amount has been approved. Your subscription is active until :date.', [
'amount' => 'R$ ' . number_format($subscription->plan->price, 2, ',', '.'),
'date' => $subscription->end_date->format('d/m/Y'),
]),
]);
return back()->with('success', __('Payment approved. Invoice generated and user notified.'));
}
public function cancelSubscription(UserSubscription $subscription): RedirectResponse
{
$subscription->update(['status' => 'cancelled', 'cancelled_at' => now()]);
UserNotification::create([
'user_id' => $subscription->user_id,
'type' => 'admin_message',
'title' => __('Subscription Cancelled'),
'message' => __('Your subscription has been cancelled by the administrator.'),
]);
return back()->with('success', __('Subscription cancelled.'));
}
public function invoices(): View
{
$invoices = Invoice::with(['user', 'subscription.plan'])
->orderByDesc('created_at')
->paginate(20);
return view('superadmin.billing.invoices', compact('invoices'));
}
public function downloadInvoice(Invoice $invoice)
{
if ($invoice->pdf_path && Storage::exists($invoice->pdf_path)) {
return Storage::download($invoice->pdf_path, "invoice-{$invoice->invoice_number}.pdf");
}
// Generate on the fly
$this->generateInvoicePdf($invoice);
return Storage::download($invoice->pdf_path, "invoice-{$invoice->invoice_number}.pdf");
}
private function generateInvoicePdf(Invoice $invoice): void
{
$pdf = Pdf::loadView('pdf.invoice', compact('invoice'));
$path = "invoices/invoice-{$invoice->invoice_number}.pdf";
Storage::put($path, $pdf->output());
$invoice->update(['pdf_path' => $path]);
}
}
+210
View File
@@ -0,0 +1,210 @@
<?php
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use App\Models\Subject;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class SystemController extends Controller
{
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(),
];
$subjects = Subject::all();
$aiDefaultProvider = \App\Models\Setting::where('key', 'ai_default_provider')->value('value');
$aiDefaultModel = \App\Models\Setting::where('key', 'ai_default_model')->value('value');
if (!$aiDefaultProvider) {
$aiDefaultProvider = config('services.ai.default_provider', 'openrouter');
}
if (!$aiDefaultModel) {
$aiDefaultModel = config('services.ai.providers.'.$aiDefaultProvider.'.model', 'inclusionai/ling-3.0-tiny:free');
}
$chatDefaultProvider = \App\Models\Setting::where('key', 'chat_default_provider')->value('value');
$chatDefaultModel = \App\Models\Setting::where('key', 'chat_default_model')->value('value');
if (!$chatDefaultProvider) {
$chatDefaultProvider = config('services.ai.default_provider', 'openrouter');
}
if (!$chatDefaultModel) {
$chatDefaultModel = config('services.ai.providers.'.$chatDefaultProvider.'.model', 'inclusionai/ling-3.0-tiny:free');
}
return view("superadmin.system.index", compact(
"stats",
"subjects",
"aiDefaultProvider",
"aiDefaultModel",
"chatDefaultProvider",
"chatDefaultModel"
));
}
public function updateAiSettings(Request $request): RedirectResponse
{
$request->validate([
"ai_default_provider" => ["required", "string", "max:50"],
"ai_default_model" => ["required", "string", "max:255"],
]);
$provider = $request->input("ai_default_provider");
$model = $request->input("ai_default_model");
\App\Models\Setting::updateOrCreate(
["key" => "ai_default_provider"],
["value" => $provider, "type" => "string"]
);
\App\Models\Setting::updateOrCreate(
["key" => "ai_default_model"],
["value" => $model, "type" => "string"]
);
return back()->with("success", __("AI settings updated successfully."));
}
public function updateChatSettings(Request $request): RedirectResponse
{
$request->validate([
"chat_default_provider" => ["required", "string", "max:50"],
"chat_default_model" => ["required", "string", "max:255"],
]);
$provider = $request->input("chat_default_provider");
$model = $request->input("chat_default_model");
\App\Models\Setting::updateOrCreate(
["key" => "chat_default_provider"],
["value" => $provider, "type" => "string"]
);
\App\Models\Setting::updateOrCreate(
["key" => "chat_default_model"],
["value" => $model, "type" => "string"]
);
return back()->with("success", __("Chat AI settings updated successfully."));
}
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.");
}
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.");
}
public function destroySubject(Subject $subject)
{
$subject->delete();
return back()->with("success", "System subject deleted successfully.");
}
public function generateAiSubject(Request $request): RedirectResponse
{
$request->validate([
"theme" => ["required", "string", "max:255"],
]);
$theme = $request->input("theme");
$prompt = "Crie um assunto de aprendizado e treinamento sobre o tema: {$theme}. Retorne APENAS um objeto JSON válido (sem texto adicional, sem markdown, sem code blocks) com os campos: name (string, máximo 255 caracteres, formato: 'Aprendizado e Treinamento de [Tema]'), description (string, máximo 1000 caracteres), introduction (string, máximo 5000 caracteres). Todo o conteúdo deve estar em Português do Brasil. O assunto deve ser educativo, prático e adequado para geração de insights de aprendizagem pela IA.";
/** @var \App\Services\AIService $ai */
$ai = app(\App\Services\AIService::class);
$result = $ai->generateInsight($theme, $prompt, ["language" => "pt_BR"]);
if (!$result["success"]) {
return back()->with("error", __("Failed to generate subject: :error", ["error" => $result["error"] ?? __("Unknown error")]));
}
$content = $result["content"];
$data = $this->extractJsonFromAiResponse($content);
if (!$data || !isset($data["name"])) {
$data = [
"name" => "Aprendizado e Treinamento de {$theme}",
"description" => $theme,
"introduction" => $content,
];
}
Subject::create([
"user_id" => null,
"name" => $data["name"],
"description" => $data["description"] ?? null,
"introduction" => $data["introduction"] ?? null,
"is_system" => true,
"is_active" => true,
]);
return back()->with("success", __("System subject generated successfully."));
}
private function extractJsonFromAiResponse(string $content): ?array
{
$content = trim($content);
if (str_starts_with($content, "```")) {
$content = preg_replace("/^```(?:json)?\s*/", "", $content);
$content = preg_replace("/\s*```$/", "", $content);
}
$data = json_decode($content, true);
if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
return $data;
}
if (preg_match("/\{.*\}/s", $content, $matches)) {
$data = json_decode($matches[0], true);
if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
return $data;
}
}
return null;
}
}
+158
View File
@@ -0,0 +1,158 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\SuperAdmin;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\View\View;
/**
* Controller for SuperAdmin user management.
* Handles listing, approving, editing, pausing, and managing users.
*/
class UserController extends Controller
{
/**
* Display a listing of all users.
*/
public function index(Request $request): View
{
$query = User::where('id', '!=', Auth::id());
// Filter by status
if ($request->has('status')) {
match ($request->status) {
'pending' => $query->where('is_approved', false),
'approved' => $query->where('is_approved', true),
'paused' => $query->where('is_active', false),
'verified' => $query->whereNotNull('email_verified_at'),
'unverified' => $query->whereNull('email_verified_at'),
default => null,
};
}
// Search by name or email
if ($search = $request->input('search')) {
$query->where(function ($q) use ($search) {
$q->where('name', 'like', "%{$search}%")
->orWhere('email', 'like', "%{$search}%");
});
}
$users = $query->orderByDesc('created_at')->paginate(20);
// Stats
$stats = [
'total' => User::count(),
'pending' => User::where('is_approved', false)->count(),
'active' => User::where('is_active', true)->count(),
'paused' => User::where('is_active', false)->count(),
'unverified' => User::whereNull('email_verified_at')->count(),
];
return view('superadmin.users.index', compact('users', 'stats'));
}
/**
* Approve a user account.
*/
public function approve(User $user): RedirectResponse
{
$user->update(['is_approved' => true]);
return back()->with('success', __('User :name approved successfully.', ['name' => $user->name]));
}
/**
* Reject/unapprove a user account.
*/
public function reject(User $user): RedirectResponse
{
$user->update(['is_approved' => false]);
return back()->with('success', __('User :name rejected successfully.', ['name' => $user->name]));
}
/**
* Toggle user active status (pause/activate).
*/
public function toggleActive(User $user): RedirectResponse
{
$user->update(['is_active' => !$user->is_active]);
$status = $user->is_active ? __('activated') : __('paused');
return back()->with('success', __("User :name {$status}.", ['name' => $user->name]));
}
/**
* Mark user email as verified without requiring email confirmation.
*/
public function verifyEmail(User $user): RedirectResponse
{
if ($user->hasVerifiedEmail()) {
return back()->with('info', __('User :name email is already verified.', ['name' => $user->name]));
}
$user->markEmailAsVerified();
return back()->with('success', __('User :name email verified successfully.', ['name' => $user->name]));
}
/**
* Show the edit form for a user.
*/
public function edit(User $user): View
{
return view('superadmin.users.edit', compact('user'));
}
/**
* Update user details.
*/
public function update(Request $request, User $user): RedirectResponse
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,' . $user->id],
'role' => ['required', 'string', 'in:admin,superadmin'],
'token_limit' => ['nullable', 'integer', 'min:0'],
'password' => ['nullable', 'string', 'min:8', 'confirmed'],
]);
$user->update([
'name' => $validated['name'],
'email' => $validated['email'],
'token_limit' => $validated['token_limit'] !== null && $validated['token_limit'] !== ''
? (int) $validated['token_limit']
: null,
]);
if (!empty($validated['password'])) {
$user->update(['password' => Hash::make($validated['password'])]);
}
// Sync role
$user->syncRoles([$validated['role']]);
return redirect()->route('superadmin.users.index')
->with('success', __('User :name updated successfully.', ['name' => $user->name]));
}
/**
* Delete a user account.
*/
public function destroy(User $user): RedirectResponse
{
$name = $user->name;
$user->delete();
return back()->with('success', __('User :name deleted successfully.', ['name' => $name]));
}
}