feat: Prepare Zemailnator for Dokploy deployment

- Add highly optimized Dockerfile with Nginx and PHP-FPM 8.4
- Add docker-compose.yml configured with Redis and MariaDB 10.11
- Implement entrypoint.sh and supervisord.conf for background workers
- Refactor legacy IMAP scripts into scheduled Artisan Commands
- Secure app by removing old routes with hardcoded basic auth credentials
- Configure email attachments to use Laravel Storage instead of insecure public/tmp
This commit is contained in:
idevakk
2026-02-28 23:17:39 +05:30
parent bf5b797cd8
commit c312ec3325
78 changed files with 750 additions and 360 deletions

View File

@@ -13,7 +13,6 @@ use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;
use Inerba\DbConfig\AbstractPageSettings;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
class ImapSettings extends AbstractPageSettings
{
@@ -145,7 +144,7 @@ class ImapSettings extends AbstractPageSettings
$fields = ['host', 'port', 'username', 'password', 'encryption', 'validate_cert', 'protocol'];
foreach ($fields as $field) {
$key = $sectionName . '.' . $field;
$key = $sectionName.'.'.$field;
// Try different data structure approaches
$value = null;
@@ -186,8 +185,8 @@ class ImapSettings extends AbstractPageSettings
return [
'section' => ucfirst($sectionName),
'success' => false,
'message' => "Missing required fields: " . $missingFields->join(', '),
'details' => null
'message' => 'Missing required fields: '.$missingFields->join(', '),
'details' => null,
];
}
@@ -198,7 +197,7 @@ class ImapSettings extends AbstractPageSettings
'section' => ucfirst($sectionName),
'success' => false,
'message' => 'IMAP extension is not loaded in your web server. Please check your Herd PHP configuration or restart your server.',
'details' => null
'details' => null,
];
}
@@ -210,7 +209,7 @@ class ImapSettings extends AbstractPageSettings
'password' => $config['password'],
'encryption' => $config['encryption'] ?? 'none',
'validate_cert' => $config['validate_cert'] ?? false,
'protocol' => $config['protocol'] ?? 'imap'
'protocol' => $config['protocol'] ?? 'imap',
];
// Test connection using the existing ZEmail::connectMailBox method
@@ -224,8 +223,8 @@ class ImapSettings extends AbstractPageSettings
'host' => $config['host'],
'port' => $config['port'],
'encryption' => $config['encryption'] ?? 'none',
'protocol' => $config['protocol'] ?? 'imap'
]
'protocol' => $config['protocol'] ?? 'imap',
],
];
} catch (\Exception $e) {
@@ -244,13 +243,12 @@ class ImapSettings extends AbstractPageSettings
'host' => $config['host'] ?? null,
'port' => $config['port'] ?? null,
'encryption' => $config['encryption'] ?? 'none',
'protocol' => $config['protocol'] ?? 'imap'
]
'protocol' => $config['protocol'] ?? 'imap',
],
];
}
}
/**
* Send appropriate notification based on test results.
*/
@@ -294,6 +292,7 @@ class ImapSettings extends AbstractPageSettings
$details[] = "{$result['section']}: {$result['details']['messages']} messages";
}
}
return implode(' | ', $details);
}
@@ -306,6 +305,7 @@ class ImapSettings extends AbstractPageSettings
foreach ($results as $result) {
$details[] = "{$result['section']}: {$result['message']}";
}
return implode(' | ', $details);
}
@@ -319,6 +319,7 @@ class ImapSettings extends AbstractPageSettings
$status = $result['success'] ? '✅' : '❌';
$details[] = "{$status} {$result['section']}";
}
return implode(' | ', $details);
}
}

View File

@@ -27,7 +27,6 @@ use Illuminate\Contracts\View\View;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use UnitEnum;
class ImpersonationLogViewer extends Page implements HasForms, HasTable
@@ -132,10 +131,10 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
TextColumn::make('duration_in_minutes')
->label('Duration')
->formatStateUsing(function ($record) {
return match(true) {
!$record->duration_in_minutes => 'Active',
return match (true) {
! $record->duration_in_minutes => 'Active',
$record->duration_in_minutes < 60 => "{$record->duration_in_minutes}m",
default => round($record->duration_in_minutes / 60, 1) . 'h',
default => round($record->duration_in_minutes / 60, 1).'h',
};
})
->sortable()
@@ -324,7 +323,7 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
->latest('start_time')
->get();
$filename = 'impersonation_logs_' . now()->format('Y_m_d_H_i_s') . '.csv';
$filename = 'impersonation_logs_'.now()->format('Y_m_d_H_i_s').'.csv';
// Create a temporary file
$handle = fopen('php://temp', 'r+');
@@ -376,7 +375,7 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
$filename,
[
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
]
);
}

View File

