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:
17
.docker/entrypoint.sh
Normal file
17
.docker/entrypoint.sh
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Storage symlink..."
|
||||||
|
php artisan storage:link || true
|
||||||
|
|
||||||
|
echo "Optimize cache..."
|
||||||
|
php artisan optimize:clear
|
||||||
|
php artisan config:cache
|
||||||
|
php artisan route:cache
|
||||||
|
php artisan view:cache
|
||||||
|
|
||||||
|
echo "Running migrations..."
|
||||||
|
php artisan migrate --force || true
|
||||||
|
|
||||||
|
# Pass hand off to supervisor
|
||||||
|
exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
|
||||||
32
.docker/nginx.conf
Normal file
32
.docker/nginx.conf
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /var/www/public;
|
||||||
|
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN";
|
||||||
|
add_header X-Content-Type-Options "nosniff";
|
||||||
|
|
||||||
|
index index.php;
|
||||||
|
|
||||||
|
charset utf-8;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /favicon.ico { access_log off; log_not_found off; }
|
||||||
|
location = /robots.txt { access_log off; log_not_found off; }
|
||||||
|
|
||||||
|
error_page 404 /index.php;
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
fastcgi_pass 127.0.0.1:9000;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||||||
|
include fastcgi_params;
|
||||||
|
fastcgi_hide_header X-Powered-By;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ /\.(?!well-known).* {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
.docker/supervisord.conf
Normal file
51
.docker/supervisord.conf
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
[supervisord]
|
||||||
|
nodaemon=true
|
||||||
|
user=root
|
||||||
|
logfile=/var/log/supervisor/supervisord.log
|
||||||
|
pidfile=/var/run/supervisord.pid
|
||||||
|
|
||||||
|
[program:php-fpm]
|
||||||
|
command=php-fpm -F
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
priority=5
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:nginx]
|
||||||
|
command=nginx -g 'daemon off;'
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
priority=10
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:laravel-worker]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /var/www/artisan queue:work --sleep=3 --tries=3 --max-time=3600
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
user=www-data
|
||||||
|
numprocs=1
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
|
||||||
|
[program:laravel-scheduler]
|
||||||
|
process_name=%(program_name)s_%(process_num)02d
|
||||||
|
command=php /var/www/artisan schedule:work
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
stopasgroup=true
|
||||||
|
killasgroup=true
|
||||||
|
user=www-data
|
||||||
|
numprocs=1
|
||||||
|
redirect_stderr=true
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
76
Project.md
Normal file
76
Project.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
description: Zemailnator - AI-First Project Documentation
|
||||||
|
---
|
||||||
|
|
||||||
|
# Zemailnator Project Documentation
|
||||||
|
|
||||||
|
> **AI INSTRUCTION**: This `Project.md` file contains critical context for the Zemailnator (Disposable Email Generator) application. When interacting with this project, ALWAYS load and review this file as part of your system instructions. Treat these guidelines, architectural details, and known security flaws as your primary working context.
|
||||||
|
|
||||||
|
## 1. Project Overview & Architecture
|
||||||
|
Zemailnator is a scalable disposable temporary email service (like Mailinator or 10 Minute Mail) built on the Laravel framework. It provides users with instant, disposable email addresses to prevent spam, with premium tiers offering custom domains, 10-minute validity enhancements, and bulk generation capabilities.
|
||||||
|
|
||||||
|
**Core Mechanics:**
|
||||||
|
- The platform uses IMAP (`ddeboer/imap` via PHP `ext-imap`) to fetch emails from a master mailbox.
|
||||||
|
- `App\Models\Email::fetchProcessStoreEmail()` processes incoming messages, parses HTML/Text bodies, extracts allowed attachments to `public/tmp/attachments`, and stores the records in the database (or a remote database if `config('app.fetch_from_remote_db')` is true).
|
||||||
|
- External cron/scripts (like `dropmail.php`) are used to automatically purge old messages and attachments to keep the platform lightweight.
|
||||||
|
|
||||||
|
## 2. Technology Stack
|
||||||
|
- **Framework:** Laravel 12.x
|
||||||
|
- **PHP Version:** >= 8.2
|
||||||
|
- **Frontend / UI:** Livewire v3, Flux UI Free, Tailwind CSS v4, Vite
|
||||||
|
- **Admin Panel:** Filament v4
|
||||||
|
- **Billing:** Laravel Cashier (Stripe) v15, alongside a custom Unified Payment System (OxaPay, etc.)
|
||||||
|
- **Testing:** Pest v3
|
||||||
|
- **Email Parsing:** PHP `ext-imap`, `ddeboer/imap`
|
||||||
|
|
||||||
|
## 3. Key Features
|
||||||
|
- **Disposable Email Generation:** Quick generation of random or temporary emails via Livewire components (e.g., `/mailbox`, `/gmailnator`).
|
||||||
|
- **Premium Subscriptions:** Tiered access (Stripe/Crypto) unlocking features like Premium Domains, 10-Minute Emails, and Bulk Email generation.
|
||||||
|
- **Admin Dashboard:** Fully managed via Filament, including logs functionality, failed jobs monitoring, and dynamic database configuration.
|
||||||
|
- **Impersonation:** Admins can impersonate users for troubleshooting (`ImpersonationController`).
|
||||||
|
|
||||||
|
## 4. Known Security Flaws & Vulnerabilities (CRITICAL)
|
||||||
|
When working on this codebase, prioritize addressing or being mindful of the following security issues:
|
||||||
|
1. **Hardcoded Credentials in Routes:**
|
||||||
|
- Found in `routes/web.php` for endpoints `/0xdash/slink` and `/0xdash/scache`. They use basic HTTP authentication with a hardcoded password (`admin@9608`). This is highly insecure and should be migrated to secure Artisan commands via the Filament Admin panel or proper token-based middleware.
|
||||||
|
2. **Public Attachment Storage:**
|
||||||
|
- Attachments are stored directly in `public/tmp/attachments`. While there is an extension filter in `Email::fetchProcessStoreEmail()`, any bypass of this filter (e.g., uploading a `.php` file masquerading as another or zero-byte vulnerabilities) can lead to direct Remote Code Execution (RCE) since the directory is publicly accessible.
|
||||||
|
3. **No Strict Rate Limiting on Generation Endpoints:**
|
||||||
|
- Temporary email and bulk email endpoints in `routes/web.php` appear to lack specific rate limiting, making the application vulnerable to Denial of Service (DoS) and excessive spam generation.
|
||||||
|
4. **Standalone PHP Scripts:**
|
||||||
|
- Scripts like `dropmail.php`, `closeTicket.php`, and `cleanCron.php` inside the public/root directory bootstrap Laravel manually. They lack proper authentication and could potentially be executed repeatedly by unauthorized entities, causing database or IMAP server exhaustion. They should be migrated to Laravel Console Commands (`app/Console/Commands`).
|
||||||
|
|
||||||
|
## 5. Pros & Cons
|
||||||
|
**Pros:**
|
||||||
|
- **Modern Stack:** Utilizes bleeding-edge tools (Laravel 12, Livewire 3, Tailwind 4) resulting in a highly responsive SPA-like feel.
|
||||||
|
- **Robust Admin Panel:** Leveraging Filament provides out-of-the-box CRUD, logging, and mail monitoring.
|
||||||
|
- **Flexible Monetization:** Mix of traditional Stripe and unified payments (crypto via OxaPay) allows for diverse revenue streams.
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- **Technical Debt:** Legacy payment routes (`checkout/{plan}`) exist alongside the unified payment system, leading to code fragmentation.
|
||||||
|
- **Synchronous IMAP Polling:** Running IMAP fetch directly or via sequential cron is slow. It should be offloaded to robust Laravel Queue workers.
|
||||||
|
- **File Management:** Relying on basic `file_put_contents` for attachments locally limits horizontal scalability (e.g., scaling across multiple Docker pods/servers). S3 or equivalent Object Storage should be used.
|
||||||
|
|
||||||
|
## 6. Development Guide & Setup Best Practices
|
||||||
|
1. Ensure both `ext-imap` and `ext-curl` are enabled in your `php.ini`.
|
||||||
|
2. Run `npm run dev` in tandem with `php artisan serve` if working on Frontend/FluxUI adjustments.
|
||||||
|
3. To test email fetching locally without an active IMAP server, consider writing Pest tests mocking the `ConnectionInterface`.
|
||||||
|
4. Always use `php artisan test` before committing. The project uses Pest. Make sure datasets and mocking are used properly per the `CLAUDE.md` guidelines.
|
||||||
|
|
||||||
|
## 7. Examples / Workflows
|
||||||
|
|
||||||
|
**Example: Fetching Emails**
|
||||||
|
The `Email` model has a primary function `fetchProcessStoreEmail()`:
|
||||||
|
```php
|
||||||
|
// Call this to manually pull emails
|
||||||
|
App\Models\Email::fetchProcessStoreEmail();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example: Reading Emails (Livewire)**
|
||||||
|
The `Mailbox` component fetches emails for the active user session or session ID, parsing them via:
|
||||||
|
```php
|
||||||
|
$emails = App\Models\Email::parseEmail($currentUserEmailAddress, $deletedArray);
|
||||||
|
```
|
||||||
|
|
||||||
|
## 8. AI Auto-Load Instruction
|
||||||
|
**For AI Agents:** Whenever you receive a new chat prompt within this workspace, your implicit first step must be to cross-reference the user's request against this `Project.md` document. Pay special attention to the Laravel 12 / Livewire 3 structures and strictly avoid executing basic auth routes or standard PHP scripts directly if Laravel-native alternatives exist. Use Pest for all tests. ALWAYS rely on the `CLAUDE.md` guidelines as your secondary set of standard rules.
|
||||||
33
app/Console/Commands/CleanAttachmentsCommand.php
Normal file
33
app/Console/Commands/CleanAttachmentsCommand.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Email;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanAttachmentsCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'attachments:clean';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Clean old email attachments from disk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->info('Starting to clean attachments...');
|
||||||
|
Email::deleteBulkAttachments();
|
||||||
|
$this->info('Finished cleaning attachments.');
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Console/Commands/CleanMailboxCommand.php
Normal file
33
app/Console/Commands/CleanMailboxCommand.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Email;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class CleanMailboxCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'mailbox:clean';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Clean old messages from the IMAP mailbox';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->info('Starting mailbox cleanup...');
|
||||||
|
$result = Email::cleanMailbox();
|
||||||
|
$this->info($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
app/Console/Commands/FetchEmailsCommand.php
Normal file
33
app/Console/Commands/FetchEmailsCommand.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use App\Models\Email;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class FetchEmailsCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'emails:fetch';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Fetch, process, and store emails from the IMAP server';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$this->info('Starting to fetch emails from IMAP server...');
|
||||||
|
Email::fetchProcessStoreEmail();
|
||||||
|
$this->info('Finished fetching emails.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ use Filament\Schemas\Components\Section;
|
|||||||
use Filament\Schemas\Schema;
|
use Filament\Schemas\Schema;
|
||||||
use Filament\Support\Icons\Heroicon;
|
use Filament\Support\Icons\Heroicon;
|
||||||
use Inerba\DbConfig\AbstractPageSettings;
|
use Inerba\DbConfig\AbstractPageSettings;
|
||||||
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
|
|
||||||
|
|
||||||
class ImapSettings extends AbstractPageSettings
|
class ImapSettings extends AbstractPageSettings
|
||||||
{
|
{
|
||||||
@@ -145,7 +144,7 @@ class ImapSettings extends AbstractPageSettings
|
|||||||
$fields = ['host', 'port', 'username', 'password', 'encryption', 'validate_cert', 'protocol'];
|
$fields = ['host', 'port', 'username', 'password', 'encryption', 'validate_cert', 'protocol'];
|
||||||
|
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
$key = $sectionName . '.' . $field;
|
$key = $sectionName.'.'.$field;
|
||||||
|
|
||||||
// Try different data structure approaches
|
// Try different data structure approaches
|
||||||
$value = null;
|
$value = null;
|
||||||
@@ -186,8 +185,8 @@ class ImapSettings extends AbstractPageSettings
|
|||||||
return [
|
return [
|
||||||
'section' => ucfirst($sectionName),
|
'section' => ucfirst($sectionName),
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => "Missing required fields: " . $missingFields->join(', '),
|
'message' => 'Missing required fields: '.$missingFields->join(', '),
|
||||||
'details' => null
|
'details' => null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,7 +197,7 @@ class ImapSettings extends AbstractPageSettings
|
|||||||
'section' => ucfirst($sectionName),
|
'section' => ucfirst($sectionName),
|
||||||
'success' => false,
|
'success' => false,
|
||||||
'message' => 'IMAP extension is not loaded in your web server. Please check your Herd PHP configuration or restart your server.',
|
'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'],
|
'password' => $config['password'],
|
||||||
'encryption' => $config['encryption'] ?? 'none',
|
'encryption' => $config['encryption'] ?? 'none',
|
||||||
'validate_cert' => $config['validate_cert'] ?? false,
|
'validate_cert' => $config['validate_cert'] ?? false,
|
||||||
'protocol' => $config['protocol'] ?? 'imap'
|
'protocol' => $config['protocol'] ?? 'imap',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Test connection using the existing ZEmail::connectMailBox method
|
// Test connection using the existing ZEmail::connectMailBox method
|
||||||
@@ -224,8 +223,8 @@ class ImapSettings extends AbstractPageSettings
|
|||||||
'host' => $config['host'],
|
'host' => $config['host'],
|
||||||
'port' => $config['port'],
|
'port' => $config['port'],
|
||||||
'encryption' => $config['encryption'] ?? 'none',
|
'encryption' => $config['encryption'] ?? 'none',
|
||||||
'protocol' => $config['protocol'] ?? 'imap'
|
'protocol' => $config['protocol'] ?? 'imap',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@@ -244,13 +243,12 @@ class ImapSettings extends AbstractPageSettings
|
|||||||
'host' => $config['host'] ?? null,
|
'host' => $config['host'] ?? null,
|
||||||
'port' => $config['port'] ?? null,
|
'port' => $config['port'] ?? null,
|
||||||
'encryption' => $config['encryption'] ?? 'none',
|
'encryption' => $config['encryption'] ?? 'none',
|
||||||
'protocol' => $config['protocol'] ?? 'imap'
|
'protocol' => $config['protocol'] ?? 'imap',
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send appropriate notification based on test results.
|
* Send appropriate notification based on test results.
|
||||||
*/
|
*/
|
||||||
@@ -294,6 +292,7 @@ class ImapSettings extends AbstractPageSettings
|
|||||||
$details[] = "{$result['section']}: {$result['details']['messages']} messages";
|
$details[] = "{$result['section']}: {$result['details']['messages']} messages";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(' | ', $details);
|
return implode(' | ', $details);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,6 +305,7 @@ class ImapSettings extends AbstractPageSettings
|
|||||||
foreach ($results as $result) {
|
foreach ($results as $result) {
|
||||||
$details[] = "{$result['section']}: {$result['message']}";
|
$details[] = "{$result['section']}: {$result['message']}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(' | ', $details);
|
return implode(' | ', $details);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,6 +319,7 @@ class ImapSettings extends AbstractPageSettings
|
|||||||
$status = $result['success'] ? '✅' : '❌';
|
$status = $result['success'] ? '✅' : '❌';
|
||||||
$details[] = "{$status} {$result['section']}";
|
$details[] = "{$status} {$result['section']}";
|
||||||
}
|
}
|
||||||
|
|
||||||
return implode(' | ', $details);
|
return implode(' | ', $details);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ use Illuminate\Contracts\View\View;
|
|||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use UnitEnum;
|
use UnitEnum;
|
||||||
|
|
||||||
class ImpersonationLogViewer extends Page implements HasForms, HasTable
|
class ImpersonationLogViewer extends Page implements HasForms, HasTable
|
||||||
@@ -132,10 +131,10 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
|
|||||||
TextColumn::make('duration_in_minutes')
|
TextColumn::make('duration_in_minutes')
|
||||||
->label('Duration')
|
->label('Duration')
|
||||||
->formatStateUsing(function ($record) {
|
->formatStateUsing(function ($record) {
|
||||||
return match(true) {
|
return match (true) {
|
||||||
!$record->duration_in_minutes => 'Active',
|
! $record->duration_in_minutes => 'Active',
|
||||||
$record->duration_in_minutes < 60 => "{$record->duration_in_minutes}m",
|
$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()
|
->sortable()
|
||||||
@@ -324,7 +323,7 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
|
|||||||
->latest('start_time')
|
->latest('start_time')
|
||||||
->get();
|
->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
|
// Create a temporary file
|
||||||
$handle = fopen('php://temp', 'r+');
|
$handle = fopen('php://temp', 'r+');
|
||||||
@@ -376,7 +375,7 @@ class ImpersonationLogViewer extends Page implements HasForms, HasTable
|
|||||||
$filename,
|
$filename,
|
||||||
[
|
[
|
||||||
'Content-Type' => 'text/csv',
|
'Content-Type' => 'text/csv',
|
||||||
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
|
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use BackedEnum;
|
|
||||||
use UnitEnum;
|
|
||||||
use App\Filament\Resources\BlogResource\Pages\CreateBlog;
|
use App\Filament\Resources\BlogResource\Pages\CreateBlog;
|
||||||
use App\Filament\Resources\BlogResource\Pages\EditBlog;
|
use App\Filament\Resources\BlogResource\Pages\EditBlog;
|
||||||
use App\Filament\Resources\BlogResource\Pages\ListBlogs;
|
use App\Filament\Resources\BlogResource\Pages\ListBlogs;
|
||||||
use App\Models\Blog;
|
use App\Models\Blog;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
@@ -30,6 +29,7 @@ use Filament\Tables\Columns\TextColumn;
|
|||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
class BlogResource extends Resource
|
class BlogResource extends Resource
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use BackedEnum;
|
|
||||||
use UnitEnum;
|
|
||||||
use App\Filament\Resources\CategoryResource\Pages\CreateCategory;
|
use App\Filament\Resources\CategoryResource\Pages\CreateCategory;
|
||||||
use App\Filament\Resources\CategoryResource\Pages\EditCategory;
|
use App\Filament\Resources\CategoryResource\Pages\EditCategory;
|
||||||
use App\Filament\Resources\CategoryResource\Pages\ListCategories;
|
use App\Filament\Resources\CategoryResource\Pages\ListCategories;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
@@ -23,6 +22,7 @@ use Filament\Tables\Columns\IconColumn;
|
|||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
class CategoryResource extends Resource
|
class CategoryResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -60,7 +60,7 @@ class CategoryResource extends Resource
|
|||||||
TextColumn::make('slug'),
|
TextColumn::make('slug'),
|
||||||
TextColumn::make('blogs_count')
|
TextColumn::make('blogs_count')
|
||||||
->label('Blogs')
|
->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(),
|
IconColumn::make('is_active')->label('Active')->boolean(),
|
||||||
])
|
])
|
||||||
->filters([
|
->filters([
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use BackedEnum;
|
|
||||||
use UnitEnum;
|
|
||||||
use App\Filament\Resources\MenuResource\Pages\CreateMenu;
|
use App\Filament\Resources\MenuResource\Pages\CreateMenu;
|
||||||
use App\Filament\Resources\MenuResource\Pages\EditMenu;
|
use App\Filament\Resources\MenuResource\Pages\EditMenu;
|
||||||
use App\Filament\Resources\MenuResource\Pages\ListMenus;
|
use App\Filament\Resources\MenuResource\Pages\ListMenus;
|
||||||
use App\Models\Menu;
|
use App\Models\Menu;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
@@ -21,6 +20,7 @@ use Filament\Schemas\Schema;
|
|||||||
use Filament\Tables\Columns\IconColumn;
|
use Filament\Tables\Columns\IconColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
class MenuResource extends Resource
|
class MenuResource extends Resource
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use BackedEnum;
|
|
||||||
use UnitEnum;
|
|
||||||
use App\Filament\Resources\PageResource\Pages\CreatePage;
|
use App\Filament\Resources\PageResource\Pages\CreatePage;
|
||||||
use App\Filament\Resources\PageResource\Pages\EditPage;
|
use App\Filament\Resources\PageResource\Pages\EditPage;
|
||||||
use App\Filament\Resources\PageResource\Pages\ListPages;
|
use App\Filament\Resources\PageResource\Pages\ListPages;
|
||||||
use App\Models\Page;
|
use App\Models\Page;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\DeleteAction;
|
use Filament\Actions\DeleteAction;
|
||||||
@@ -29,6 +28,7 @@ use Filament\Tables\Columns\TextColumn;
|
|||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
use Filament\Tables\Table;
|
use Filament\Tables\Table;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
class PageResource extends Resource
|
class PageResource extends Resource
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources\PaymentProviders\Tables;
|
namespace App\Filament\Resources\PaymentProviders\Tables;
|
||||||
|
|
||||||
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\BulkAction;
|
use Filament\Actions\BulkAction;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
use Filament\Actions\CreateAction;
|
use Filament\Actions\CreateAction;
|
||||||
use Filament\Actions\DeleteBulkAction;
|
use Filament\Actions\DeleteBulkAction;
|
||||||
use Filament\Actions\EditAction;
|
use Filament\Actions\EditAction;
|
||||||
use Filament\Actions\Action;
|
|
||||||
use Filament\Tables\Columns\IconColumn;
|
use Filament\Tables\Columns\IconColumn;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Tables\Filters\SelectFilter;
|
use Filament\Tables\Filters\SelectFilter;
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ class PlanResource extends Resource
|
|||||||
|
|
||||||
// Halt the bulk deletion process
|
// Halt the bulk deletion process
|
||||||
$action->halt();
|
$action->halt();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Filament\Resources;
|
namespace App\Filament\Resources;
|
||||||
|
|
||||||
use BackedEnum;
|
|
||||||
use UnitEnum;
|
|
||||||
use App\Filament\Resources\TicketResource\Pages\CreateTicket;
|
use App\Filament\Resources\TicketResource\Pages\CreateTicket;
|
||||||
use App\Filament\Resources\TicketResource\Pages\EditTicket;
|
use App\Filament\Resources\TicketResource\Pages\EditTicket;
|
||||||
use App\Filament\Resources\TicketResource\Pages\ListTickets;
|
use App\Filament\Resources\TicketResource\Pages\ListTickets;
|
||||||
@@ -11,6 +9,7 @@ use App\Filament\Resources\TicketResource\RelationManagers\ResponsesRelationMana
|
|||||||
use App\Mail\TicketResponseNotification;
|
use App\Mail\TicketResponseNotification;
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
use App\Models\TicketResponse;
|
use App\Models\TicketResponse;
|
||||||
|
use BackedEnum;
|
||||||
use Filament\Actions\Action;
|
use Filament\Actions\Action;
|
||||||
use Filament\Actions\BulkAction;
|
use Filament\Actions\BulkAction;
|
||||||
use Filament\Actions\BulkActionGroup;
|
use Filament\Actions\BulkActionGroup;
|
||||||
@@ -33,6 +32,7 @@ use Filament\Tables\Table;
|
|||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\Mail;
|
use Illuminate\Support\Facades\Mail;
|
||||||
use Illuminate\Support\HtmlString;
|
use Illuminate\Support\HtmlString;
|
||||||
|
use UnitEnum;
|
||||||
|
|
||||||
class TicketResource extends Resource
|
class TicketResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -120,7 +120,7 @@ class TicketResource extends Resource
|
|||||||
DatePicker::make('created_from')->label('Created From'),
|
DatePicker::make('created_from')->label('Created From'),
|
||||||
DatePicker::make('created_until')->label('Created Until'),
|
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_from'], fn ($query, $date) => $query->whereDate('created_at', '>=', $date))
|
||||||
->when($data['created_until'], 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')
|
Action::make('view')
|
||||||
->label('View & Respond')
|
->label('View & Respond')
|
||||||
->icon('heroicon-o-eye')
|
->icon('heroicon-o-eye')
|
||||||
->schema(fn(Ticket $ticket): array => [
|
->schema(fn (Ticket $ticket): array => [
|
||||||
TextArea::make('response')
|
TextArea::make('response')
|
||||||
->label('Your Response')
|
->label('Your Response')
|
||||||
->required()
|
->required()
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class TrialExtensionForm
|
|||||||
if ($subscription->trial_ends_at) {
|
if ($subscription->trial_ends_at) {
|
||||||
$label .= " ({$subscription->trial_ends_at->format('M j, Y')})";
|
$label .= " ({$subscription->trial_ends_at->format('M j, Y')})";
|
||||||
}
|
}
|
||||||
|
|
||||||
return [$subscription->id => $label];
|
return [$subscription->id => $label];
|
||||||
})
|
})
|
||||||
->toArray();
|
->toArray();
|
||||||
@@ -158,12 +159,14 @@ class TrialExtensionForm
|
|||||||
|
|
||||||
if (! $subscriptionId || ! $extensionDays) {
|
if (! $subscriptionId || ! $extensionDays) {
|
||||||
$set('new_trial_ends_at', null);
|
$set('new_trial_ends_at', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$subscription = Subscription::find($subscriptionId);
|
$subscription = Subscription::find($subscriptionId);
|
||||||
if (! $subscription) {
|
if (! $subscription) {
|
||||||
$set('new_trial_ends_at', null);
|
$set('new_trial_ends_at', null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class TrialExtensionsTable
|
|||||||
->label('View Subscription')
|
->label('View Subscription')
|
||||||
->icon('heroicon-o-rectangle-stack')
|
->icon('heroicon-o-rectangle-stack')
|
||||||
->color('blue')
|
->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(),
|
->openUrlInNewTab(),
|
||||||
])
|
])
|
||||||
->toolbarActions([
|
->toolbarActions([
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use App\Filament\Resources\TrialExtensions\Pages\EditTrialExtension;
|
|||||||
use App\Filament\Resources\TrialExtensions\Pages\ListTrialExtensions;
|
use App\Filament\Resources\TrialExtensions\Pages\ListTrialExtensions;
|
||||||
use App\Filament\Resources\TrialExtensions\Schemas\TrialExtensionForm;
|
use App\Filament\Resources\TrialExtensions\Schemas\TrialExtensionForm;
|
||||||
use App\Filament\Resources\TrialExtensions\Tables\TrialExtensionsTable;
|
use App\Filament\Resources\TrialExtensions\Tables\TrialExtensionsTable;
|
||||||
use App\Models\Subscription;
|
|
||||||
use App\Models\TrialExtension;
|
use App\Models\TrialExtension;
|
||||||
use BackedEnum;
|
use BackedEnum;
|
||||||
use Filament\Resources\Resource;
|
use Filament\Resources\Resource;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace App\Filament\Widgets;
|
namespace App\Filament\Widgets;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Date;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
|
||||||
use App\Models\Log;
|
use App\Models\Log;
|
||||||
use App\Models\Meta;
|
use App\Models\Meta;
|
||||||
use App\Models\PremiumEmail;
|
use App\Models\PremiumEmail;
|
||||||
@@ -11,6 +9,8 @@ use App\Models\Ticket;
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
|
||||||
use Filament\Widgets\StatsOverviewWidget\Stat;
|
use Filament\Widgets\StatsOverviewWidget\Stat;
|
||||||
|
use Illuminate\Support\Facades\Date;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
class StatsOverview extends BaseWidget
|
class StatsOverview extends BaseWidget
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class ArrayHelper
|
|||||||
$keys = explode('.', $key);
|
$keys = explode('.', $key);
|
||||||
|
|
||||||
foreach ($keys as $segment) {
|
foreach ($keys as $segment) {
|
||||||
if (!isset($array[$segment])) {
|
if (! isset($array[$segment])) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
$array = $array[$segment];
|
$array = $array[$segment];
|
||||||
@@ -32,7 +32,7 @@ class ArrayHelper
|
|||||||
);
|
);
|
||||||
} catch (\JsonException $e) {
|
} catch (\JsonException $e) {
|
||||||
// Optional: Log the error
|
// Optional: Log the error
|
||||||
Log::error("JSON encode failed: " . $e->getMessage());
|
Log::error('JSON encode failed: '.$e->getMessage());
|
||||||
|
|
||||||
// Fallback: return empty object instead of crashing
|
// Fallback: return empty object instead of crashing
|
||||||
return '{}';
|
return '{}';
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Session;
|
|
||||||
use Illuminate\Routing\Redirector;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use App\Models\Premium;
|
use App\Models\Premium;
|
||||||
use App\Models\ZEmail;
|
use App\Models\ZEmail;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Routing\Redirector;
|
||||||
|
|
||||||
class AppController extends Controller
|
class AppController extends Controller
|
||||||
{
|
{
|
||||||
@@ -56,6 +55,7 @@ class AppController extends Controller
|
|||||||
|
|
||||||
return to_route('mailbox');
|
return to_route('mailbox');
|
||||||
}
|
}
|
||||||
|
|
||||||
return to_route('home');
|
return to_route('home');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +86,7 @@ class AppController extends Controller
|
|||||||
|
|
||||||
return to_route('dashboard.premium');
|
return to_route('dashboard.premium');
|
||||||
}
|
}
|
||||||
|
|
||||||
return to_route('dashboard');
|
return to_route('dashboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ class PaymentCancelController extends Controller
|
|||||||
|
|
||||||
Log::info('PaymentCancelController: Cancellation page accessed', [
|
Log::info('PaymentCancelController: Cancellation page accessed', [
|
||||||
'user_id' => auth()->id(),
|
'user_id' => auth()->id(),
|
||||||
'session_token' => $sessionToken ? substr($sessionToken, 0, 20) . '...' : 'none',
|
'session_token' => $sessionToken ? substr($sessionToken, 0, 20).'...' : 'none',
|
||||||
'ip_address' => $request->ip(),
|
'ip_address' => $request->ip(),
|
||||||
'user_agent' => $request->userAgent(),
|
'user_agent' => $request->userAgent(),
|
||||||
]);
|
]);
|
||||||
@@ -48,4 +48,4 @@ class PaymentCancelController extends Controller
|
|||||||
'recentSubscription' => $recentSubscription,
|
'recentSubscription' => $recentSubscription,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Auth;
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
class CheckUserBanned
|
class CheckUserBanned
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Actions;
|
namespace App\Livewire\Actions;
|
||||||
|
|
||||||
use Illuminate\Routing\Redirector;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Routing\Redirector;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Session;
|
use Illuminate\Support\Facades\Session;
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Session;
|
use App\Models\ZEmail;
|
||||||
use Illuminate\Contracts\View\Factory;
|
use Illuminate\Contracts\View\Factory;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use App\Models\ZEmail;
|
use Illuminate\Support\Facades\Session;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class AddOn extends Component
|
class AddOn extends Component
|
||||||
@@ -31,6 +31,7 @@ class AddOn extends Component
|
|||||||
if (count($messages['data']) > 0) {
|
if (count($messages['data']) > 0) {
|
||||||
return to_route('mailbox');
|
return to_route('mailbox');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ class AddOn extends Component
|
|||||||
$this->faqSchema = [
|
$this->faqSchema = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'FAQPage',
|
'@type' => 'FAQPage',
|
||||||
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
|
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
|
||||||
'@type' => 'Question',
|
'@type' => 'Question',
|
||||||
'name' => $faq['title'],
|
'name' => $faq['title'],
|
||||||
'acceptedAnswer' => [
|
'acceptedAnswer' => [
|
||||||
@@ -66,7 +67,7 @@ class AddOn extends Component
|
|||||||
$this->faqSchema = [
|
$this->faqSchema = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'FAQPage',
|
'@type' => 'FAQPage',
|
||||||
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
|
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
|
||||||
'@type' => 'Question',
|
'@type' => 'Question',
|
||||||
'name' => $faq['title'],
|
'name' => $faq['title'],
|
||||||
'acceptedAnswer' => [
|
'acceptedAnswer' => [
|
||||||
@@ -85,7 +86,7 @@ class AddOn extends Component
|
|||||||
$this->faqSchema = [
|
$this->faqSchema = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'FAQPage',
|
'@type' => 'FAQPage',
|
||||||
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
|
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
|
||||||
'@type' => 'Question',
|
'@type' => 'Question',
|
||||||
'name' => $faq['title'],
|
'name' => $faq['title'],
|
||||||
'acceptedAnswer' => [
|
'acceptedAnswer' => [
|
||||||
@@ -103,7 +104,7 @@ class AddOn extends Component
|
|||||||
$this->faqSchema = [
|
$this->faqSchema = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'FAQPage',
|
'@type' => 'FAQPage',
|
||||||
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
|
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
|
||||||
'@type' => 'Question',
|
'@type' => 'Question',
|
||||||
'name' => $faq['title'],
|
'name' => $faq['title'],
|
||||||
'acceptedAnswer' => [
|
'acceptedAnswer' => [
|
||||||
@@ -121,7 +122,7 @@ class AddOn extends Component
|
|||||||
$this->faqSchema = [
|
$this->faqSchema = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'FAQPage',
|
'@type' => 'FAQPage',
|
||||||
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
|
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
|
||||||
'@type' => 'Question',
|
'@type' => 'Question',
|
||||||
'name' => $faq['title'],
|
'name' => $faq['title'],
|
||||||
'acceptedAnswer' => [
|
'acceptedAnswer' => [
|
||||||
@@ -139,7 +140,7 @@ class AddOn extends Component
|
|||||||
$this->faqSchema = [
|
$this->faqSchema = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'FAQPage',
|
'@type' => 'FAQPage',
|
||||||
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
|
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
|
||||||
'@type' => 'Question',
|
'@type' => 'Question',
|
||||||
'name' => $faq['title'],
|
'name' => $faq['title'],
|
||||||
'acceptedAnswer' => [
|
'acceptedAnswer' => [
|
||||||
@@ -157,7 +158,7 @@ class AddOn extends Component
|
|||||||
$this->faqSchema = [
|
$this->faqSchema = [
|
||||||
'@context' => 'https://schema.org',
|
'@context' => 'https://schema.org',
|
||||||
'@type' => 'FAQPage',
|
'@type' => 'FAQPage',
|
||||||
'mainEntity' => collect($this->faqs)->map(fn(array $faq): array => [
|
'mainEntity' => collect($this->faqs)->map(fn (array $faq): array => [
|
||||||
'@type' => 'Question',
|
'@type' => 'Question',
|
||||||
'name' => $faq['title'],
|
'name' => $faq['title'],
|
||||||
'acceptedAnswer' => [
|
'acceptedAnswer' => [
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Dashboard;
|
namespace App\Livewire\Dashboard;
|
||||||
|
|
||||||
|
use App\Models\Subscription;
|
||||||
use App\Models\UsageLog;
|
use App\Models\UsageLog;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
@@ -10,7 +11,6 @@ use Illuminate\Support\Facades\Date;
|
|||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Stripe\StripeClient;
|
use Stripe\StripeClient;
|
||||||
use App\Models\Subscription;
|
|
||||||
|
|
||||||
class Dashboard extends Component
|
class Dashboard extends Component
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Dashboard;
|
namespace App\Livewire\Dashboard;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Support\Facades\Request;
|
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
use App\Models\TicketResponse;
|
use App\Models\TicketResponse;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\Request;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Support extends Component
|
class Support extends Component
|
||||||
@@ -129,9 +129,9 @@ class Support extends Component
|
|||||||
|
|
||||||
public function updateTicketCounts(): void
|
public function updateTicketCounts(): void
|
||||||
{
|
{
|
||||||
$this->open = $this->tickets->filter(fn($ticket): bool => in_array($ticket->status, ['open', 'pending']))->count();
|
$this->open = $this->tickets->filter(fn ($ticket): bool => in_array($ticket->status, ['open', 'pending']))->count();
|
||||||
|
|
||||||
$this->closed = $this->tickets->filter(fn($ticket): bool => $ticket->status === 'closed')->count();
|
$this->closed = $this->tickets->filter(fn ($ticket): bool => $ticket->status === 'closed')->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getClientIp()
|
protected function getClientIp()
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Frontend;
|
namespace App\Livewire\Frontend;
|
||||||
|
|
||||||
use Illuminate\Routing\Redirector;
|
|
||||||
use Illuminate\Http\RedirectResponse;
|
|
||||||
use App\Models\ZEmail;
|
use App\Models\ZEmail;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Routing\Redirector;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Email extends Component
|
class Email extends Component
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class Mailbox extends Component
|
|||||||
if (! ZEmail::getEmail()) {
|
if (! ZEmail::getEmail()) {
|
||||||
return to_route('home');
|
return to_route('home');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Livewire;
|
namespace App\Livewire;
|
||||||
|
|
||||||
|
use App\Models\ZEmail;
|
||||||
use Illuminate\Contracts\View\Factory;
|
use Illuminate\Contracts\View\Factory;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use App\Models\ZEmail;
|
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
class Home extends Component
|
class Home extends Component
|
||||||
@@ -18,6 +18,7 @@ class Home extends Component
|
|||||||
if (count($messages['data']) > 0) {
|
if (count($messages['data']) > 0) {
|
||||||
return to_route('mailbox');
|
return to_route('mailbox');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,9 +18,7 @@ class TicketResponseNotification extends Mailable
|
|||||||
/**
|
/**
|
||||||
* Create a new message instance.
|
* Create a new message instance.
|
||||||
*/
|
*/
|
||||||
public function __construct(public Ticket $ticket, public Collection $responses)
|
public function __construct(public Ticket $ticket, public Collection $responses) {}
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the message envelope.
|
* Get the message envelope.
|
||||||
|
|||||||
@@ -2,18 +2,17 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use DateTimeImmutable;
|
|
||||||
use Illuminate\Support\Facades\Date;
|
|
||||||
use App\ColorPicker;
|
use App\ColorPicker;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Ddeboer\Imap\ConnectionInterface;
|
use Ddeboer\Imap\ConnectionInterface;
|
||||||
use Ddeboer\Imap\Search\Date\Since;
|
use Ddeboer\Imap\Search\Date\Since;
|
||||||
use Ddeboer\Imap\Server;
|
use Ddeboer\Imap\Server;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\File;
|
use Illuminate\Support\Facades\Date;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
@@ -78,7 +77,7 @@ class Email extends Model
|
|||||||
|
|
||||||
$sender = $message->getFrom();
|
$sender = $message->getFrom();
|
||||||
$date = $message->getDate();
|
$date = $message->getDate();
|
||||||
if (!$date instanceof DateTimeImmutable) {
|
if (! $date instanceof DateTimeImmutable) {
|
||||||
$date = new DateTime;
|
$date = new DateTime;
|
||||||
if ($message->getHeaders()->get('udate')) {
|
if ($message->getHeaders()->get('udate')) {
|
||||||
$date->setTimestamp($message->getHeaders()->get('udate'));
|
$date->setTimestamp($message->getHeaders()->get('udate'));
|
||||||
@@ -100,11 +99,11 @@ class Email extends Model
|
|||||||
|
|
||||||
$obj = [];
|
$obj = [];
|
||||||
|
|
||||||
$to = $message->getHeaders()->get('To') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('To')) : [];
|
$to = $message->getHeaders()->get('To') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('To')) : [];
|
||||||
|
|
||||||
$cc = $message->getHeaders()->get('Cc') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Cc')) : [];
|
$cc = $message->getHeaders()->get('Cc') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Cc')) : [];
|
||||||
|
|
||||||
$bcc = $message->getHeaders()->get('Bcc') ? array_map(fn($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Bcc')) : [];
|
$bcc = $message->getHeaders()->get('Bcc') ? array_map(fn ($entry): string => $entry->mailbox.'@'.$entry->host, $message->getHeaders()->get('Bcc')) : [];
|
||||||
|
|
||||||
$messageTime = $message->getDate();
|
$messageTime = $message->getDate();
|
||||||
$utcTime = CarbonImmutable::instance($messageTime)->setTimezone('UTC')->toDateTimeString();
|
$utcTime = CarbonImmutable::instance($messageTime)->setTimezone('UTC')->toDateTimeString();
|
||||||
@@ -127,28 +126,27 @@ class Email extends Model
|
|||||||
|
|
||||||
if ($message->hasAttachments()) {
|
if ($message->hasAttachments()) {
|
||||||
$attachments = $message->getAttachments();
|
$attachments = $message->getAttachments();
|
||||||
$directory = './tmp/attachments/'.$obj['id'].'/';
|
$directoryPath = 'attachments/'.$obj['id'];
|
||||||
|
|
||||||
if (!is_dir($directory)) {
|
|
||||||
mkdir($directory, 0777, true);
|
|
||||||
}
|
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
$filenameArray = explode('.', (string) $attachment->getFilename());
|
$filenameArray = explode('.', (string) $attachment->getFilename());
|
||||||
$extension = $filenameArray[count($filenameArray) - 1];
|
$extension = $filenameArray[count($filenameArray) - 1];
|
||||||
if (in_array($extension, $allowed)) {
|
if (in_array($extension, $allowed)) {
|
||||||
if (! file_exists($directory.$attachment->getFilename())) {
|
$filePath = $directoryPath.'/'.$attachment->getFilename();
|
||||||
|
|
||||||
|
if (! \Illuminate\Support\Facades\Storage::disk('public')->exists($filePath)) {
|
||||||
try {
|
try {
|
||||||
file_put_contents(
|
\Illuminate\Support\Facades\Storage::disk('public')->put(
|
||||||
$directory.$attachment->getFilename(),
|
$filePath,
|
||||||
$attachment->getDecodedContent()
|
$attachment->getDecodedContent()
|
||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($attachment->getFilename() !== 'undefined') {
|
if ($attachment->getFilename() !== 'undefined') {
|
||||||
$url = config('app.settings.app_base_url').str_replace('./', '/', $directory.$attachment->getFilename());
|
$url = config('app.settings.app_base_url').'/storage/'.$filePath;
|
||||||
$structure = $attachment->getStructure();
|
$structure = $attachment->getStructure();
|
||||||
if (isset($structure->id) && str_contains($obj['content'], trim($structure->id, '<>'))) {
|
if (isset($structure->id) && str_contains($obj['content'], trim($structure->id, '<>'))) {
|
||||||
$obj['content'] = str_replace('cid:'.trim($structure->id, '<>'), $url, $obj['content']);
|
$obj['content'] = str_replace('cid:'.trim($structure->id, '<>'), $url, $obj['content']);
|
||||||
@@ -159,9 +157,7 @@ class Email extends Model
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$response['data'][] = $obj;
|
$response['data'][] = $obj;
|
||||||
@@ -354,11 +350,9 @@ class Email extends Model
|
|||||||
|
|
||||||
public static function deleteBulkAttachments(): void
|
public static function deleteBulkAttachments(): void
|
||||||
{
|
{
|
||||||
$dir = public_path('/tmp/attachments');
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (File::exists($dir)) {
|
if (\Illuminate\Support\Facades\Storage::disk('public')->exists('attachments')) {
|
||||||
File::cleanDirectory($dir);
|
\Illuminate\Support\Facades\Storage::disk('public')->deleteDirectory('attachments');
|
||||||
}
|
}
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
@@ -431,6 +425,7 @@ class Email extends Model
|
|||||||
|
|
||||||
$currentTime = Date::now('UTC');
|
$currentTime = Date::now('UTC');
|
||||||
$lastRecordTime = Date::parse($latestRecord->timestamp);
|
$lastRecordTime = Date::parse($latestRecord->timestamp);
|
||||||
|
|
||||||
return $lastRecordTime->diffInMinutes($currentTime) < 5;
|
return $lastRecordTime->diffInMinutes($currentTime) < 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Date;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Date;
|
||||||
|
|
||||||
class Log extends Model
|
class Log extends Model
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use DateTimeImmutable;
|
|
||||||
use App\ColorPicker;
|
use App\ColorPicker;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use DateTime;
|
use DateTime;
|
||||||
|
use DateTimeImmutable;
|
||||||
use Ddeboer\Imap\Search\Email\Cc;
|
use Ddeboer\Imap\Search\Email\Cc;
|
||||||
use Ddeboer\Imap\Search\Email\To;
|
use Ddeboer\Imap\Search\Email\To;
|
||||||
use Ddeboer\Imap\SearchExpression;
|
use Ddeboer\Imap\SearchExpression;
|
||||||
@@ -41,7 +41,7 @@ class Message extends Model
|
|||||||
$message->attachments = $request->get('attachment-info');
|
$message->attachments = $request->get('attachment-info');
|
||||||
$message->save();
|
$message->save();
|
||||||
$directory = './attachments/'.$message->id;
|
$directory = './attachments/'.$message->id;
|
||||||
if (!is_dir($directory)) {
|
if (! is_dir($directory)) {
|
||||||
mkdir($directory, 0777, true);
|
mkdir($directory, 0777, true);
|
||||||
}
|
}
|
||||||
$attachment_ids = json_decode((string) $request->get('attachment-info'));
|
$attachment_ids = json_decode((string) $request->get('attachment-info'));
|
||||||
@@ -146,7 +146,7 @@ class Message extends Model
|
|||||||
$blocked = false;
|
$blocked = false;
|
||||||
$sender = $message->getFrom();
|
$sender = $message->getFrom();
|
||||||
$date = $message->getDate();
|
$date = $message->getDate();
|
||||||
if (!$date instanceof DateTimeImmutable) {
|
if (! $date instanceof DateTimeImmutable) {
|
||||||
$date = new DateTime;
|
$date = new DateTime;
|
||||||
if ($message->getHeaders()->get('udate')) {
|
if ($message->getHeaders()->get('udate')) {
|
||||||
$date->setTimestamp($message->getHeaders()->get('udate'));
|
$date->setTimestamp($message->getHeaders()->get('udate'));
|
||||||
@@ -193,7 +193,7 @@ class Message extends Model
|
|||||||
if ($message->hasAttachments() && ! $blocked) {
|
if ($message->hasAttachments() && ! $blocked) {
|
||||||
$attachments = $message->getAttachments();
|
$attachments = $message->getAttachments();
|
||||||
$directory = './tmp/attachments/'.$obj['id'].'/';
|
$directory = './tmp/attachments/'.$obj['id'].'/';
|
||||||
if (!is_dir($directory)) {
|
if (! is_dir($directory)) {
|
||||||
mkdir($directory, 0777, true);
|
mkdir($directory, 0777, true);
|
||||||
}
|
}
|
||||||
foreach ($attachments as $attachment) {
|
foreach ($attachments as $attachment) {
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Date;
|
|
||||||
use App\ColorPicker;
|
use App\ColorPicker;
|
||||||
use Carbon\CarbonImmutable;
|
use Carbon\CarbonImmutable;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Date;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
|
|
||||||
class PremiumEmail extends Model
|
class PremiumEmail extends Model
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Ddeboer\Imap\ConnectionInterface;
|
use Ddeboer\Imap\ConnectionInterface;
|
||||||
use Ddeboer\Imap\Server;
|
use Ddeboer\Imap\Server;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Support\Facades\Cookie;
|
use Illuminate\Support\Facades\Cookie;
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ class ZEmail extends Model
|
|||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
public static function connectMailBox($imap = null): ConnectionInterface
|
public static function connectMailBox($imap = null): ConnectionInterface
|
||||||
{
|
{
|
||||||
if ($imap === null) {
|
if ($imap === null) {
|
||||||
@@ -38,6 +39,7 @@ class ZEmail extends Model
|
|||||||
if (Email::mailToDBStatus()) {
|
if (Email::mailToDBStatus()) {
|
||||||
return Email::parseEmail($email, $deleted);
|
return Email::parseEmail($email, $deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Message::fetchMessages($email, $type, $deleted);
|
return Message::fetchMessages($email, $type, $deleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,6 +59,7 @@ class ZEmail extends Model
|
|||||||
if (Cookie::has('email')) {
|
if (Cookie::has('email')) {
|
||||||
return Cookie::get('email');
|
return Cookie::get('email');
|
||||||
}
|
}
|
||||||
|
|
||||||
return $generate ? ZEmail::generateRandomEmail() : null;
|
return $generate ? ZEmail::generateRandomEmail() : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +68,7 @@ class ZEmail extends Model
|
|||||||
if (Cookie::has('emails')) {
|
if (Cookie::has('emails')) {
|
||||||
return unserialize(Cookie::get('emails'));
|
return unserialize(Cookie::get('emails'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
$this->appConfig = [
|
$this->appConfig = [
|
||||||
'website_settings' => [],
|
'website_settings' => [],
|
||||||
'imap_settings' => [],
|
'imap_settings' => [],
|
||||||
'configuration_settings' => []
|
'configuration_settings' => [],
|
||||||
];
|
];
|
||||||
Log::error($e->getMessage());
|
Log::error($e->getMessage());
|
||||||
}
|
}
|
||||||
@@ -114,40 +114,40 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
private function loadLegacySettings(): array
|
private function loadLegacySettings(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
"app_name" => $this->getConfig('website_settings.app_name'),
|
'app_name' => $this->getConfig('website_settings.app_name'),
|
||||||
"app_version" => $this->getConfig('website_settings.app_version'),
|
'app_version' => $this->getConfig('website_settings.app_version'),
|
||||||
"app_base_url" => $this->getConfig('website_settings.app_base_url'),
|
'app_base_url' => $this->getConfig('website_settings.app_base_url'),
|
||||||
"app_admin" => $this->getConfig('website_settings.app_admin'),
|
'app_admin' => $this->getConfig('website_settings.app_admin'),
|
||||||
"app_title" => $this->getConfig('website_settings.app_title'),
|
'app_title' => $this->getConfig('website_settings.app_title'),
|
||||||
"app_description" => $this->getConfig('website_settings.app_description'),
|
'app_description' => $this->getConfig('website_settings.app_description'),
|
||||||
"app_keywords" => $this->getConfig('website_settings.app_keywords'),
|
'app_keywords' => $this->getConfig('website_settings.app_keywords'),
|
||||||
"app_contact" => $this->getConfig('website_settings.app_contact'),
|
'app_contact' => $this->getConfig('website_settings.app_contact'),
|
||||||
"app_meta" => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.app_meta')),
|
'app_meta' => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.app_meta')),
|
||||||
"app_social" => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.app_social')),
|
'app_social' => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.app_social')),
|
||||||
"app_header" => $this->getConfig('website_settings.app_header'),
|
'app_header' => $this->getConfig('website_settings.app_header'),
|
||||||
"app_footer" => $this->getConfig('website_settings.app_footer'),
|
'app_footer' => $this->getConfig('website_settings.app_footer'),
|
||||||
"imap_settings" => ArrayHelper::jsonEncodeSafe([
|
'imap_settings' => ArrayHelper::jsonEncodeSafe([
|
||||||
"host" => $this->getConfig('imap_settings.public.host'),
|
'host' => $this->getConfig('imap_settings.public.host'),
|
||||||
"port" => $this->getConfig('imap_settings.public.port'),
|
'port' => $this->getConfig('imap_settings.public.port'),
|
||||||
"username" => $this->getConfig('imap_settings.public.username'),
|
'username' => $this->getConfig('imap_settings.public.username'),
|
||||||
"password" => $this->getConfig('imap_settings.public.password'),
|
'password' => $this->getConfig('imap_settings.public.password'),
|
||||||
"encryption" => $this->getConfig('imap_settings.public.encryption'),
|
'encryption' => $this->getConfig('imap_settings.public.encryption'),
|
||||||
"validate_cert" => $this->getConfig('imap_settings.public.validate_cert'),
|
'validate_cert' => $this->getConfig('imap_settings.public.validate_cert'),
|
||||||
"default_account" => $this->getConfig('imap_settings.public.default_account'),
|
'default_account' => $this->getConfig('imap_settings.public.default_account'),
|
||||||
"protocol" => $this->getConfig('imap_settings.public.protocol'),
|
'protocol' => $this->getConfig('imap_settings.public.protocol'),
|
||||||
"cc_check" => $this->getConfig('imap_settings.public.cc_check'),
|
'cc_check' => $this->getConfig('imap_settings.public.cc_check'),
|
||||||
"premium_host" => $this->getConfig('imap_settings.premium.host'),
|
'premium_host' => $this->getConfig('imap_settings.premium.host'),
|
||||||
"premium_port" => $this->getConfig('imap_settings.premium.port'),
|
'premium_port' => $this->getConfig('imap_settings.premium.port'),
|
||||||
"premium_username" => $this->getConfig('imap_settings.premium.username'),
|
'premium_username' => $this->getConfig('imap_settings.premium.username'),
|
||||||
"premium_password" => $this->getConfig('imap_settings.premium.password'),
|
'premium_password' => $this->getConfig('imap_settings.premium.password'),
|
||||||
"premium_encryption" => $this->getConfig('imap_settings.premium.premium_encryption'),
|
'premium_encryption' => $this->getConfig('imap_settings.premium.premium_encryption'),
|
||||||
"premium_validate_cert" => $this->getConfig('imap_settings.premium.validate_cert'),
|
'premium_validate_cert' => $this->getConfig('imap_settings.premium.validate_cert'),
|
||||||
"premium_default_account" => $this->getConfig('imap_settings.premium.default_account'),
|
'premium_default_account' => $this->getConfig('imap_settings.premium.default_account'),
|
||||||
"premium_protocol" => $this->getConfig('imap_settings.premium.protocol'),
|
'premium_protocol' => $this->getConfig('imap_settings.premium.protocol'),
|
||||||
"premium_cc_check" => $this->getConfig('imap_settings.premium.cc_check'),
|
'premium_cc_check' => $this->getConfig('imap_settings.premium.cc_check'),
|
||||||
]),
|
]),
|
||||||
"configuration_settings" => ArrayHelper::jsonEncodeSafe($this->appConfig['configuration_settings']),
|
'configuration_settings' => ArrayHelper::jsonEncodeSafe($this->appConfig['configuration_settings']),
|
||||||
"ads_settings" => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.ads_settings')),
|
'ads_settings' => ArrayHelper::jsonEncodeSafe($this->getConfig('website_settings.ads_settings')),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class OxapayProvider implements PaymentProviderContract
|
|||||||
$config = array_merge($dbConfig, $config);
|
$config = array_merge($dbConfig, $config);
|
||||||
|
|
||||||
$this->config = $config;
|
$this->config = $config;
|
||||||
$this->sandbox = $config['sandbox'] === "true" ?? false;
|
$this->sandbox = $config['sandbox'] === 'true' ?? false;
|
||||||
$this->merchantApiKey = $this->sandbox ? ($config['sandbox_merchant_api_key'] ?? '') : ($config['merchant_api_key'] ?? '');
|
$this->merchantApiKey = $this->sandbox ? ($config['sandbox_merchant_api_key'] ?? '') : ($config['merchant_api_key'] ?? '');
|
||||||
|
|
||||||
$this->baseUrl = 'https://api.oxapay.com/v1';
|
$this->baseUrl = 'https://api.oxapay.com/v1';
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ $app = require_once __DIR__.'/bootstrap/app.php';
|
|||||||
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Run the Artisan command 'ping'
|
// Run the Artisan command
|
||||||
$exitCode = $kernel->call('cleanMail');
|
$exitCode = $kernel->call('mailbox:clean');
|
||||||
|
|
||||||
// Get the output of the command
|
// Get the output of the command
|
||||||
$output = $kernel->output();
|
$output = $kernel->output();
|
||||||
|
|
||||||
echo "Artisan command 'schedule:run' executed successfully. Exit code: $exitCode\n";
|
echo "Artisan command executed successfully. Exit code: $exitCode\n";
|
||||||
echo "Output:\n$output";
|
echo "Output:\n$output";
|
||||||
|
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ return [
|
|||||||
'pattern' => [
|
'pattern' => [
|
||||||
'prefix' => 'laravel-',
|
'prefix' => 'laravel-',
|
||||||
'date' => '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]',
|
'date' => '[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]',
|
||||||
'extension' => '.log'
|
'extension' => '.log',
|
||||||
],
|
],
|
||||||
|
|
||||||
/* -----------------------------------------------------------------
|
/* -----------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Laravel\Sanctum\Http\Middleware\AuthenticateSession;
|
|
||||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||||
|
use Laravel\Sanctum\Http\Middleware\AuthenticateSession;
|
||||||
use Laravel\Sanctum\Sanctum;
|
use Laravel\Sanctum\Sanctum;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
use App\Models\TicketResponse;
|
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
|
use App\Models\TicketResponse;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ return new class extends Migration
|
|||||||
$table->longText('html')->nullable()->change();
|
$table->longText('html')->nullable()->change();
|
||||||
$table->longText('text')->nullable()->change();
|
$table->longText('text')->nullable()->change();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down()
|
public function down()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ return new class extends Migration
|
|||||||
Schema::table(config('mails.database.tables.events', 'mail_events'), function (Blueprint $table) {
|
Schema::table(config('mails.database.tables.events', 'mail_events'), function (Blueprint $table) {
|
||||||
$table->longText('link')->nullable()->change();
|
$table->longText('link')->nullable()->change();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down()
|
public function down()
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class CreateActivityLogTable extends Migration
|
class CreateActivityLogTable extends Migration
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class AddEventColumnToActivityLogTable extends Migration
|
class AddEventColumnToActivityLogTable extends Migration
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
class AddBatchUuidColumnToActivityLogTable extends Migration
|
class AddBatchUuidColumnToActivityLogTable extends Migration
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class extends Migration
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@ return new class extends Migration
|
|||||||
try {
|
try {
|
||||||
// MySQL automatically names check constraints in different ways,
|
// MySQL automatically names check constraints in different ways,
|
||||||
// so we attempt a generic drop and ignore failures.
|
// so we attempt a generic drop and ignore failures.
|
||||||
DB::statement("ALTER TABLE `payment_providers` DROP CHECK `payment_providers.configuration`;");
|
DB::statement('ALTER TABLE `payment_providers` DROP CHECK `payment_providers.configuration`;');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
// ignore if constraint does not exist
|
// ignore if constraint does not exist
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ return new class extends Migration
|
|||||||
AND pg_get_constraintdef(pg_constraint.oid) LIKE '%configuration%json%';
|
AND pg_get_constraintdef(pg_constraint.oid) LIKE '%configuration%json%';
|
||||||
");
|
");
|
||||||
|
|
||||||
if (!empty($constraint->conname)) {
|
if (! empty($constraint->conname)) {
|
||||||
DB::statement("ALTER TABLE payment_providers DROP CONSTRAINT {$constraint->conname};");
|
DB::statement("ALTER TABLE payment_providers DROP CONSTRAINT {$constraint->conname};");
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
|||||||
@@ -34,4 +34,4 @@ return new class extends Migration
|
|||||||
$table->unique('username', 'usernames_username_unique');
|
$table->unique('username', 'usernames_username_unique');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace Database\Seeders;
|
namespace Database\Seeders;
|
||||||
|
|
||||||
use stdClass;
|
|
||||||
use App\Models\Meta;
|
use App\Models\Meta;
|
||||||
use Illuminate\Database\Seeder;
|
use Illuminate\Database\Seeder;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
class MetaSeeder extends Seeder
|
class MetaSeeder extends Seeder
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ class PaymentProviderSeeder extends Seeder
|
|||||||
'secret_key' => env('STRIPE_SECRET') ?: 'sk_test_placeholder',
|
'secret_key' => env('STRIPE_SECRET') ?: 'sk_test_placeholder',
|
||||||
'publishable_key' => env('STRIPE_PUBLISHABLE_KEY') ?: 'pk_test_placeholder',
|
'publishable_key' => env('STRIPE_PUBLISHABLE_KEY') ?: 'pk_test_placeholder',
|
||||||
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET') ?: 'whsec_placeholder',
|
'webhook_secret' => env('STRIPE_WEBHOOK_SECRET') ?: 'whsec_placeholder',
|
||||||
'webhook_url' => env('APP_URL', 'https://example.com') . '/webhook/stripe',
|
'webhook_url' => env('APP_URL', 'https://example.com').'/webhook/stripe',
|
||||||
'success_url' => env('APP_URL', 'https://example.com') . '/payment/success',
|
'success_url' => env('APP_URL', 'https://example.com').'/payment/success',
|
||||||
'cancel_url' => env('APP_URL', 'https://example.com') . '/payment/cancel',
|
'cancel_url' => env('APP_URL', 'https://example.com').'/payment/cancel',
|
||||||
'currency' => env('CASHIER_CURRENCY', 'USD'),
|
'currency' => env('CASHIER_CURRENCY', 'USD'),
|
||||||
],
|
],
|
||||||
'supports_recurring' => true,
|
'supports_recurring' => true,
|
||||||
'supports_one_time' => true,
|
'supports_one_time' => true,
|
||||||
'supported_currencies' => [
|
'supported_currencies' => [
|
||||||
'USD' => 'US Dollar'
|
'USD' => 'US Dollar',
|
||||||
],
|
],
|
||||||
'fee_structure' => [
|
'fee_structure' => [
|
||||||
'fixed_fee' => '0.50',
|
'fixed_fee' => '0.50',
|
||||||
@@ -56,14 +56,14 @@ class PaymentProviderSeeder extends Seeder
|
|||||||
'api_key' => env('LEMON_SQUEEZY_API_KEY', 'lsk_...'),
|
'api_key' => env('LEMON_SQUEEZY_API_KEY', 'lsk_...'),
|
||||||
'store_id' => env('LEMON_SQUEEZY_STORE_ID', '...'),
|
'store_id' => env('LEMON_SQUEEZY_STORE_ID', '...'),
|
||||||
'webhook_secret' => env('LEMON_SQUEEZY_WEBHOOK_SECRET', 'whsec_...'),
|
'webhook_secret' => env('LEMON_SQUEEZY_WEBHOOK_SECRET', 'whsec_...'),
|
||||||
'webhook_url' => env('APP_URL') . '/webhook/lemon-squeezy',
|
'webhook_url' => env('APP_URL').'/webhook/lemon-squeezy',
|
||||||
'success_url' => env('APP_URL') . '/payment/success',
|
'success_url' => env('APP_URL').'/payment/success',
|
||||||
'cancel_url' => env('APP_URL') . '/payment/cancel',
|
'cancel_url' => env('APP_URL').'/payment/cancel',
|
||||||
],
|
],
|
||||||
'supports_recurring' => true,
|
'supports_recurring' => true,
|
||||||
'supports_one_time' => true,
|
'supports_one_time' => true,
|
||||||
'supported_currencies' => [
|
'supported_currencies' => [
|
||||||
'USD' => 'US Dollar'
|
'USD' => 'US Dollar',
|
||||||
],
|
],
|
||||||
'fee_structure' => [
|
'fee_structure' => [
|
||||||
'fixed_fee' => '0.50',
|
'fixed_fee' => '0.50',
|
||||||
@@ -86,9 +86,9 @@ class PaymentProviderSeeder extends Seeder
|
|||||||
'sandbox_api_key' => env('POLAR_SANDBOX_API_KEY', 'pol_test_...'),
|
'sandbox_api_key' => env('POLAR_SANDBOX_API_KEY', 'pol_test_...'),
|
||||||
'sandbox_webhook_secret' => env('POLAR_SANDBOX_WEBHOOK_SECRET', 'whsec_test_...'),
|
'sandbox_webhook_secret' => env('POLAR_SANDBOX_WEBHOOK_SECRET', 'whsec_test_...'),
|
||||||
'access_token' => env('POLAR_ACCESS_TOKEN', 'polar_...'),
|
'access_token' => env('POLAR_ACCESS_TOKEN', 'polar_...'),
|
||||||
'webhook_url' => env('APP_URL') . '/webhook/polar',
|
'webhook_url' => env('APP_URL').'/webhook/polar',
|
||||||
'success_url' => env('APP_URL') . '/payment/success',
|
'success_url' => env('APP_URL').'/payment/success',
|
||||||
'cancel_url' => env('APP_URL') . '/payment/cancel',
|
'cancel_url' => env('APP_URL').'/payment/cancel',
|
||||||
],
|
],
|
||||||
'supports_recurring' => true,
|
'supports_recurring' => true,
|
||||||
'supports_one_time' => true,
|
'supports_one_time' => true,
|
||||||
@@ -115,9 +115,9 @@ class PaymentProviderSeeder extends Seeder
|
|||||||
'sandbox_merchant_api_key' => env('OXAPAY_SANDBOX_MERCHANT_API_KEY', 'merchant_sb_...'),
|
'sandbox_merchant_api_key' => env('OXAPAY_SANDBOX_MERCHANT_API_KEY', 'merchant_sb_...'),
|
||||||
'payout_api_key' => env('OXAPAY_PAYOUT_API_KEY', 'payout_...'),
|
'payout_api_key' => env('OXAPAY_PAYOUT_API_KEY', 'payout_...'),
|
||||||
'sandbox_payout_api_key' => env('OXAPAY_SANDBOX_PAYOUT_API_KEY', 'payout_sb_...'),
|
'sandbox_payout_api_key' => env('OXAPAY_SANDBOX_PAYOUT_API_KEY', 'payout_sb_...'),
|
||||||
'callback_url' => env('OXAPAY_CALLBACK_URL', env('APP_URL') . '/webhook/oxapay'),
|
'callback_url' => env('OXAPAY_CALLBACK_URL', env('APP_URL').'/webhook/oxapay'),
|
||||||
'success_url' => env('APP_URL') . '/payment/success',
|
'success_url' => env('APP_URL').'/payment/success',
|
||||||
'cancel_url' => env('APP_URL') . '/payment/cancel',
|
'cancel_url' => env('APP_URL').'/payment/cancel',
|
||||||
'sandbox' => env('OXAPAY_SANDBOX', true),
|
'sandbox' => env('OXAPAY_SANDBOX', true),
|
||||||
'currency' => env('OXAPAY_CURRENCY', 'USD'), // string
|
'currency' => env('OXAPAY_CURRENCY', 'USD'), // string
|
||||||
'lifetime' => env('OXAPAY_LIFETIME', 30), // integer · min: 15 · max: 2880
|
'lifetime' => env('OXAPAY_LIFETIME', 30), // integer · min: 15 · max: 2880
|
||||||
@@ -157,9 +157,9 @@ class PaymentProviderSeeder extends Seeder
|
|||||||
'exchange_rate_provider' => env('CRYPTO_EXCHANGE_RATE_PROVIDER', 'coingecko'),
|
'exchange_rate_provider' => env('CRYPTO_EXCHANGE_RATE_PROVIDER', 'coingecko'),
|
||||||
'coingecko_api_key' => env('COINGECKO_API_KEY'),
|
'coingecko_api_key' => env('COINGECKO_API_KEY'),
|
||||||
'blockchair_api_key' => env('BLOCKCHAIR_API_KEY'),
|
'blockchair_api_key' => env('BLOCKCHAIR_API_KEY'),
|
||||||
'webhook_url' => env('APP_URL') . '/webhook/crypto',
|
'webhook_url' => env('APP_URL').'/webhook/crypto',
|
||||||
'success_url' => env('APP_URL') . '/payment/success',
|
'success_url' => env('APP_URL').'/payment/success',
|
||||||
'cancel_url' => env('APP_URL') . '/payment/cancel',
|
'cancel_url' => env('APP_URL').'/payment/cancel',
|
||||||
'supported_wallets' => [
|
'supported_wallets' => [
|
||||||
'btc' => ['bitcoin', 'lightning'],
|
'btc' => ['bitcoin', 'lightning'],
|
||||||
'eth' => ['ethereum', 'erc20'],
|
'eth' => ['ethereum', 'erc20'],
|
||||||
@@ -227,6 +227,7 @@ class PaymentProviderSeeder extends Seeder
|
|||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$this->command->error("❌ Error seeding provider {$providerData['name']}: {$e->getMessage()}");
|
$this->command->error("❌ Error seeding provider {$providerData['name']}: {$e->getMessage()}");
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
docker-compose.yml
Normal file
61
docker-compose.yml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${APP_PORT:-8000}:80"
|
||||||
|
environment:
|
||||||
|
- APP_ENV=${APP_ENV:-production}
|
||||||
|
- APP_DEBUG=${APP_DEBUG:-false}
|
||||||
|
- APP_URL=${APP_URL}
|
||||||
|
- DB_CONNECTION=${DB_CONNECTION:-mysql}
|
||||||
|
- DB_HOST=${DB_HOST:-mariadb}
|
||||||
|
- DB_PORT=${DB_PORT:-3306}
|
||||||
|
- DB_DATABASE=${DB_DATABASE:-zemail}
|
||||||
|
- DB_USERNAME=${DB_USERNAME:-zemail_user}
|
||||||
|
- DB_PASSWORD=${DB_PASSWORD:-secret}
|
||||||
|
- CACHE_STORE=${CACHE_STORE:-redis}
|
||||||
|
- QUEUE_CONNECTION=${QUEUE_CONNECTION:-redis}
|
||||||
|
- SESSION_DRIVER=${SESSION_DRIVER:-redis}
|
||||||
|
- REDIS_HOST=${REDIS_HOST:-redis}
|
||||||
|
- REDIS_PASSWORD=${REDIS_PASSWORD:-null}
|
||||||
|
- REDIS_PORT=${REDIS_PORT:-6379}
|
||||||
|
healthcheck:
|
||||||
|
test: [ "CMD", "curl", "-f", "http://localhost/health" ]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
volumes:
|
||||||
|
- app-storage:/var/www/storage
|
||||||
|
- app-public-tmp:/var/www/public/tmp
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- mariadb
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redis-data:/data
|
||||||
|
|
||||||
|
mariadb:
|
||||||
|
image: mariadb:10.11
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- MYSQL_DATABASE=${DB_DATABASE:-zemail}
|
||||||
|
- MYSQL_USER=${DB_USERNAME:-zemail_user}
|
||||||
|
- MYSQL_PASSWORD=${DB_PASSWORD:-secret}
|
||||||
|
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD:-rootsecret}
|
||||||
|
volumes:
|
||||||
|
- mariadb-data:/var/lib/mysql
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
app-storage:
|
||||||
|
app-public-tmp:
|
||||||
|
redis-data:
|
||||||
|
mariadb-data:
|
||||||
74
dropmail.php
74
dropmail.php
@@ -11,71 +11,11 @@ $kernel->bootstrap();
|
|||||||
|
|
||||||
set_time_limit(0);
|
set_time_limit(0);
|
||||||
|
|
||||||
$newTimezone = 'Europe/London';
|
try {
|
||||||
date_default_timezone_set($newTimezone);
|
// Run the new Artisan command fallback
|
||||||
|
$exitCode = $kernel->call('mailbox:clean');
|
||||||
$imapDB = json_decode(config('app.settings.imap_settings') ?: '{}', true);
|
$output = $kernel->output();
|
||||||
|
echo $output;
|
||||||
// Mailbox credentials
|
} catch (\Exception $e) {
|
||||||
$hostname = '{'.($imapDB['host'] ?? 'localhost').':'.($imapDB['port'] ?? '993').'/ssl}INBOX';
|
echo 'Error running Artisan command: '.$e->getMessage();
|
||||||
$username = $imapDB['username'] ?? '';
|
|
||||||
$password = $imapDB['password'] ?? '';
|
|
||||||
|
|
||||||
// Connect to mailbox
|
|
||||||
$inbox = imap_open($hostname, $username, $password);
|
|
||||||
|
|
||||||
// Check for connection errors
|
|
||||||
if (! $inbox) {
|
|
||||||
exit('Could not connect to mailbox: '.imap_last_error());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current time in Unix timestamp
|
|
||||||
$current_time = time();
|
|
||||||
|
|
||||||
// Search for messages older than one day
|
|
||||||
// $search_criteria = 'BEFORE "' . date('d-M-Y', strtotime('-3 hours', $current_time)) . '"';
|
|
||||||
// $messages = imap_search($inbox, $search_criteria);
|
|
||||||
|
|
||||||
$messages = imap_search($inbox, 'ALL');
|
|
||||||
|
|
||||||
$batch_size = 10;
|
|
||||||
$deleted_count = 0;
|
|
||||||
|
|
||||||
// if ($messages) {
|
|
||||||
// $chunks = array_chunk($messages, $batch_size);
|
|
||||||
// foreach ($chunks as $chunk) {
|
|
||||||
// foreach ($chunk as $message_number) {
|
|
||||||
// imap_delete($inbox, $message_number);
|
|
||||||
// }
|
|
||||||
// imap_expunge($inbox);
|
|
||||||
// $deleted_count += count($chunk);
|
|
||||||
// }
|
|
||||||
// echo $deleted_count . ' messages older than specified time have been deleted.';
|
|
||||||
// } else {
|
|
||||||
// echo 'No messages older than specified time found in mailbox.';
|
|
||||||
// }
|
|
||||||
|
|
||||||
if ($messages) {
|
|
||||||
$chunks = array_chunk($messages, $batch_size);
|
|
||||||
foreach ($chunks as $chunk) {
|
|
||||||
foreach ($chunk as $message_number) {
|
|
||||||
// Get message header to fetch internal date
|
|
||||||
$header = imap_headerinfo($inbox, $message_number);
|
|
||||||
$date_str = $header->date;
|
|
||||||
$msg_time = strtotime($date_str);
|
|
||||||
|
|
||||||
// Check if message is older than 3 hours
|
|
||||||
if ($msg_time !== false && ($current_time - $msg_time) > 2 * 3600) {
|
|
||||||
imap_delete($inbox, $message_number);
|
|
||||||
$deleted_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
imap_expunge($inbox);
|
|
||||||
}
|
|
||||||
echo $deleted_count.' messages older than 2 hours have been deleted.';
|
|
||||||
} else {
|
|
||||||
echo 'No messages found in mailbox.';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close mailbox connection
|
|
||||||
imap_close($inbox);
|
|
||||||
|
|||||||
162
laravel_webhook_handover.md
Normal file
162
laravel_webhook_handover.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# MailOps Webhook Handover Document
|
||||||
|
|
||||||
|
This document provides the exact specifications needed to implement the receiving end of the MailOps email synchronization system within the Laravel application.
|
||||||
|
|
||||||
|
## 1. Webhook Endpoint Specification
|
||||||
|
|
||||||
|
The MailOps worker will push new emails to this exact endpoint on your Laravel server:
|
||||||
|
|
||||||
|
* **URL:** `POST https://your-laravel-app.com/api/webhooks/incoming_email`
|
||||||
|
* **Headers:**
|
||||||
|
* `Content-Type: application/json`
|
||||||
|
* `Authorization: Bearer <CONFIGURED_WEBHOOK_SECRET>` (You must configure this secret in both MailOps and Laravel).
|
||||||
|
|
||||||
|
### A. Expected JSON Payload (With Attachments)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hash": "a1b2c3d4e5f6g7h8i9j0...",
|
||||||
|
"metadata": {
|
||||||
|
"hash": "a1b2c3d4e5f6g7h8i9j0...",
|
||||||
|
"recipientEmail": "user@example.com",
|
||||||
|
"recipientName": "John Doe",
|
||||||
|
"senderEmail": "alert@service.com",
|
||||||
|
"senderName": "Service Alerts",
|
||||||
|
"domain": "example.com",
|
||||||
|
"subject": "Important Notification",
|
||||||
|
"received_at": "2026-02-26T17:35:00Z",
|
||||||
|
"attachments": [
|
||||||
|
{
|
||||||
|
"filename": "invoice.pdf",
|
||||||
|
"mimeType": "application/pdf",
|
||||||
|
"size": 102400,
|
||||||
|
"s3_path": "mail-attachments/2026/02/26/hash_invoice.pdf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"attachmentSize": 102400
|
||||||
|
},
|
||||||
|
"bodyText": "Plain text content...",
|
||||||
|
"bodyHtml": "<html>HTML content...</html>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
*(Note: `received_at` is in ISO 8601 format ending with `Z` to explicitly denote UTC. `bodyHtml` and `bodyText` are completely separated from the metadata to optimize database payload sizes).*
|
||||||
|
|
||||||
|
### B. Expected JSON Payload (NO Attachments)
|
||||||
|
When an email has no attachments, the `attachments` array will be empty and `attachmentSize` will be zero. Also, depending on the email client, `bodyHtml` or `bodyText` might be `null`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hash": "b2c3d4e5f6g7h8i9j0a1...",
|
||||||
|
"metadata": {
|
||||||
|
"hash": "b2c3d4e5f6g7h8i9j0a1...",
|
||||||
|
"recipientEmail": "user@example.com",
|
||||||
|
"recipientName": "",
|
||||||
|
"senderEmail": "friend@service.com",
|
||||||
|
"senderName": "Friend",
|
||||||
|
"domain": "example.com",
|
||||||
|
"subject": "Quick Question",
|
||||||
|
"received_at": "2026-02-26T17:38:12Z",
|
||||||
|
"attachments": [],
|
||||||
|
"attachmentSize": 0
|
||||||
|
},
|
||||||
|
"bodyText": "Hey, are we still fast approaching the deadline?",
|
||||||
|
"bodyHtml": null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Laravel Implementation Checklist
|
||||||
|
|
||||||
|
When you switch to the Laravel project, you need to build the following:
|
||||||
|
|
||||||
|
### Step 1: Route & Middleware
|
||||||
|
Define the API route and protect it with a simple Bearer token check.
|
||||||
|
```php
|
||||||
|
// routes/api.php
|
||||||
|
Route::post('/webhooks/incoming_email', [EmailWebhookController::class, 'handle'])
|
||||||
|
->middleware('verify.webhook.secret');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: The Controller
|
||||||
|
The controller persists the metadata to MariaDB and the heavy body to MongoDB. **Crucially**, it also checks if the MongoDB TTL index exists, and if not, automatically creates it using the value defined in your Laravel `.env` file (e.g., `EMAIL_BODY_TTL_SECONDS=259200`).
|
||||||
|
|
||||||
|
```php
|
||||||
|
// app/Http/Controllers/EmailWebhookController.php
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
public function handle(Request $request)
|
||||||
|
{
|
||||||
|
$payload = $request->all();
|
||||||
|
$meta = $payload['metadata'];
|
||||||
|
$hash = $payload['hash'];
|
||||||
|
|
||||||
|
// 1. Auto-Setup MongoDB TTL Index (Executes only once via Cache)
|
||||||
|
$this->ensureMongoTtlIndexExists();
|
||||||
|
|
||||||
|
// 2. MariaDB: Save Metadata
|
||||||
|
Email::updateOrCreate(
|
||||||
|
['unique_id_hash' => $hash],
|
||||||
|
[
|
||||||
|
'recipient_email' => $meta['recipientEmail'],
|
||||||
|
'sender_email' => $meta['senderEmail'],
|
||||||
|
'subject' => $meta['subject'] ?? '',
|
||||||
|
'is_read' => false,
|
||||||
|
// Parse the ISO 8601 UTC timestamp format explicitly for SQL
|
||||||
|
'received_at' => Carbon::parse($meta['received_at'])->setTimezone('UTC')->toDateTimeString(),
|
||||||
|
// Store attachments JSON. If empty, ensure it's saved as an empty array '[]'
|
||||||
|
'attachments' => !empty($meta['attachments']) ? json_encode($meta['attachments']) : '[]',
|
||||||
|
'attachment_size' => $meta['attachmentSize'] ?? 0
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. MongoDB: Save the heavy body with TTL
|
||||||
|
// Assuming you have the jenssegers/mongodb package installed
|
||||||
|
RecentEmailBody::updateOrCreate(
|
||||||
|
['unique_id_hash' => $hash],
|
||||||
|
[
|
||||||
|
// Handle cases where the sender only sends Text or only HTML
|
||||||
|
'body_text' => $payload['bodyText'] ?? '',
|
||||||
|
'body_html' => $payload['bodyHtml'] ?? '',
|
||||||
|
'created_at' => new \MongoDB\BSON\UTCDateTime(now()->timestamp * 1000), // BSON required for TTL
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json(['status' => 'success'], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures the TTL index is created on the MongoDB collection.
|
||||||
|
* Uses Laravel Cache to avoid checking the database on every single webhook.
|
||||||
|
*/
|
||||||
|
private function ensureMongoTtlIndexExists()
|
||||||
|
{
|
||||||
|
Cache::rememberForever('mongo_ttl_index_created', function () {
|
||||||
|
// Fetch TTL from Laravel .env (Default: 72 hours / 259200 seconds)
|
||||||
|
$ttlSeconds = (int) env('EMAIL_BODY_TTL_SECONDS', 259200);
|
||||||
|
|
||||||
|
$collection = DB::connection('mongodb')->getCollection('recent_email_bodies');
|
||||||
|
|
||||||
|
// Background creation prevents locking the database during webhook execution
|
||||||
|
$collection->createIndex(
|
||||||
|
['created_at' => 1],
|
||||||
|
[
|
||||||
|
'expireAfterSeconds' => $ttlSeconds,
|
||||||
|
'background' => true,
|
||||||
|
'name' => 'ttl_created_at_index' // Named index prevents duplicate recreation errors
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Resiliency Notes
|
||||||
|
|
||||||
|
* **Idempotency:** The MailOps worker might retry a webhook if a network timeout occurs even after Laravel successfully saved it. Your Laravel code MUST use `updateOrCreate` or `INSERT IGNORE` (like the example above) so it doesn't create duplicate emails if the same payload hash is received twice.
|
||||||
|
* **Timeouts:** The MailOps worker expects a response within 5 to 10 seconds. Do not perform long-running synchronous tasks (like connecting to external APIs or sending heavy push notifications) inside the webhook controller. Dispatch those to a Laravel Queue instead.
|
||||||
@@ -3,4 +3,4 @@
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Route::get('/user', fn(Request $request) => $request->user())->middleware('auth:sanctum');
|
Route::get('/user', fn (Request $request) => $request->user())->middleware('auth:sanctum');
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Livewire\Actions\Logout;
|
|
||||||
use App\Http\Controllers\Auth\VerifyEmailController;
|
use App\Http\Controllers\Auth\VerifyEmailController;
|
||||||
|
use App\Livewire\Actions\Logout;
|
||||||
use App\Livewire\Auth\ConfirmPassword;
|
use App\Livewire\Auth\ConfirmPassword;
|
||||||
use App\Livewire\Auth\ForgotPassword;
|
use App\Livewire\Auth\ForgotPassword;
|
||||||
use App\Livewire\Auth\Login;
|
use App\Livewire\Auth\Login;
|
||||||
|
|||||||
@@ -1,27 +1,22 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Models\Log;
|
|
||||||
use App\Models\Email;
|
use App\Models\Email;
|
||||||
|
use App\Models\Log;
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
use Illuminate\Foundation\Inspiring;
|
use Illuminate\Foundation\Inspiring;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Schedule;
|
||||||
|
|
||||||
Artisan::command('inspire', function (): void {
|
Artisan::command('inspire', function (): void {
|
||||||
$this->comment(Inspiring::quote());
|
$this->comment(Inspiring::quote());
|
||||||
})->purpose('Display an inspiring quote');
|
})->purpose('Display an inspiring quote');
|
||||||
|
|
||||||
// Schedule::call(function () {
|
// Schedule the new commands instead of closures
|
||||||
// Email::fetchProcessStoreEmail();
|
Schedule::command('emails:fetch')->everyMinute();
|
||||||
// })->everyMinute();
|
Schedule::command('attachments:clean')->daily();
|
||||||
|
Schedule::command('mailbox:clean')->everyTwoHours();
|
||||||
Schedule::call(function (): void {
|
|
||||||
Email::deleteBulkAttachments();
|
|
||||||
})->daily();
|
|
||||||
|
|
||||||
// Schedule::call(function () {
|
|
||||||
// Email::deleteBulkMailboxes();
|
|
||||||
// })->everyMinute();
|
|
||||||
|
|
||||||
|
// Keep other necessary schedules
|
||||||
Schedule::call(function (): void {
|
Schedule::call(function (): void {
|
||||||
Email::deleteMessagesFromDB();
|
Email::deleteMessagesFromDB();
|
||||||
})->everyTwoHours();
|
})->everyTwoHours();
|
||||||
@@ -30,10 +25,7 @@ Schedule::call(function (): void {
|
|||||||
Log::deleteLogsFromDB();
|
Log::deleteLogsFromDB();
|
||||||
})->everyThreeHours();
|
})->everyThreeHours();
|
||||||
|
|
||||||
Schedule::call(function (): void {
|
// Preserve existing commands
|
||||||
Email::cleanMailbox();
|
|
||||||
});
|
|
||||||
|
|
||||||
Artisan::command('cleanMail', function (): void {
|
Artisan::command('cleanMail', function (): void {
|
||||||
$this->comment(Email::cleanMailbox());
|
$this->comment(Email::cleanMailbox());
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\AppController;
|
use App\Http\Controllers\AppController;
|
||||||
|
|
||||||
use App\Http\Controllers\ImpersonationController;
|
use App\Http\Controllers\ImpersonationController;
|
||||||
use App\Http\Controllers\WebhookController;
|
use App\Http\Controllers\WebhookController;
|
||||||
use App\Http\Middleware\CheckPageSlug;
|
use App\Http\Middleware\CheckPageSlug;
|
||||||
@@ -22,11 +21,14 @@ use App\Livewire\Settings\Billing;
|
|||||||
use App\Livewire\Settings\Password;
|
use App\Livewire\Settings\Password;
|
||||||
use App\Livewire\Settings\Profile;
|
use App\Livewire\Settings\Profile;
|
||||||
use App\Models\Email;
|
use App\Models\Email;
|
||||||
use Illuminate\Support\Facades\Artisan;
|
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Illuminate\Support\Facades\Request;
|
use Illuminate\Support\Facades\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::get('/health', function () {
|
||||||
|
return response('OK', 200);
|
||||||
|
});
|
||||||
|
|
||||||
Route::get('/', Home::class)->name('home');
|
Route::get('/', Home::class)->name('home');
|
||||||
Route::get('/mailbox', Mailbox::class)->name('mailbox');
|
Route::get('/mailbox', Mailbox::class)->name('mailbox');
|
||||||
Route::get('/mailbox/{email?}', [AppController::class, 'mailbox'])->name('mailboxFromURL');
|
Route::get('/mailbox/{email?}', [AppController::class, 'mailbox'])->name('mailboxFromURL');
|
||||||
@@ -71,76 +73,12 @@ Route::middleware(['auth', 'verified', CheckUserBanned::class])->group(function
|
|||||||
Route::get('dashboard/compose-email', Dashboard::class)->name('dashboard.compose');
|
Route::get('dashboard/compose-email', Dashboard::class)->name('dashboard.compose');
|
||||||
Route::get('dashboard/support', Support::class)->name('dashboard.support');
|
Route::get('dashboard/support', Support::class)->name('dashboard.support');
|
||||||
|
|
||||||
// LEGACY: Old Stripe Cashier checkout route (deprecated - use unified payment system)
|
|
||||||
Route::get('checkout/{plan}', function ($pricing_id) {
|
|
||||||
$plans = config('app.plans');
|
|
||||||
$pricingData = [];
|
|
||||||
foreach ($plans as $plan) {
|
|
||||||
$pricingData[] = $plan['pricing_id'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (in_array($pricing_id, $pricingData)) {
|
|
||||||
return auth()->user()
|
|
||||||
->newSubscription('default', $pricing_id)
|
|
||||||
->allowPromotionCodes()
|
|
||||||
->checkout([
|
|
||||||
'billing_address_collection' => 'required',
|
|
||||||
'success_url' => route('checkout.success'),
|
|
||||||
'cancel_url' => route('checkout.cancel'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
}
|
|
||||||
abort(404);
|
|
||||||
})->name('checkout');
|
|
||||||
|
|
||||||
// LEGACY: Payment status routes (used by both legacy and unified systems)
|
// LEGACY: Payment status routes (used by both legacy and unified systems)
|
||||||
Route::get('dashboard/success', [Dashboard::class, 'paymentStatus'])->name('checkout.success')->defaults('status', 'success');
|
Route::get('dashboard/success', [Dashboard::class, 'paymentStatus'])->name('checkout.success')->defaults('status', 'success');
|
||||||
Route::get('dashboard/cancel', [Dashboard::class, 'paymentStatus'])->name('checkout.cancel')->defaults('status', 'cancel');
|
Route::get('dashboard/cancel', [Dashboard::class, 'paymentStatus'])->name('checkout.cancel')->defaults('status', 'cancel');
|
||||||
|
|
||||||
Route::get('dashboard/billing', fn () => auth()->user()->redirectToBillingPortal(route('dashboard')))->name('billing');
|
Route::get('dashboard/billing', fn () => auth()->user()->redirectToBillingPortal(route('dashboard')))->name('billing');
|
||||||
|
|
||||||
Route::get('0xdash/slink', function (Request $request) {
|
|
||||||
$validUser = 'admin';
|
|
||||||
$validPass = 'admin@9608'; // 🔐 Change this to something secure
|
|
||||||
|
|
||||||
if (! isset($_SERVER['PHP_AUTH_USER']) ||
|
|
||||||
Request::server('PHP_AUTH_USER') !== $validUser ||
|
|
||||||
Request::server('PHP_AUTH_PW') !== $validPass) {
|
|
||||||
|
|
||||||
header('WWW-Authenticate: Basic realm="Restricted Area"');
|
|
||||||
header('HTTP/1.0 401 Unauthorized');
|
|
||||||
echo 'Unauthorized';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
Artisan::call('storage:link');
|
|
||||||
$output = Artisan::output();
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => trim($output),
|
|
||||||
]);
|
|
||||||
})->name('storageLink');
|
|
||||||
|
|
||||||
Route::get('0xdash/scache', function (Request $request) {
|
|
||||||
$validUser = 'admin';
|
|
||||||
$validPass = 'admin@9608'; // 🔐 Change this to something secure
|
|
||||||
|
|
||||||
if (! isset($_SERVER['PHP_AUTH_USER']) ||
|
|
||||||
Request::server('PHP_AUTH_USER') !== $validUser ||
|
|
||||||
Request::server('PHP_AUTH_PW') !== $validPass) {
|
|
||||||
|
|
||||||
header('WWW-Authenticate: Basic realm="Restricted Area"');
|
|
||||||
header('HTTP/1.0 401 Unauthorized');
|
|
||||||
echo 'Unauthorized';
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
Artisan::call('cache:clear');
|
|
||||||
$output = Artisan::output();
|
|
||||||
|
|
||||||
return response()->json([
|
|
||||||
'message' => trim($output),
|
|
||||||
]);
|
|
||||||
})->name('cacheClear');
|
|
||||||
|
|
||||||
// Impersonation Routes
|
// Impersonation Routes
|
||||||
Route::prefix('impersonation')->name('impersonation.')->group(function (): void {
|
Route::prefix('impersonation')->name('impersonation.')->group(function (): void {
|
||||||
Route::post('/stop', [ImpersonationController::class, 'stop'])->name('stop');
|
Route::post('/stop', [ImpersonationController::class, 'stop'])->name('stop');
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
namespace Tests\Concerns;
|
namespace Tests\Concerns;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use Illuminate\Support\Collection;
|
|
||||||
use App\Models\Blog;
|
use App\Models\Blog;
|
||||||
use App\Models\Menu;
|
use App\Models\Menu;
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
|
use Exception;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
trait LoadsApplicationData
|
trait LoadsApplicationData
|
||||||
{
|
{
|
||||||
@@ -64,7 +64,7 @@ trait LoadsApplicationData
|
|||||||
try {
|
try {
|
||||||
$menus = cache()->remember('app_menus', now()->addHours(6), Menu::all(...));
|
$menus = cache()->remember('app_menus', now()->addHours(6), Menu::all(...));
|
||||||
|
|
||||||
$blogs = cache()->remember('app_blogs', now()->addHours(6), fn() => Blog::query()->where('is_published', 1)->get());
|
$blogs = cache()->remember('app_blogs', now()->addHours(6), fn () => Blog::query()->where('is_published', 1)->get());
|
||||||
|
|
||||||
$plans = cache()->remember('app_plans', now()->addHours(6), Plan::all(...));
|
$plans = cache()->remember('app_plans', now()->addHours(6), Plan::all(...));
|
||||||
} catch (Exception) {
|
} catch (Exception) {
|
||||||
|
|||||||
@@ -2,30 +2,30 @@
|
|||||||
|
|
||||||
namespace Tests\Feature\Filament;
|
namespace Tests\Feature\Filament;
|
||||||
|
|
||||||
use App\Filament\Resources\TicketResource\Pages\ListTickets;
|
use App\Filament\Resources\BlogResource;
|
||||||
use App\Filament\Resources\TicketResource\Pages\EditTicket;
|
use App\Filament\Resources\BlogResource\Pages\CreateBlog;
|
||||||
use App\Filament\Resources\TicketResource\RelationManagers\ResponsesRelationManager;
|
|
||||||
use App\Filament\Resources\TicketResource;
|
|
||||||
use App\Filament\Resources\PlanResource\Pages\ListPlans;
|
|
||||||
use App\Filament\Resources\PlanResource\Pages\EditPlan;
|
|
||||||
use App\Filament\Resources\BlogResource\Pages\ListBlogs;
|
|
||||||
use App\Filament\Resources\BlogResource\Pages\EditBlog;
|
use App\Filament\Resources\BlogResource\Pages\EditBlog;
|
||||||
use App\Filament\Resources\CategoryResource\Pages\ListCategories;
|
use App\Filament\Resources\BlogResource\Pages\ListBlogs;
|
||||||
use App\Filament\Resources\CategoryResource\Pages\CreateCategory;
|
use App\Filament\Resources\CategoryResource\Pages\CreateCategory;
|
||||||
use App\Filament\Resources\CategoryResource\Pages\EditCategory;
|
use App\Filament\Resources\CategoryResource\Pages\EditCategory;
|
||||||
use App\Filament\Resources\PageResource\Pages\ListPages;
|
use App\Filament\Resources\CategoryResource\Pages\ListCategories;
|
||||||
|
use App\Filament\Resources\MenuResource\Pages\CreateMenu;
|
||||||
|
use App\Filament\Resources\MenuResource\Pages\ListMenus;
|
||||||
use App\Filament\Resources\PageResource\Pages\CreatePage;
|
use App\Filament\Resources\PageResource\Pages\CreatePage;
|
||||||
use App\Filament\Resources\PageResource\Pages\EditPage;
|
use App\Filament\Resources\PageResource\Pages\EditPage;
|
||||||
use App\Filament\Resources\MenuResource\Pages\ListMenus;
|
use App\Filament\Resources\PageResource\Pages\ListPages;
|
||||||
use App\Filament\Resources\MenuResource\Pages\CreateMenu;
|
|
||||||
use App\Filament\Resources\UserResource\Pages\ListUsers;
|
|
||||||
use App\Filament\Resources\UserResource;
|
|
||||||
use App\Filament\Resources\PlanResource;
|
use App\Filament\Resources\PlanResource;
|
||||||
use App\Filament\Resources\BlogResource;
|
|
||||||
use App\Filament\Resources\UserResource\Pages\CreateUser;
|
|
||||||
use App\Filament\Resources\BlogResource\Pages\CreateBlog;
|
|
||||||
use App\Filament\Resources\PlanResource\Pages\CreatePlan;
|
use App\Filament\Resources\PlanResource\Pages\CreatePlan;
|
||||||
|
use App\Filament\Resources\PlanResource\Pages\EditPlan;
|
||||||
|
use App\Filament\Resources\PlanResource\Pages\ListPlans;
|
||||||
|
use App\Filament\Resources\TicketResource;
|
||||||
use App\Filament\Resources\TicketResource\Pages\CreateTicket;
|
use App\Filament\Resources\TicketResource\Pages\CreateTicket;
|
||||||
|
use App\Filament\Resources\TicketResource\Pages\EditTicket;
|
||||||
|
use App\Filament\Resources\TicketResource\Pages\ListTickets;
|
||||||
|
use App\Filament\Resources\TicketResource\RelationManagers\ResponsesRelationManager;
|
||||||
|
use App\Filament\Resources\UserResource;
|
||||||
|
use App\Filament\Resources\UserResource\Pages\CreateUser;
|
||||||
|
use App\Filament\Resources\UserResource\Pages\ListUsers;
|
||||||
use App\Models\Blog;
|
use App\Models\Blog;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Models\Menu;
|
use App\Models\Menu;
|
||||||
@@ -40,6 +40,7 @@ use Tests\TestCase;
|
|||||||
class ResourcesTest extends TestCase
|
class ResourcesTest extends TestCase
|
||||||
{
|
{
|
||||||
public $adminUser;
|
public $adminUser;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
|
|
||||||
namespace Tests\Feature\Filament;
|
namespace Tests\Feature\Filament;
|
||||||
|
|
||||||
use App\Filament\Resources\UserResource\RelationManagers\LogsRelationManager;
|
|
||||||
use App\Filament\Resources\UserResource\RelationManagers\UsageLogsRelationManager;
|
|
||||||
use App\Filament\Resources\UserResource;
|
use App\Filament\Resources\UserResource;
|
||||||
use App\Filament\Resources\UserResource\Pages\CreateUser;
|
use App\Filament\Resources\UserResource\Pages\CreateUser;
|
||||||
use App\Filament\Resources\UserResource\Pages\EditUser;
|
use App\Filament\Resources\UserResource\Pages\EditUser;
|
||||||
use App\Filament\Resources\UserResource\Pages\ListUsers;
|
use App\Filament\Resources\UserResource\Pages\ListUsers;
|
||||||
|
use App\Filament\Resources\UserResource\RelationManagers\LogsRelationManager;
|
||||||
|
use App\Filament\Resources\UserResource\RelationManagers\UsageLogsRelationManager;
|
||||||
use App\Models\Log;
|
use App\Models\Log;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
@@ -16,6 +16,7 @@ use Tests\TestCase;
|
|||||||
class UserResourceTest extends TestCase
|
class UserResourceTest extends TestCase
|
||||||
{
|
{
|
||||||
public $adminUser;
|
public $adminUser;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use Tests\TestCase;
|
|||||||
class LoginTest extends TestCase
|
class LoginTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use Tests\TestCase;
|
|||||||
class DashboardTest extends TestCase
|
class DashboardTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
namespace Tests\Feature\Livewire;
|
namespace Tests\Feature\Livewire;
|
||||||
|
|
||||||
use App\Livewire\Home;
|
|
||||||
use App\Livewire\Frontend\Mailbox;
|
use App\Livewire\Frontend\Mailbox;
|
||||||
use Exception;
|
use App\Livewire\Home;
|
||||||
use App\Models\Blog;
|
|
||||||
use App\Livewire\ListBlog;
|
use App\Livewire\ListBlog;
|
||||||
|
use App\Models\Blog;
|
||||||
use App\Models\Page;
|
use App\Models\Page;
|
||||||
use App\Models\ZEmail;
|
use App\Models\ZEmail;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Cookie;
|
use Illuminate\Support\Facades\Cookie;
|
||||||
use Livewire\Livewire;
|
use Livewire\Livewire;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Tests\TestCase;
|
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -30,7 +30,7 @@ pest()->extend(TestCase::class)
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
expect()->extend('toBeOne', fn() => $this->toBe(1));
|
expect()->extend('toBeOne', fn () => $this->toBe(1));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Tests\TestCase;
|
|||||||
class ActivationKeyTest extends TestCase
|
class ActivationKeyTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Models;
|
namespace Tests\Unit\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use App\Models\Blog;
|
use App\Models\Blog;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class BlogTest extends TestCase
|
class BlogTest extends TestCase
|
||||||
{
|
{
|
||||||
private User|Collection $user;
|
private User|Collection $user;
|
||||||
|
|
||||||
public $category;
|
public $category;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Models;
|
namespace Tests\Unit\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use App\Models\Blog;
|
use App\Models\Blog;
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class CategoryTest extends TestCase
|
class CategoryTest extends TestCase
|
||||||
|
|||||||
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Models;
|
namespace Tests\Unit\Models;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Date;
|
|
||||||
use App\Models\Email;
|
use App\Models\Email;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
|
use Illuminate\Support\Facades\Date;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class EmailTest extends TestCase
|
class EmailTest extends TestCase
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Tests\TestCase;
|
|||||||
class LogTest extends TestCase
|
class LogTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Models;
|
namespace Tests\Unit\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use App\Models\Plan;
|
use App\Models\Plan;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class PlanTest extends TestCase
|
class PlanTest extends TestCase
|
||||||
{
|
{
|
||||||
public $planData;
|
public $planData;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use Tests\TestCase;
|
|||||||
class PremiumEmailTest extends TestCase
|
class PremiumEmailTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Models;
|
namespace Tests\Unit\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
use App\Models\TicketResponse;
|
use App\Models\TicketResponse;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class TicketResponseTest extends TestCase
|
class TicketResponseTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
public $ticket;
|
public $ticket;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Models;
|
namespace Tests\Unit\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
use App\Models\TicketResponse;
|
use App\Models\TicketResponse;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class TicketTest extends TestCase
|
class TicketTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
public $ticketData;
|
public $ticketData;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use Tests\TestCase;
|
|||||||
class UsageLogTest extends TestCase
|
class UsageLogTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,25 +2,26 @@
|
|||||||
|
|
||||||
namespace Tests\Unit\Models;
|
namespace Tests\Unit\Models;
|
||||||
|
|
||||||
use Carbon\Carbon;
|
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Notifications\Notifiable;
|
|
||||||
use Laravel\Cashier\Billable;
|
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
|
||||||
use Filament\Models\Contracts\FilamentUser;
|
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
|
||||||
use Laravel\Sanctum\NewAccessToken;
|
|
||||||
use App\Models\Log;
|
use App\Models\Log;
|
||||||
use App\Models\Ticket;
|
use App\Models\Ticket;
|
||||||
use App\Models\UsageLog;
|
use App\Models\UsageLog;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Filament\Models\Contracts\FilamentUser;
|
||||||
use Filament\Panel;
|
use Filament\Panel;
|
||||||
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Laravel\Cashier\Billable;
|
||||||
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
|
use Laravel\Sanctum\NewAccessToken;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
class UserTest extends TestCase
|
class UserTest extends TestCase
|
||||||
{
|
{
|
||||||
public $user;
|
public $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
namespace Tests\Unit;
|
namespace Tests\Unit;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
use App\Http\Controllers\WebhookController;
|
use App\Http\Controllers\WebhookController;
|
||||||
use App\NotifyMe;
|
use App\NotifyMe;
|
||||||
|
use Exception;
|
||||||
use Illuminate\Support\Facades\Config;
|
use Illuminate\Support\Facades\Config;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -19,6 +19,7 @@ class TestNotifier
|
|||||||
class NotifyMeTest extends TestCase
|
class NotifyMeTest extends TestCase
|
||||||
{
|
{
|
||||||
public $notifier;
|
public $notifier;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
@@ -42,7 +43,7 @@ class NotifyMeTest extends TestCase
|
|||||||
$result = $this->notifier->sendTelegramNotification('Test message');
|
$result = $this->notifier->sendTelegramNotification('Test message');
|
||||||
|
|
||||||
$this->assertTrue($result);
|
$this->assertTrue($result);
|
||||||
Http::assertSent(fn(array $request): bool => $request->url() === 'https://api.telegram.org/bottest_bot_token/sendMessage' &&
|
Http::assertSent(fn (array $request): bool => $request->url() === 'https://api.telegram.org/bottest_bot_token/sendMessage' &&
|
||||||
$request['chat_id'] === 'test_chat_id' &&
|
$request['chat_id'] === 'test_chat_id' &&
|
||||||
$request['text'] === 'Test message' &&
|
$request['text'] === 'Test message' &&
|
||||||
$request['parse_mode'] === 'HTML');
|
$request['parse_mode'] === 'HTML');
|
||||||
@@ -133,7 +134,7 @@ class NotifyMeTest extends TestCase
|
|||||||
$htmlMessage = '<b>Bold text</b> and <i>italic text</i>';
|
$htmlMessage = '<b>Bold text</b> and <i>italic text</i>';
|
||||||
$this->notifier->sendTelegramNotification($htmlMessage);
|
$this->notifier->sendTelegramNotification($htmlMessage);
|
||||||
|
|
||||||
Http::assertSent(fn(array $request): bool => $request['parse_mode'] === 'HTML' &&
|
Http::assertSent(fn (array $request): bool => $request['parse_mode'] === 'HTML' &&
|
||||||
$request['text'] === $htmlMessage);
|
$request['text'] === $htmlMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user