Fix impersonation routing, invoice numbers, dashboard caching, indexes, and Stripe property name
- Add impersonation stop route on account subdomain so impersonated users can exit (#11) - Replace non-concurrency-safe invoice number generation (count+1, rand) with date+random string - Wrap admin dashboard stats queries in Cache::remember with 5-minute TTL - Add database indexes on invoices.status, orders.status, audit_logs.action, audit_logs.created_at - Fix last_four to last4 matching Stripe's actual card property name Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -91,7 +91,7 @@ class BillingController extends Controller
|
|||||||
$paymentMethods[] = [
|
$paymentMethods[] = [
|
||||||
'id' => $method->id,
|
'id' => $method->id,
|
||||||
'brand' => $method->card->brand ?? 'unknown',
|
'brand' => $method->card->brand ?? 'unknown',
|
||||||
'last_four' => $method->card->last_four ?? '****',
|
'last_four' => $method->card->last4 ?? '****',
|
||||||
'exp_month' => $method->card->exp_month,
|
'exp_month' => $method->card->exp_month,
|
||||||
'exp_year' => $method->card->exp_year,
|
'exp_year' => $method->card->exp_year,
|
||||||
'is_default' => $method->id === $defaultPaymentMethod,
|
'is_default' => $method->id === $defaultPaymentMethod,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use App\Models\PaymentTransaction;
|
|||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
@@ -19,181 +20,185 @@ class DashboardController extends Controller
|
|||||||
{
|
{
|
||||||
public function index(): Response
|
public function index(): Response
|
||||||
{
|
{
|
||||||
$totalCustomers = User::role('customer')->count();
|
$stats = Cache::remember('admin.dashboard.stats', 300, function () {
|
||||||
|
$totalCustomers = User::role('customer')->count();
|
||||||
|
|
||||||
// MRR: sum of plan prices for active subscriptions
|
// MRR: sum of plan prices for active subscriptions
|
||||||
$mrr = Subscription::query()
|
$mrr = (float) Subscription::query()
|
||||||
->where('stripe_status', 'active')
|
->where('stripe_status', 'active')
|
||||||
->whereNotNull('plan_id')
|
->whereNotNull('plan_id')
|
||||||
->join('plans', 'subscriptions.plan_id', '=', 'plans.id')
|
->join('plans', 'subscriptions.plan_id', '=', 'plans.id')
|
||||||
->sum('plans.price');
|
->sum('plans.price');
|
||||||
|
|
||||||
// Total revenue: sum of paid invoice totals
|
// Total revenue: sum of paid invoice totals
|
||||||
$totalRevenue = Invoice::query()
|
$totalRevenue = (float) Invoice::query()
|
||||||
->where('status', 'paid')
|
->where('status', 'paid')
|
||||||
->sum('total');
|
->sum('total');
|
||||||
|
|
||||||
$activeServices = Service::query()
|
$activeServices = Service::query()
|
||||||
->where('status', 'active')
|
->where('status', 'active')
|
||||||
->count();
|
|
||||||
|
|
||||||
// Pending invoices
|
|
||||||
$pendingInvoicesCount = Invoice::query()
|
|
||||||
->where('status', 'pending')
|
|
||||||
->count();
|
|
||||||
|
|
||||||
$pendingInvoicesAmount = Invoice::query()
|
|
||||||
->where('status', 'pending')
|
|
||||||
->sum('total');
|
|
||||||
|
|
||||||
// Overdue invoices
|
|
||||||
$overdueCount = Invoice::query()
|
|
||||||
->where('status', 'overdue')
|
|
||||||
->count();
|
|
||||||
|
|
||||||
$overdueAmount = Invoice::query()
|
|
||||||
->where('status', 'overdue')
|
|
||||||
->sum('total');
|
|
||||||
|
|
||||||
// Recent invoices with user info
|
|
||||||
$recentInvoices = Invoice::query()
|
|
||||||
->with('user:id,name,email')
|
|
||||||
->latest()
|
|
||||||
->limit(10)
|
|
||||||
->get(['id', 'user_id', 'number', 'total', 'status', 'gateway', 'created_at']);
|
|
||||||
|
|
||||||
// Recent subscriptions with user and plan
|
|
||||||
$recentSubscriptions = Subscription::query()
|
|
||||||
->select([
|
|
||||||
'subscriptions.id',
|
|
||||||
'subscriptions.user_id',
|
|
||||||
'subscriptions.plan_id',
|
|
||||||
'subscriptions.type',
|
|
||||||
'subscriptions.stripe_status',
|
|
||||||
'subscriptions.gateway',
|
|
||||||
'subscriptions.created_at',
|
|
||||||
])
|
|
||||||
->leftJoin('plans', 'subscriptions.plan_id', '=', 'plans.id')
|
|
||||||
->addSelect([
|
|
||||||
'plans.name as plan_name',
|
|
||||||
'plans.price as plan_price',
|
|
||||||
'plans.billing_cycle as plan_billing_cycle',
|
|
||||||
])
|
|
||||||
->leftJoin('users', 'subscriptions.user_id', '=', 'users.id')
|
|
||||||
->addSelect([
|
|
||||||
'users.name as user_name',
|
|
||||||
'users.email as user_email',
|
|
||||||
])
|
|
||||||
->orderByDesc('subscriptions.created_at')
|
|
||||||
->limit(10)
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// Popular plans: ordered by active subscription count
|
|
||||||
$popularPlans = Plan::query()
|
|
||||||
->withCount(['services as active_services_count' => function ($query): void {
|
|
||||||
$query->where('status', 'active');
|
|
||||||
}])
|
|
||||||
->where('status', 'active')
|
|
||||||
->orderByDesc('active_services_count')
|
|
||||||
->limit(8)
|
|
||||||
->get(['id', 'name', 'service_type', 'price', 'billing_cycle']);
|
|
||||||
|
|
||||||
// Revenue by service type from plans linked through invoices
|
|
||||||
$revenueByServiceType = Invoice::query()
|
|
||||||
->where('invoices.status', 'paid')
|
|
||||||
->join('subscriptions', 'invoices.subscription_id', '=', 'subscriptions.id')
|
|
||||||
->join('plans', 'subscriptions.plan_id', '=', 'plans.id')
|
|
||||||
->select('plans.service_type', DB::raw('SUM(invoices.total) as revenue'), DB::raw('COUNT(invoices.id) as invoice_count'))
|
|
||||||
->groupBy('plans.service_type')
|
|
||||||
->orderByDesc('revenue')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
// New customers this month
|
|
||||||
$newCustomersThisMonth = User::role('customer')
|
|
||||||
->where('created_at', '>=', now()->startOfMonth())
|
|
||||||
->count();
|
|
||||||
|
|
||||||
// Revenue this month
|
|
||||||
$revenueThisMonth = Invoice::query()
|
|
||||||
->where('status', 'paid')
|
|
||||||
->where('paid_at', '>=', now()->startOfMonth())
|
|
||||||
->sum('total');
|
|
||||||
|
|
||||||
// ARR (Annual Recurring Revenue)
|
|
||||||
$arr = (float) $mrr * 12;
|
|
||||||
|
|
||||||
// Monthly Revenue Trend (last 12 months)
|
|
||||||
$revenueByMonth = PaymentTransaction::query()
|
|
||||||
->where('status', 'completed')
|
|
||||||
->where('created_at', '>=', now()->subMonths(12))
|
|
||||||
->selectRaw("DATE_FORMAT(created_at, '%Y-%m') as month, SUM(amount) as total")
|
|
||||||
->groupBy('month')
|
|
||||||
->orderBy('month')
|
|
||||||
->get()
|
|
||||||
->map(fn ($row) => ['month' => $row->month, 'total' => (float) $row->total]);
|
|
||||||
|
|
||||||
// Customer Growth (last 12 months - new signups per month)
|
|
||||||
$customerGrowth = User::role('customer')
|
|
||||||
->where('created_at', '>=', now()->subMonths(12))
|
|
||||||
->selectRaw("DATE_FORMAT(created_at, '%Y-%m') as month, COUNT(*) as count")
|
|
||||||
->groupBy('month')
|
|
||||||
->orderBy('month')
|
|
||||||
->get()
|
|
||||||
->map(fn ($row) => ['month' => $row->month, 'count' => (int) $row->count]);
|
|
||||||
|
|
||||||
// Churn Rate (subscriptions cancelled vs total in last 6 months)
|
|
||||||
$churnData = [];
|
|
||||||
for ($i = 5; $i >= 0; $i--) {
|
|
||||||
$monthStart = now()->subMonths($i)->startOfMonth();
|
|
||||||
$monthEnd = now()->subMonths($i)->endOfMonth();
|
|
||||||
|
|
||||||
$totalAtStart = Subscription::query()
|
|
||||||
->where('created_at', '<', $monthStart)
|
|
||||||
->where(function ($query) use ($monthStart): void {
|
|
||||||
$query->whereNull('cancelled_at')
|
|
||||||
->orWhere('cancelled_at', '>', $monthStart);
|
|
||||||
})
|
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
$cancelled = Subscription::query()
|
// Pending invoices
|
||||||
->whereBetween('cancelled_at', [$monthStart, $monthEnd])
|
$pendingInvoicesCount = Invoice::query()
|
||||||
|
->where('status', 'pending')
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
$churnData[] = [
|
$pendingInvoicesAmount = (float) Invoice::query()
|
||||||
'month' => $monthStart->format('Y-m'),
|
->where('status', 'pending')
|
||||||
'rate' => $totalAtStart > 0 ? round(($cancelled / $totalAtStart) * 100, 1) : 0,
|
->sum('total');
|
||||||
'cancelled' => $cancelled,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overdue Invoices (detailed list)
|
// Overdue invoices
|
||||||
$overdueInvoices = Invoice::query()
|
$overdueCount = Invoice::query()
|
||||||
->where('status', 'overdue')
|
->where('status', 'overdue')
|
||||||
->with('user:id,name,email')
|
->count();
|
||||||
->latest('due_date')
|
|
||||||
->take(10)
|
|
||||||
->get(['id', 'user_id', 'number', 'total', 'due_date', 'status']);
|
|
||||||
|
|
||||||
return Inertia::render('Admin/Dashboard', [
|
$overdueAmount = (float) Invoice::query()
|
||||||
'totalCustomers' => $totalCustomers,
|
->where('status', 'overdue')
|
||||||
'mrr' => (float) $mrr,
|
->sum('total');
|
||||||
'totalRevenue' => (float) $totalRevenue,
|
|
||||||
'activeServices' => $activeServices,
|
// Recent invoices with user info
|
||||||
'pendingInvoicesCount' => $pendingInvoicesCount,
|
$recentInvoices = Invoice::query()
|
||||||
'pendingInvoicesAmount' => (float) $pendingInvoicesAmount,
|
->with('user:id,name,email')
|
||||||
'overdueCount' => $overdueCount,
|
->latest()
|
||||||
'overdueAmount' => (float) $overdueAmount,
|
->limit(10)
|
||||||
'recentInvoices' => $recentInvoices,
|
->get(['id', 'user_id', 'number', 'total', 'status', 'gateway', 'created_at']);
|
||||||
'recentSubscriptions' => $recentSubscriptions,
|
|
||||||
'popularPlans' => $popularPlans,
|
// Recent subscriptions with user and plan
|
||||||
'revenueByServiceType' => $revenueByServiceType,
|
$recentSubscriptions = Subscription::query()
|
||||||
'newCustomersThisMonth' => $newCustomersThisMonth,
|
->select([
|
||||||
'revenueThisMonth' => (float) $revenueThisMonth,
|
'subscriptions.id',
|
||||||
'arr' => $arr,
|
'subscriptions.user_id',
|
||||||
'revenueByMonth' => $revenueByMonth,
|
'subscriptions.plan_id',
|
||||||
'customerGrowth' => $customerGrowth,
|
'subscriptions.type',
|
||||||
'churnData' => $churnData,
|
'subscriptions.stripe_status',
|
||||||
'overdueInvoices' => $overdueInvoices,
|
'subscriptions.gateway',
|
||||||
]);
|
'subscriptions.created_at',
|
||||||
|
])
|
||||||
|
->leftJoin('plans', 'subscriptions.plan_id', '=', 'plans.id')
|
||||||
|
->addSelect([
|
||||||
|
'plans.name as plan_name',
|
||||||
|
'plans.price as plan_price',
|
||||||
|
'plans.billing_cycle as plan_billing_cycle',
|
||||||
|
])
|
||||||
|
->leftJoin('users', 'subscriptions.user_id', '=', 'users.id')
|
||||||
|
->addSelect([
|
||||||
|
'users.name as user_name',
|
||||||
|
'users.email as user_email',
|
||||||
|
])
|
||||||
|
->orderByDesc('subscriptions.created_at')
|
||||||
|
->limit(10)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// Popular plans: ordered by active subscription count
|
||||||
|
$popularPlans = Plan::query()
|
||||||
|
->withCount(['services as active_services_count' => function ($query): void {
|
||||||
|
$query->where('status', 'active');
|
||||||
|
}])
|
||||||
|
->where('status', 'active')
|
||||||
|
->orderByDesc('active_services_count')
|
||||||
|
->limit(8)
|
||||||
|
->get(['id', 'name', 'service_type', 'price', 'billing_cycle']);
|
||||||
|
|
||||||
|
// Revenue by service type from plans linked through invoices
|
||||||
|
$revenueByServiceType = Invoice::query()
|
||||||
|
->where('invoices.status', 'paid')
|
||||||
|
->join('subscriptions', 'invoices.subscription_id', '=', 'subscriptions.id')
|
||||||
|
->join('plans', 'subscriptions.plan_id', '=', 'plans.id')
|
||||||
|
->select('plans.service_type', DB::raw('SUM(invoices.total) as revenue'), DB::raw('COUNT(invoices.id) as invoice_count'))
|
||||||
|
->groupBy('plans.service_type')
|
||||||
|
->orderByDesc('revenue')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
// New customers this month
|
||||||
|
$newCustomersThisMonth = User::role('customer')
|
||||||
|
->where('created_at', '>=', now()->startOfMonth())
|
||||||
|
->count();
|
||||||
|
|
||||||
|
// Revenue this month
|
||||||
|
$revenueThisMonth = (float) Invoice::query()
|
||||||
|
->where('status', 'paid')
|
||||||
|
->where('paid_at', '>=', now()->startOfMonth())
|
||||||
|
->sum('total');
|
||||||
|
|
||||||
|
// ARR (Annual Recurring Revenue)
|
||||||
|
$arr = $mrr * 12;
|
||||||
|
|
||||||
|
// Monthly Revenue Trend (last 12 months)
|
||||||
|
$revenueByMonth = PaymentTransaction::query()
|
||||||
|
->where('status', 'completed')
|
||||||
|
->where('created_at', '>=', now()->subMonths(12))
|
||||||
|
->selectRaw("DATE_FORMAT(created_at, '%Y-%m') as month, SUM(amount) as total")
|
||||||
|
->groupBy('month')
|
||||||
|
->orderBy('month')
|
||||||
|
->get()
|
||||||
|
->map(fn ($row) => ['month' => $row->month, 'total' => (float) $row->total]);
|
||||||
|
|
||||||
|
// Customer Growth (last 12 months - new signups per month)
|
||||||
|
$customerGrowth = User::role('customer')
|
||||||
|
->where('created_at', '>=', now()->subMonths(12))
|
||||||
|
->selectRaw("DATE_FORMAT(created_at, '%Y-%m') as month, COUNT(*) as count")
|
||||||
|
->groupBy('month')
|
||||||
|
->orderBy('month')
|
||||||
|
->get()
|
||||||
|
->map(fn ($row) => ['month' => $row->month, 'count' => (int) $row->count]);
|
||||||
|
|
||||||
|
// Churn Rate (subscriptions cancelled vs total in last 6 months)
|
||||||
|
$churnData = [];
|
||||||
|
for ($i = 5; $i >= 0; $i--) {
|
||||||
|
$monthStart = now()->subMonths($i)->startOfMonth();
|
||||||
|
$monthEnd = now()->subMonths($i)->endOfMonth();
|
||||||
|
|
||||||
|
$totalAtStart = Subscription::query()
|
||||||
|
->where('created_at', '<', $monthStart)
|
||||||
|
->where(function ($query) use ($monthStart): void {
|
||||||
|
$query->whereNull('cancelled_at')
|
||||||
|
->orWhere('cancelled_at', '>', $monthStart);
|
||||||
|
})
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$cancelled = Subscription::query()
|
||||||
|
->whereBetween('cancelled_at', [$monthStart, $monthEnd])
|
||||||
|
->count();
|
||||||
|
|
||||||
|
$churnData[] = [
|
||||||
|
'month' => $monthStart->format('Y-m'),
|
||||||
|
'rate' => $totalAtStart > 0 ? round(($cancelled / $totalAtStart) * 100, 1) : 0,
|
||||||
|
'cancelled' => $cancelled,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overdue Invoices (detailed list)
|
||||||
|
$overdueInvoices = Invoice::query()
|
||||||
|
->where('status', 'overdue')
|
||||||
|
->with('user:id,name,email')
|
||||||
|
->latest('due_date')
|
||||||
|
->take(10)
|
||||||
|
->get(['id', 'user_id', 'number', 'total', 'due_date', 'status']);
|
||||||
|
|
||||||
|
return compact(
|
||||||
|
'totalCustomers',
|
||||||
|
'mrr',
|
||||||
|
'totalRevenue',
|
||||||
|
'activeServices',
|
||||||
|
'pendingInvoicesCount',
|
||||||
|
'pendingInvoicesAmount',
|
||||||
|
'overdueCount',
|
||||||
|
'overdueAmount',
|
||||||
|
'recentInvoices',
|
||||||
|
'recentSubscriptions',
|
||||||
|
'popularPlans',
|
||||||
|
'revenueByServiceType',
|
||||||
|
'newCustomersThisMonth',
|
||||||
|
'revenueThisMonth',
|
||||||
|
'arr',
|
||||||
|
'revenueByMonth',
|
||||||
|
'customerGrowth',
|
||||||
|
'churnData',
|
||||||
|
'overdueInvoices',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return Inertia::render('Admin/Dashboard', $stats);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ use Barryvdh\DomPDF\Facade\Pdf;
|
|||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Inertia\Inertia;
|
use Inertia\Inertia;
|
||||||
use Inertia\Response;
|
use Inertia\Response;
|
||||||
|
|
||||||
@@ -75,7 +76,7 @@ class InvoiceController extends Controller
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Generate unique invoice number
|
// Generate unique invoice number
|
||||||
$number = 'INV-'.str_pad((string) (Invoice::query()->count() + 1), 6, '0', STR_PAD_LEFT);
|
$number = 'INV-'.now()->format('Ymd').'-'.strtoupper(Str::random(6));
|
||||||
|
|
||||||
$status = ($validated['send_immediately'] ?? false) ? 'pending' : 'draft';
|
$status = ($validated['send_immediately'] ?? false) ? 'pending' : 'draft';
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use App\Models\User;
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Cashier\Subscription;
|
use Laravel\Cashier\Subscription;
|
||||||
|
|
||||||
class PayPalWebhookController extends Controller
|
class PayPalWebhookController extends Controller
|
||||||
@@ -188,7 +189,7 @@ class PayPalWebhookController extends Controller
|
|||||||
'subscription_id' => $subscription->id,
|
'subscription_id' => $subscription->id,
|
||||||
'gateway' => 'paypal',
|
'gateway' => 'paypal',
|
||||||
'gateway_invoice_id' => $resource['id'] ?? null,
|
'gateway_invoice_id' => $resource['id'] ?? null,
|
||||||
'number' => config('billing.invoice.prefix').'-'.now()->format('Ymd').'-'.rand(1000, 9999),
|
'number' => config('billing.invoice.prefix', 'INV').'-'.now()->format('Ymd').'-'.strtoupper(Str::random(6)),
|
||||||
'total' => (float) ($resource['amount']['total'] ?? 0),
|
'total' => (float) ($resource['amount']['total'] ?? 0),
|
||||||
'tax' => 0,
|
'tax' => 0,
|
||||||
'currency' => strtoupper($resource['amount']['currency'] ?? 'USD'),
|
'currency' => strtoupper($resource['amount']['currency'] ?? 'USD'),
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use App\Models\Invoice;
|
|||||||
use App\Models\PaymentTransaction;
|
use App\Models\PaymentTransaction;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Cashier\Http\Controllers\WebhookController;
|
use Laravel\Cashier\Http\Controllers\WebhookController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
@@ -123,7 +124,7 @@ class StripeWebhookController extends WebhookController
|
|||||||
'user_id' => $user->id,
|
'user_id' => $user->id,
|
||||||
'subscription_id' => $subscription?->id,
|
'subscription_id' => $subscription?->id,
|
||||||
'gateway' => 'stripe',
|
'gateway' => 'stripe',
|
||||||
'number' => $stripeInvoice['number'] ?? config('billing.invoice.prefix').'-'.now()->format('Ymd').'-'.rand(1000, 9999),
|
'number' => $stripeInvoice['number'] ?? config('billing.invoice.prefix', 'INV').'-'.now()->format('Ymd').'-'.strtoupper(Str::random(6)),
|
||||||
'total' => ($stripeInvoice['amount_paid'] ?? 0) / 100,
|
'total' => ($stripeInvoice['amount_paid'] ?? 0) / 100,
|
||||||
'tax' => ($stripeInvoice['tax'] ?? 0) / 100,
|
'tax' => ($stripeInvoice['tax'] ?? 0) / 100,
|
||||||
'currency' => strtoupper($stripeInvoice['currency'] ?? 'usd'),
|
'currency' => strtoupper($stripeInvoice['currency'] ?? 'usd'),
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->index('status');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('orders', function (Blueprint $table) {
|
||||||
|
$table->index('status');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('audit_logs', function (Blueprint $table) {
|
||||||
|
$table->index('action');
|
||||||
|
$table->index('created_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->dropIndex(['status']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('orders', function (Blueprint $table) {
|
||||||
|
$table->dropIndex(['status']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::table('audit_logs', function (Blueprint $table) {
|
||||||
|
$table->dropIndex(['action']);
|
||||||
|
$table->dropIndex(['created_at']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -15,6 +15,9 @@ use App\Http\Controllers\Account\UpgradeController;
|
|||||||
use App\Http\Controllers\Account\VpsController;
|
use App\Http\Controllers\Account\VpsController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
// Impersonation stop (must be on account subdomain since impersonated user lacks admin role)
|
||||||
|
Route::post('/impersonate/stop', [\App\Http\Controllers\Admin\ImpersonationController::class, 'stop'])->name('impersonation.stop');
|
||||||
|
|
||||||
Route::get('/dashboard', [DashboardController::class, 'index'])->name('account.dashboard');
|
Route::get('/dashboard', [DashboardController::class, 'index'])->name('account.dashboard');
|
||||||
|
|
||||||
Route::get('/profile', [ProfileController::class, 'show'])->name('account.profile');
|
Route::get('/profile', [ProfileController::class, 'show'])->name('account.profile');
|
||||||
|
|||||||
Reference in New Issue
Block a user