@@ -2,13 +2,12 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\BlogResource\Pages\CreateBlog;
use App\Filament\Resources\BlogResource\Pages\EditBlog;
use App\Filament\Resources\BlogResource\Pages\ListBlogs;
use App\Models\Blog;
use App\Models\Category;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
@@ -30,6 +29,7 @@ use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use UnitEnum;
class BlogResource extends Resource
{

View File

@@ -2,12 +2,11 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\CategoryResource\Pages\CreateCategory;
use App\Filament\Resources\CategoryResource\Pages\EditCategory;
use App\Filament\Resources\CategoryResource\Pages\ListCategories;
use App\Models\Category;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
@@ -23,6 +22,7 @@ use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use UnitEnum;
class CategoryResource extends Resource
{
@@ -60,7 +60,7 @@ class CategoryResource extends Resource
TextColumn::make('slug'),
TextColumn::make('blogs_count')
->label('Blogs')
->getStateUsing(fn(Category $record): int => $record->blogs()->count()),
->getStateUsing(fn (Category $record): int => $record->blogs()->count()),
IconColumn::make('is_active')->label('Active')->boolean(),
])
->filters([

View File

@@ -2,12 +2,11 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\MenuResource\Pages\CreateMenu;
use App\Filament\Resources\MenuResource\Pages\EditMenu;
use App\Filament\Resources\MenuResource\Pages\ListMenus;
use App\Models\Menu;
use BackedEnum;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
use Filament\Actions\DeleteBulkAction;
@@ -21,6 +20,7 @@ use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Table;
use UnitEnum;
class MenuResource extends Resource
{

View File

@@ -2,12 +2,11 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\PageResource\Pages\CreatePage;
use App\Filament\Resources\PageResource\Pages\EditPage;
use App\Filament\Resources\PageResource\Pages\ListPages;
use App\Models\Page;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteAction;
@@ -29,6 +28,7 @@ use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Support\Str;
use UnitEnum;
class PageResource extends Resource
{

View File

@@ -2,12 +2,12 @@
namespace App\Filament\Resources\PaymentProviders\Tables;
use Filament\Actions\Action;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
use Filament\Actions\CreateAction;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
use Filament\Actions\Action;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Filters\SelectFilter;

View File

@@ -301,6 +301,7 @@ class PlanResource extends Resource
// Halt the bulk deletion process
$action->halt();
return;
}
}

View File

@@ -2,8 +2,6 @@
namespace App\Filament\Resources;
use BackedEnum;
use UnitEnum;
use App\Filament\Resources\TicketResource\Pages\CreateTicket;
use App\Filament\Resources\TicketResource\Pages\EditTicket;
use App\Filament\Resources\TicketResource\Pages\ListTickets;
@@ -11,6 +9,7 @@ use App\Filament\Resources\TicketResource\RelationManagers\ResponsesRelationMana
use App\Mail\TicketResponseNotification;
use App\Models\Ticket;
use App\Models\TicketResponse;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\BulkAction;
use Filament\Actions\BulkActionGroup;
@@ -33,6 +32,7 @@ use Filament\Tables\Table;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\HtmlString;
use UnitEnum;
class TicketResource extends Resource
{
@@ -120,7 +120,7 @@ class TicketResource extends Resource
DatePicker::make('created_from')->label('Created From'),
DatePicker::make('created_until')->label('Created Until'),
])
->query(fn($query, array $data) => $query
->query(fn ($query, array $data) => $query
->when($data['created_from'], fn ($query, $date) => $query->whereDate('created_at', '>=', $date))
->when($data['created_until'], fn ($query, $date) => $query->whereDate('created_at', '<=', $date))),
])
@@ -134,7 +134,7 @@ class TicketResource extends Resource
Action::make('view')
->label('View & Respond')
->icon('heroicon-o-eye')
->schema(fn(Ticket $ticket): array => [
->schema(fn (Ticket $ticket): array => [
TextArea::make('response')
->label('Your Response')
->required()

View File

@@ -34,6 +34,7 @@ class TrialExtensionForm
if ($subscription->trial_ends_at) {
$label .= " ({$subscription->trial_ends_at->format('M j, Y')})";
}
return [$subscription->id => $label];
})
->toArray();
@@ -158,12 +159,14 @@ class TrialExtensionForm
if (! $subscriptionId || ! $extensionDays) {
$set('new_trial_ends_at', null);
return;
}
$subscription = Subscription::find($subscriptionId);
if (! $subscription) {
$set('new_trial_ends_at', null);
return;
}

View File

@@ -96,7 +96,7 @@ class TrialExtensionsTable
->label('View Subscription')
->icon('heroicon-o-rectangle-stack')
->color('blue')
->url(fn ($record) => route('filament.' . filament()->getCurrentPanel()->getId() . '.resources.subscriptions.edit', $record->subscription_id))
->url(fn ($record) => route('filament.'.filament()->getCurrentPanel()->getId().'.resources.subscriptions.edit', $record->subscription_id))
->openUrlInNewTab(),
])
->toolbarActions([

View File

@@ -7,7 +7,6 @@ use App\Filament\Resources\TrialExtensions\Pages\EditTrialExtension;
use App\Filament\Resources\TrialExtensions\Pages\ListTrialExtensions;
use App\Filament\Resources\TrialExtensions\Schemas\TrialExtensionForm;
use App\Filament\Resources\TrialExtensions\Tables\TrialExtensionsTable;
use App\Models\Subscription;
use App\Models\TrialExtension;
use BackedEnum;
use Filament\Resources\Resource;

View File

@@ -2,8 +2,6 @@
namespace App\Filament\Widgets;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
use App\Models\Log;
use App\Models\Meta;
use App\Models\PremiumEmail;
@@ -11,6 +9,8 @@ use App\Models\Ticket;
use App\Models\User;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Facades\DB;
class StatsOverview extends BaseWidget
{