373 lines
14 KiB
PHP
373 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Pages;
|
|
|
|
use App\Models\ActivationKey;
|
|
use App\Models\Plan;
|
|
use BackedEnum;
|
|
use Filament\Actions\BulkAction;
|
|
use Filament\Forms\Components\Select;
|
|
use Filament\Forms\Components\Textarea;
|
|
use Filament\Forms\Components\TextInput;
|
|
use Filament\Forms\Concerns\InteractsWithForms;
|
|
use Filament\Forms\Contracts\HasForms;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Filament\Tables\Columns\BadgeColumn;
|
|
use Filament\Tables\Columns\TextColumn;
|
|
use Filament\Tables\Concerns\InteractsWithTable;
|
|
use Filament\Tables\Contracts\HasTable;
|
|
use Filament\Tables\Filters\SelectFilter;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
use UnitEnum;
|
|
|
|
class GenerateActivationKeys extends Page implements HasForms, HasTable
|
|
{
|
|
use InteractsWithForms, InteractsWithTable;
|
|
|
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-key';
|
|
|
|
protected string $view = 'filament.pages.generate-activation-keys';
|
|
|
|
protected static string|UnitEnum|null $navigationGroup = 'Admin';
|
|
|
|
protected static ?string $title = 'Activation Keys';
|
|
|
|
public $plan_id;
|
|
|
|
public $quantity = 1;
|
|
|
|
public $notes;
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->form->fill();
|
|
}
|
|
|
|
protected function getFormSchema(): array
|
|
{
|
|
return [
|
|
Select::make('plan_id')
|
|
->label('Select Plan')
|
|
->options(function () {
|
|
return Plan::with('planTier')
|
|
->get()
|
|
->map(function ($plan) {
|
|
$tierName = $plan->planTier ? $plan->planTier->name : '';
|
|
$billingCycle = $plan->getBillingCycleDisplay();
|
|
$name = $plan->name;
|
|
|
|
if ($tierName) {
|
|
$name .= " ({$tierName})";
|
|
}
|
|
if ($billingCycle) {
|
|
$name .= " - {$billingCycle}";
|
|
}
|
|
|
|
return $name;
|
|
})
|
|
->toArray();
|
|
})
|
|
->required()
|
|
->reactive()
|
|
->afterStateUpdated(fn ($state, callable $set) => $set('quantity', 1)),
|
|
|
|
TextInput::make('quantity')
|
|
->label('Number of Keys')
|
|
->numeric()
|
|
->minValue(1)
|
|
->maxValue(100)
|
|
->default(1)
|
|
->required()
|
|
->helperText('Generate multiple keys for bulk distribution'),
|
|
|
|
Textarea::make('notes')
|
|
->label('Generation Notes (Optional)')
|
|
->rows(2)
|
|
->placeholder('Add notes about this batch of keys...')
|
|
->helperText('These notes will be stored with the generated keys for tracking purposes'),
|
|
];
|
|
}
|
|
|
|
public function generate(): void
|
|
{
|
|
try {
|
|
$data = $this->form->getState();
|
|
$plan = Plan::with('planTier')->findOrFail($data['plan_id']);
|
|
|
|
$generatedKeys = [];
|
|
$batchId = 'BATCH_'.now()->format('YmdHis').'_'.Str::random(8);
|
|
|
|
for ($i = 0; $i < $data['quantity']; $i++) {
|
|
$activationKey = ActivationKey::query()->create([
|
|
'price_id' => $plan->pricing_id,
|
|
'activation_key' => $this->generateUniqueActivationKey($plan),
|
|
'is_activated' => false,
|
|
]);
|
|
|
|
$generatedKeys[] = $activationKey->activation_key;
|
|
}
|
|
|
|
// Log the generation for audit purposes
|
|
\Log::info('Activation keys generated', [
|
|
'batch_id' => $batchId,
|
|
'plan_id' => $plan->id,
|
|
'plan_name' => $plan->name,
|
|
'quantity' => $data['quantity'],
|
|
'generated_by' => auth()->id(),
|
|
'notes' => $data['notes'] ?? null,
|
|
]);
|
|
|
|
Notification::make()
|
|
->title("{$data['quantity']} activation key(s) generated successfully")
|
|
->body("For {$plan->name} - Batch ID: {$batchId}")
|
|
->success()
|
|
->send();
|
|
|
|
$this->form->fill(); // Reset form
|
|
} catch (\Exception $exception) {
|
|
$this->form->fill();
|
|
Notification::make()
|
|
->title('Something went wrong')
|
|
->body("Error: {$exception->getMessage()}")
|
|
->danger()
|
|
->send();
|
|
}
|
|
|
|
}
|
|
|
|
private function generateUniqueActivationKey(Plan $plan): string
|
|
{
|
|
do {
|
|
// Create a more structured key format: PLAN_PREFIX + RANDOM
|
|
$prefix = strtoupper(substr(str_replace([' ', '-'], '', $plan->name), 0, 3));
|
|
$random = strtoupper(Str::random(13));
|
|
$key = $prefix.$random;
|
|
} while (ActivationKey::where('activation_key', $key)->exists());
|
|
|
|
return $key;
|
|
}
|
|
|
|
// === Table Setup ===
|
|
protected function getTableQuery(): Builder
|
|
{
|
|
return ActivationKey::query()->latest();
|
|
}
|
|
|
|
protected function getTableColumns(): array
|
|
{
|
|
return [
|
|
TextColumn::make('activation_key')
|
|
->label('Activation Key')
|
|
->copyable()
|
|
->searchable()
|
|
->weight('font-semibold'),
|
|
|
|
BadgeColumn::make('status')
|
|
->label('Status')
|
|
->getStateUsing(function ($record): string {
|
|
return $record->is_activated ? 'Activated' : 'Unused';
|
|
})
|
|
->colors([
|
|
'success' => 'Activated',
|
|
'warning' => 'Unused',
|
|
]),
|
|
|
|
TextColumn::make('plan.name')
|
|
->label('Plan')
|
|
->getStateUsing(function ($record): string {
|
|
$plan = Plan::where('pricing_id', $record->price_id)->first();
|
|
|
|
return $plan ? $plan->name : 'Unknown Plan';
|
|
})
|
|
->searchable(),
|
|
|
|
TextColumn::make('plan.planTier.name')
|
|
->label('Tier')
|
|
->getStateUsing(function ($record): ?string {
|
|
$plan = Plan::where('pricing_id', $record->price_id)->first();
|
|
|
|
return $plan && $plan->planTier ? $plan->planTier->name : null;
|
|
}),
|
|
|
|
TextColumn::make('user.email')
|
|
->label('Activated By')
|
|
->default('Not activated')
|
|
->searchable(),
|
|
|
|
TextColumn::make('billing_cycle')
|
|
->label('Billing Cycle')
|
|
->getStateUsing(function ($record): string {
|
|
$plan = Plan::where('pricing_id', $record->price_id)->first();
|
|
|
|
return $plan ? $plan->getBillingCycleDisplay() : 'Unknown';
|
|
}),
|
|
|
|
TextColumn::make('created_at')
|
|
->label('Generated')
|
|
->dateTime()
|
|
->sortable(),
|
|
];
|
|
}
|
|
|
|
protected function getTableFilters(): array
|
|
{
|
|
return [
|
|
SelectFilter::make('is_activated')
|
|
->label('Status')
|
|
->options([
|
|
true => 'Activated',
|
|
false => 'Not Activated',
|
|
]),
|
|
|
|
SelectFilter::make('price_id')
|
|
->label('Plan')
|
|
->options(function () {
|
|
return Plan::with('planTier')
|
|
->get()
|
|
->mapWithKeys(function ($plan) {
|
|
$tierName = $plan->planTier ? " ({$plan->planTier->name})" : '';
|
|
|
|
return [$plan->pricing_id => $plan->name.$tierName];
|
|
})
|
|
->toArray();
|
|
}),
|
|
|
|
SelectFilter::make('created_at')
|
|
->label('Generation Date')
|
|
->options([
|
|
'today' => 'Today',
|
|
'this_week' => 'This Week',
|
|
'this_month' => 'This Month',
|
|
'last_month' => 'Last Month',
|
|
])
|
|
->query(function (Builder $query, array $data): Builder {
|
|
if ($data['value'] === 'today') {
|
|
return $query->whereDate('created_at', today());
|
|
} elseif ($data['value'] === 'this_week') {
|
|
return $query->whereBetween('created_at', [now()->startOfWeek(), now()->endOfWeek()]);
|
|
} elseif ($data['value'] === 'this_month') {
|
|
return $query->whereMonth('created_at', now()->month)
|
|
->whereYear('created_at', now()->year);
|
|
} elseif ($data['value'] === 'last_month') {
|
|
return $query->whereMonth('created_at', now()->subMonth()->month)
|
|
->whereYear('created_at', now()->subMonth()->year);
|
|
}
|
|
|
|
return $query;
|
|
}),
|
|
];
|
|
}
|
|
|
|
protected function getTableBulkActions(): array
|
|
{
|
|
return [
|
|
BulkAction::make('Download Keys')
|
|
->action(fn (Collection $records): BinaryFileResponse => $this->downloadKeys($records))
|
|
->deselectRecordsAfterCompletion()
|
|
->requiresConfirmation()
|
|
->modalDescription('Download selected activation keys as a text file.'),
|
|
|
|
BulkAction::make('Deactivate Keys')
|
|
->action(function (Collection $records) {
|
|
$count = 0;
|
|
foreach ($records as $record) {
|
|
if ($record->is_activated) {
|
|
// Deactivate the subscription if it exists
|
|
$subscription = $record->user?->subscriptions()
|
|
->where('provider', 'activation_key')
|
|
->where('provider_subscription_id', $record->id)
|
|
->first();
|
|
|
|
if ($subscription) {
|
|
$subscription->update([
|
|
'status' => 'cancelled',
|
|
'ends_at' => now(),
|
|
]);
|
|
}
|
|
|
|
$record->update([
|
|
'is_activated' => false,
|
|
'user_id' => null,
|
|
]);
|
|
$count++;
|
|
}
|
|
}
|
|
|
|
Notification::make()
|
|
->title("{$count} key(s) deactivated")
|
|
->success()
|
|
->send();
|
|
})
|
|
->deselectRecordsAfterCompletion()
|
|
->requiresConfirmation()
|
|
->modalDescription('This will deactivate the selected keys and cancel associated subscriptions.')
|
|
->color('danger'),
|
|
|
|
BulkAction::make('Delete Keys')
|
|
->action(function (Collection $records) {
|
|
$count = $records->count();
|
|
|
|
// First, deactivate any associated subscriptions
|
|
foreach ($records as $record) {
|
|
$subscription = $record->user?->subscriptions()
|
|
->where('provider', 'activation_key')
|
|
->where('provider_subscription_id', $record->id)
|
|
->first();
|
|
|
|
if ($subscription) {
|
|
$subscription->delete();
|
|
}
|
|
}
|
|
|
|
// Delete the keys
|
|
$records->each->delete();
|
|
|
|
Notification::make()
|
|
->title("{$count} key(s) deleted")
|
|
->success()
|
|
->send();
|
|
})
|
|
->deselectRecordsAfterCompletion()
|
|
->requiresConfirmation()
|
|
->modalDescription('This will permanently delete the selected keys and associated subscriptions.')
|
|
->color('danger'),
|
|
];
|
|
}
|
|
|
|
public function downloadKeys(Collection $records): BinaryFileResponse
|
|
{
|
|
$content = "# Activation Keys\n";
|
|
$content .= '# Generated: '.now()->toDateTimeString()."\n";
|
|
$content .= '# Total Keys: '.$records->count()."\n\n";
|
|
|
|
foreach ($records as $record) {
|
|
$plan = Plan::where('pricing_id', $record->price_id)->first();
|
|
$content .= "Key: {$record->activation_key}\n";
|
|
$content .= 'Plan: '.($plan->name ?? 'Unknown Plan')."\n";
|
|
$content .= 'Status: '.($record->is_activated ? 'Activated' : 'Unused')."\n";
|
|
$content .= 'Generated: '.$record->created_at->toDateTimeString()."\n";
|
|
if ($record->user) {
|
|
$content .= 'Activated By: '.$record->user->email."\n";
|
|
}
|
|
$content .= "---\n\n";
|
|
}
|
|
|
|
$filename = 'activation_keys_'.now()->format('Y-m-d_H-i-s').'.txt';
|
|
$path = public_path("activation/{$filename}");
|
|
|
|
// Make sure the 'activation' folder exists
|
|
if (! file_exists(public_path('activation')) && ! mkdir($concurrentDirectory = public_path('activation'), 0755, true) && ! is_dir($concurrentDirectory)) {
|
|
Log::error('Failed to create activation keys file: '.$concurrentDirectory);
|
|
}
|
|
|
|
file_put_contents($path, $content);
|
|
|
|
return response()->download($path)->deleteFileAfterSend(true);
|
|
}
|
|
}
|