Configurations cleanup & database configurations for mail and pdfs (#479)

* Move Mail, PDF configuration to Database, standardize configurations

* Set default currency to USD on install

* Pint code
This commit is contained in:
Darko Gjorgjijoski
2025-09-19 15:42:53 +02:00
committed by GitHub
parent 3da86965e1
commit 18d63a3375
24 changed files with 943 additions and 435 deletions

View File

@@ -1,19 +1,11 @@
APP_ENV=production
APP_DEBUG=false
APP_KEY=base64:kgk/4DW1vEVy7aEvet5FPp5un6PIGe/so8H0mvoUtW0=
APP_DEBUG=true
APP_NAME="InvoiceShelf"
APP_LOG_LEVEL=debug
APP_TIMEZONE=UTC
APP_URL=
APP_LOCALE=en
APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file
APP_MAINTENANCE_STORE=database
BCRYPT_ROUNDS=12
DB_CONNECTION=sqlite
DB_HOST=
@@ -22,38 +14,6 @@ DB_DATABASE=
DB_USERNAME=
DB_PASSWORD=
BROADCAST_CONNECTION=log
CACHE_STORE=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=1440
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=
MAIL_PORT=
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=
MAIL_FROM_NAME=
MAIL_FROM_ADDRESS=
PUSHER_APP_ID=
PUSHER_KEY=
PUSHER_SECRET=
SANCTUM_STATEFUL_DOMAIN=
TRUSTED_PROXIES="*"
CRON_JOB_AUTH_TOKEN=""
LOG_STACK=single
PDF_DRIVER=dompdf
GOTENBERG_HOST=
GOTENBERG_PAPERSIZE=

View File

@@ -41,13 +41,79 @@ class MailConfigurationController extends Controller
$this->authorize('manage email config');
$setting = Setting::getSetting('profile_complete');
$results = $this->environmentManager->saveMailVariables($request);
// Prepare mail settings for database storage
$mailSettings = $this->prepareMailSettingsForDatabase($request);
// Save mail settings to database
Setting::setSettings($mailSettings);
if ($setting !== 'COMPLETED') {
Setting::setSetting('profile_complete', 4);
}
return response()->json($results);
return response()->json([
'success' => 'mail_variables_save_successfully',
]);
}
/**
* Prepare mail settings for database storage
*
* @return array
*/
private function prepareMailSettingsForDatabase(MailEnvironmentRequest $request)
{
$driver = $request->get('mail_driver');
// Base settings that are always saved
$settings = [
'mail_driver' => $driver,
'from_name' => $request->get('from_name'),
'from_mail' => $request->get('from_mail'),
];
// Driver-specific settings
switch ($driver) {
case 'smtp':
$settings = array_merge($settings, [
'mail_host' => $request->get('mail_host'),
'mail_port' => $request->get('mail_port'),
'mail_username' => $request->get('mail_username'),
'mail_password' => $request->get('mail_password'),
'mail_encryption' => $request->get('mail_encryption', 'none'),
'mail_scheme' => $request->get('mail_scheme'),
'mail_url' => $request->get('mail_url'),
'mail_timeout' => $request->get('mail_timeout'),
'mail_local_domain' => $request->get('mail_local_domain'),
]);
break;
case 'mailgun':
$settings = array_merge($settings, [
'mail_mailgun_domain' => $request->get('mail_mailgun_domain'),
'mail_mailgun_secret' => $request->get('mail_mailgun_secret'),
'mail_mailgun_endpoint' => $request->get('mail_mailgun_endpoint', 'api.mailgun.net'),
'mail_mailgun_scheme' => $request->get('mail_mailgun_scheme', 'https'),
]);
break;
case 'ses':
$settings = array_merge($settings, [
'mail_ses_key' => $request->get('mail_ses_key'),
'mail_ses_secret' => $request->get('mail_ses_secret'),
'mail_ses_region' => $request->get('mail_ses_region', 'us-east-1'),
]);
break;
case 'sendmail':
$settings = array_merge($settings, [
'mail_sendmail_path' => $request->get('mail_sendmail_path', '/usr/sbin/sendmail -bs -i'),
]);
break;
}
return $settings;
}
/**
@@ -61,51 +127,75 @@ class MailConfigurationController extends Controller
{
$this->authorize('manage email config');
$driver = config('mail.default');
// Get mail settings from database
$mailSettings = Setting::getSettings([
'mail_driver',
'mail_host',
'mail_port',
'mail_username',
'mail_password',
'mail_encryption',
'mail_scheme',
'mail_url',
'mail_timeout',
'mail_local_domain',
'from_name',
'from_mail',
'mail_mailgun_domain',
'mail_mailgun_secret',
'mail_mailgun_endpoint',
'mail_mailgun_scheme',
'mail_ses_key',
'mail_ses_secret',
'mail_ses_region',
'mail_sendmail_path',
]);
$driver = $mailSettings['mail_driver'] ?? config('mail.default');
// Base data that's always available
$MailData = [
'mail_driver' => $driver,
'from_name' => config('mail.from.name'),
'from_mail' => config('mail.from.address'),
'from_name' => $mailSettings['from_name'] ?? config('mail.from.name'),
'from_mail' => $mailSettings['from_mail'] ?? config('mail.from.address'),
];
// Driver-specific configuration
switch ($driver) {
case 'smtp':
$MailData = array_merge($MailData, [
'mail_host' => config('mail.mailers.smtp.host'),
'mail_port' => config('mail.mailers.smtp.port'),
'mail_username' => config('mail.mailers.smtp.username'),
'mail_password' => config('mail.mailers.smtp.password'),
'mail_encryption' => config('mail.mailers.smtp.scheme') ?? 'none',
'mail_scheme' => config('mail.mailers.smtp.scheme'),
'mail_url' => config('mail.mailers.smtp.url'),
'mail_timeout' => config('mail.mailers.smtp.timeout'),
'mail_local_domain' => config('mail.mailers.smtp.local_domain'),
'mail_host' => $mailSettings['mail_host'] ?? '',
'mail_port' => $mailSettings['mail_port'] ?? '',
'mail_username' => $mailSettings['mail_username'] ?? '',
'mail_password' => $mailSettings['mail_password'] ?? '',
'mail_encryption' => $mailSettings['mail_encryption'] ?? 'none',
'mail_scheme' => $mailSettings['mail_scheme'] ?? '',
'mail_url' => $mailSettings['mail_url'] ?? '',
'mail_timeout' => $mailSettings['mail_timeout'] ?? '',
'mail_local_domain' => $mailSettings['mail_local_domain'] ?? '',
]);
break;
case 'mailgun':
$MailData = array_merge($MailData, [
'mail_mailgun_domain' => config('mail.mailers.mailgun.domain'),
'mail_mailgun_secret' => config('mail.mailers.mailgun.secret'),
'mail_mailgun_endpoint' => config('mail.mailers.mailgun.endpoint'),
'mail_mailgun_scheme' => config('mail.mailers.mailgun.scheme'),
'mail_mailgun_domain' => $mailSettings['mail_mailgun_domain'] ?? '',
'mail_mailgun_secret' => $mailSettings['mail_mailgun_secret'] ?? '',
'mail_mailgun_endpoint' => $mailSettings['mail_mailgun_endpoint'] ?? 'api.mailgun.net',
'mail_mailgun_scheme' => $mailSettings['mail_mailgun_scheme'] ?? 'https',
]);
break;
case 'ses':
$MailData = array_merge($MailData, [
'mail_ses_key' => config('services.ses.key'),
'mail_ses_secret' => config('services.ses.secret'),
'mail_ses_region' => config('services.ses.region'),
'mail_ses_key' => $mailSettings['mail_ses_key'] ?? '',
'mail_ses_secret' => $mailSettings['mail_ses_secret'] ?? '',
'mail_ses_region' => $mailSettings['mail_ses_region'] ?? 'us-east-1',
]);
break;
case 'sendmail':
$MailData = array_merge($MailData, [
'mail_sendmail_path' => config('mail.mailers.sendmail.path'),
'mail_sendmail_path' => $mailSettings['mail_sendmail_path'] ?? '/usr/sbin/sendmail -bs -i',
]);
break;

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\V1\Admin\Settings;
use App\Http\Controllers\Controller;
use App\Http\Requests\PDFConfigurationRequest;
use App\Models\Setting;
use App\Space\EnvironmentManager;
class PDFConfigurationController extends Controller
@@ -34,11 +35,19 @@ class PDFConfigurationController extends Controller
{
$this->authorize('manage pdf config');
// Get PDF settings from database
$pdfSettings = Setting::getSettings([
'pdf_driver',
'gotenberg_host',
'gotenberg_papersize',
'gotenberg_margins',
]);
$config = [
'pdf_driver' => config('pdf.driver'),
'gotenberg_host' => config('pdf.gotenberg.host'),
'gotenberg_margins' => config('pdf.gotenberg.margins'),
'gotenberg_papersize' => config('pdf.gotenberg.papersize'),
'pdf_driver' => $pdfSettings['pdf_driver'] ?? config('pdf.driver'),
'gotenberg_host' => $pdfSettings['gotenberg_host'] ?? config('pdf.connections.gotenberg.host'),
'gotenberg_margins' => $pdfSettings['gotenberg_margins'] ?? config('pdf.connections.gotenberg.margins'),
'gotenberg_papersize' => $pdfSettings['gotenberg_papersize'] ?? config('pdf.connections.gotenberg.papersize'),
];
return response()->json($config);
@@ -47,8 +56,47 @@ class PDFConfigurationController extends Controller
public function saveEnvironment(PDFConfigurationRequest $request)
{
$this->authorize('manage pdf config');
$results = $this->environmentManager->savePDFVariables($request);
return response()->json($results);
// Prepare PDF settings for database storage
$pdfSettings = $this->preparePDFSettingsForDatabase($request);
// Save PDF settings to database
Setting::setSettings($pdfSettings);
return response()->json([
'success' => 'pdf_variables_save_successfully',
]);
}
/**
* Prepare PDF settings for database storage
*
* @return array
*/
private function preparePDFSettingsForDatabase(PDFConfigurationRequest $request)
{
$driver = $request->get('pdf_driver');
// Base settings that are always saved
$settings = [
'pdf_driver' => $driver,
];
// Driver-specific settings
switch ($driver) {
case 'gotenberg':
$settings = array_merge($settings, [
'gotenberg_host' => $request->get('gotenberg_host'),
'gotenberg_papersize' => $request->get('gotenberg_papersize'),
'gotenberg_margins' => $request->get('gotenberg_margins'),
]);
break;
case 'dompdf':
// dompdf doesn't have additional configuration in the current setup
break;
}
return $settings;
}
}

View File

@@ -18,15 +18,15 @@ class ConfigMiddleware
public function handle(Request $request, Closure $next): Response
{
if (InstallUtils::isDbCreated()) {
// Only handle dynamic file disk switching when file_disk_id is provided
if ($request->has('file_disk_id')) {
$file_disk = FileDisk::find($request->file_disk_id);
} else {
$file_disk = FileDisk::whereSetAsDefault(true)->first();
}
if ($file_disk) {
$file_disk->setConfig();
if ($file_disk) {
$file_disk->setConfig();
}
}
// Default file disk is now handled by AppConfigProvider during boot
}
return $next($request);

View File

@@ -33,10 +33,34 @@ class MailEnvironmentRequest extends FormRequest
'mail_port' => [
'required',
],
'mail_username' => [
'nullable',
'string',
],
'mail_password' => [
'nullable',
'string',
],
'mail_encryption' => [
'nullable',
'string',
],
'mail_scheme' => [
'nullable',
'string',
],
'mail_url' => [
'nullable',
'string',
],
'mail_timeout' => [
'nullable',
'integer',
],
'mail_local_domain' => [
'nullable',
'string',
],
'from_name' => [
'required',
'string',
@@ -44,11 +68,10 @@ class MailEnvironmentRequest extends FormRequest
'from_mail' => [
'required',
'string',
'email',
],
];
break;
case 'mailgun':
return [
'mail_driver' => [
@@ -64,43 +87,10 @@ class MailEnvironmentRequest extends FormRequest
'string',
],
'mail_mailgun_endpoint' => [
'required',
'nullable',
'string',
],
'from_name' => [
'required',
'string',
],
'from_mail' => [
'required',
'string',
],
];
break;
case 'ses':
return [
'mail_driver' => [
'required',
'string',
],
'mail_host' => [
'required',
'string',
],
'mail_port' => [
'required',
],
'mail_ses_key' => [
'required',
'string',
],
'mail_ses_secret' => [
'required',
'string',
],
'mail_encryption' => [
'mail_mailgun_scheme' => [
'nullable',
'string',
],
@@ -111,13 +101,28 @@ class MailEnvironmentRequest extends FormRequest
'from_mail' => [
'required',
'string',
'email',
],
];
break;
case 'mail':
case 'ses':
return [
'mail_driver' => [
'required',
'string',
],
'mail_ses_key' => [
'required',
'string',
],
'mail_ses_secret' => [
'required',
'string',
],
'mail_ses_region' => [
'nullable',
'string',
],
'from_name' => [
'required',
'string',
@@ -125,13 +130,20 @@ class MailEnvironmentRequest extends FormRequest
'from_mail' => [
'required',
'string',
'email',
],
];
break;
case 'sendmail':
return [
'mail_driver' => [
'required',
'string',
],
'mail_sendmail_path' => [
'nullable',
'string',
],
'from_name' => [
'required',
'string',
@@ -139,10 +151,26 @@ class MailEnvironmentRequest extends FormRequest
'from_mail' => [
'required',
'string',
'email',
],
];
break;
default:
return [
'mail_driver' => [
'required',
'string',
],
'from_name' => [
'required',
'string',
],
'from_mail' => [
'required',
'string',
'email',
],
];
}
}
}

View File

@@ -27,7 +27,6 @@ class PDFConfigurationRequest extends FormRequest
'string',
],
];
break;
case 'gotenberg':
return [
@@ -40,18 +39,28 @@ class PDFConfigurationRequest extends FormRequest
'url',
],
'gotenberg_papersize' => [
'required',
'string',
function ($attribute, $value, $fail) {
($attribute); // unused
$reg = "/^\d+(pt|px|pc|mm|cm|in) \d+(pt|px|pc|mm|cm|in)$/";
if (! preg_match($reg, $value)) {
$fail('Invalid papersize, must be in format "210mm 297mm". Accepts: pt,px,pc,mm,cm,in');
}
},
],
'gotenberg_margins' => [
'nullable',
'string',
],
];
break;
default:
return [
'pdf_driver' => [
'required',
'string',
],
];
}
throw new \InvalidArgumentException('Invalid PDFDriver requested');
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Models;
use App\Carbon;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

View File

@@ -0,0 +1,176 @@
<?php
namespace App\Providers;
use App\Models\Setting;
use App\Space\InstallUtils;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\ServiceProvider;
class AppConfigProvider extends ServiceProvider
{
/**
* Bootstrap services.
*/
public function boot(): void
{
// Check if database is available
if (! InstallUtils::isDbCreated()) {
return;
}
$this->configureMailFromDatabase();
$this->configurePDFFromDatabase();
$this->configureFileSystemFromDatabase();
}
/**
* Configure mail settings from database
*/
protected function configureMailFromDatabase(): void
{
try {
// Get mail settings from database
$mailSettings = Setting::getSettings([
'mail_driver',
'mail_host',
'mail_port',
'mail_username',
'mail_password',
'mail_encryption',
'mail_scheme',
'mail_url',
'mail_timeout',
'mail_local_domain',
'from_name',
'from_mail',
'mail_mailgun_domain',
'mail_mailgun_secret',
'mail_mailgun_endpoint',
'mail_mailgun_scheme',
'mail_ses_key',
'mail_ses_secret',
'mail_ses_region',
'mail_sendmail_path',
]);
if (! empty($mailSettings['mail_driver'])) {
$driver = $mailSettings['mail_driver'];
// Set default mailer
Config::set('mail.default', $driver);
// Configure based on driver
switch ($driver) {
case 'smtp':
Config::set('mail.mailers.smtp.host', $mailSettings['mail_host'] ?? '127.0.0.1');
Config::set('mail.mailers.smtp.port', $mailSettings['mail_port'] ?? 2525);
Config::set('mail.mailers.smtp.username', $mailSettings['mail_username'] ?? null);
Config::set('mail.mailers.smtp.password', $mailSettings['mail_password'] ?? null);
Config::set('mail.mailers.smtp.encryption', $mailSettings['mail_encryption'] ?? 'none');
Config::set('mail.mailers.smtp.scheme', $mailSettings['mail_scheme'] ?? null);
Config::set('mail.mailers.smtp.url', $mailSettings['mail_url'] ?? null);
Config::set('mail.mailers.smtp.timeout', $mailSettings['mail_timeout'] ?? null);
Config::set('mail.mailers.smtp.local_domain', $mailSettings['mail_local_domain'] ?? null);
break;
case 'mailgun':
Config::set('mail.mailers.mailgun.domain', $mailSettings['mail_mailgun_domain'] ?? null);
Config::set('mail.mailers.mailgun.secret', $mailSettings['mail_mailgun_secret'] ?? null);
Config::set('mail.mailers.mailgun.endpoint', $mailSettings['mail_mailgun_endpoint'] ?? 'api.mailgun.net');
Config::set('mail.mailers.mailgun.scheme', $mailSettings['mail_mailgun_scheme'] ?? 'https');
// Also set services config for mailgun
Config::set('services.mailgun.domain', $mailSettings['mail_mailgun_domain'] ?? null);
Config::set('services.mailgun.secret', $mailSettings['mail_mailgun_secret'] ?? null);
Config::set('services.mailgun.endpoint', $mailSettings['mail_mailgun_endpoint'] ?? 'api.mailgun.net');
break;
case 'ses':
Config::set('services.ses.key', $mailSettings['mail_ses_key'] ?? null);
Config::set('services.ses.secret', $mailSettings['mail_ses_secret'] ?? null);
Config::set('services.ses.region', $mailSettings['mail_ses_region'] ?? 'us-east-1');
break;
case 'sendmail':
Config::set('mail.mailers.sendmail.path', $mailSettings['mail_sendmail_path'] ?? '/usr/sbin/sendmail -bs -i');
break;
}
// Set global from address and name
if (! empty($mailSettings['from_mail'])) {
Config::set('mail.from.address', $mailSettings['from_mail']);
}
if (! empty($mailSettings['from_name'])) {
Config::set('mail.from.name', $mailSettings['from_name']);
}
}
} catch (\Exception $e) {
// Silently fail if database is not available (during installation, migrations, etc.)
// This prevents the application from breaking during setup
}
}
/**
* Configure PDF settings from database
*/
protected function configurePDFFromDatabase(): void
{
try {
// Get PDF settings from database
$pdfSettings = Setting::getSettings([
'pdf_driver',
'gotenberg_host',
'gotenberg_papersize',
'gotenberg_margins',
]);
if (! empty($pdfSettings['pdf_driver'])) {
$driver = $pdfSettings['pdf_driver'];
// Set PDF driver
Config::set('pdf.driver', $driver);
// Configure based on driver
switch ($driver) {
case 'gotenberg':
if (! empty($pdfSettings['gotenberg_host'])) {
Config::set('pdf.connections.gotenberg.host', $pdfSettings['gotenberg_host']);
}
if (! empty($pdfSettings['gotenberg_papersize'])) {
Config::set('pdf.connections.gotenberg.papersize', $pdfSettings['gotenberg_papersize']);
}
if (! empty($pdfSettings['gotenberg_margins'])) {
Config::set('pdf.connections.gotenberg.margins', $pdfSettings['gotenberg_margins']);
}
break;
case 'dompdf':
// dompdf doesn't have additional configuration in the current setup
break;
}
}
} catch (\Exception $e) {
// Silently fail if database is not available (during installation, migrations, etc.)
// This prevents the application from breaking during setup
}
}
/**
* Configure file system settings from database
*/
protected function configureFileSystemFromDatabase(): void
{
try {
// Get the default file disk from database
$fileDisk = \App\Models\FileDisk::whereSetAsDefault(true)->first();
if ($fileDisk) {
$fileDisk->setConfig();
}
} catch (\Exception $e) {
// Silently fail if database is not available (during installation, migrations, etc.)
// This prevents the application from breaking during setup
}
}
}

View File

@@ -36,12 +36,12 @@ class GotenbergPDFDriver
{
public function loadView(string $viewname): GotenbergPDFResponse
{
$papersize = explode(' ', config('pdf.gotenberg.papersize'));
$papersize = explode(' ', config('pdf.connections.gotenberg.papersize'));
if (count($papersize) != 2) {
throw new \InvalidArgumentException('Invalid Gotenberg Papersize specified');
}
$host = config('pdf.gotenberg.host');
$host = config('pdf.connections.gotenberg.host');
$request = Gotenberg::chromium($host)
->pdf()
->margins(0, 0, 0, 0) // Margins can be set using CSS

View File

@@ -3,9 +3,7 @@
namespace App\Space;
use App\Http\Requests\DatabaseEnvironmentRequest;
use App\Http\Requests\DiskEnvironmentRequest;
use App\Http\Requests\DomainEnvironmentRequest;
use App\Http\Requests\MailEnvironmentRequest;
use App\Http\Requests\PDFConfigurationRequest;
use Exception;
use Illuminate\Support\Facades\Artisan;
@@ -238,29 +236,6 @@ class EnvironmentManager
return DB::connection()->getPdo();
}
/**
* Save the mail content to the .env file.
*
* @return array
*/
public function saveMailVariables(MailEnvironmentRequest $request)
{
$mailEnv = $this->getMailConfiguration($request);
try {
$this->updateEnv($mailEnv);
} catch (Exception $e) {
return [
'error' => 'mail_variables_save_error',
];
}
return [
'success' => 'mail_variables_save_successfully',
];
}
/**
* Save the pdf generation content to the .env file.
*
@@ -315,169 +290,6 @@ class EnvironmentManager
return $pdfEnv;
}
/**
* Returns the mail configuration
*
* @param MailEnvironmentRequest $request
* @return array
*/
private function getMailConfiguration($request)
{
$mailEnv = [];
$driver = $request->get('mail_driver');
switch ($driver) {
case 'smtp':
$mailEnv = [
'MAIL_MAILER' => $request->get('mail_driver'),
'MAIL_HOST' => $request->get('mail_host'),
'MAIL_PORT' => $request->get('mail_port'),
'MAIL_USERNAME' => $request->get('mail_username'),
'MAIL_PASSWORD' => $request->get('mail_password'),
'MAIL_SCHEME' => $request->get('mail_encryption') !== 'none' ? $request->get('mail_encryption') : 'null',
'MAIL_FROM_ADDRESS' => $request->get('from_mail'),
'MAIL_FROM_NAME' => $request->get('from_name'),
];
break;
case 'mailgun':
$mailEnv = [
'MAIL_MAILER' => $request->get('mail_driver'),
'MAIL_HOST' => $request->get('mail_host'),
'MAIL_PORT' => $request->get('mail_port'),
'MAIL_USERNAME' => config('mail.username'),
'MAIL_PASSWORD' => config('mail.password'),
'MAIL_FROM_ADDRESS' => $request->get('from_mail'),
'MAIL_FROM_NAME' => $request->get('from_name'),
'MAILGUN_DOMAIN' => $request->get('mail_mailgun_domain'),
'MAILGUN_SECRET' => $request->get('mail_mailgun_secret'),
'MAILGUN_ENDPOINT' => $request->get('mail_mailgun_endpoint'),
];
break;
case 'ses':
$mailEnv = [
'MAIL_MAILER' => $request->get('mail_driver'),
'MAIL_HOST' => $request->get('mail_host'),
'MAIL_PORT' => $request->get('mail_port'),
'MAIL_USERNAME' => config('mail.username'),
'MAIL_PASSWORD' => config('mail.password'),
'MAIL_ENCRYPTION' => $request->get('mail_encryption'),
'MAIL_FROM_ADDRESS' => $request->get('from_mail'),
'MAIL_FROM_NAME' => $request->get('from_name'),
'SES_KEY' => $request->get('mail_ses_key'),
'SES_SECRET' => $request->get('mail_ses_secret'),
'SES_REGION' => $request->get('mail_ses_region'),
];
break;
case 'sendmail':
case 'mail':
$mailEnv = [
'MAIL_MAILER' => $request->get('mail_driver'),
'MAIL_HOST' => config('mail.host'),
'MAIL_PORT' => config('mail.port'),
'MAIL_USERNAME' => config('mail.username'),
'MAIL_PASSWORD' => config('mail.password'),
'MAIL_ENCRYPTION' => config('mail.encryption'),
'MAIL_FROM_ADDRESS' => $request->get('from_mail'),
'MAIL_FROM_NAME' => $request->get('from_name'),
];
break;
}
return $mailEnv;
}
/**
* Save the disk content to the .env file.
*
* @return array
*/
public function saveDiskVariables(DiskEnvironmentRequest $request)
{
$diskEnv = $this->getDiskConfiguration($request);
try {
$this->updateEnv($diskEnv);
} catch (Exception $e) {
return [
'error' => 'disk_variables_save_error',
];
}
return [
'success' => 'disk_variables_save_successfully',
];
}
/**
* Returns the disk configuration
*
* @return array
*/
private function getDiskConfiguration(DiskEnvironmentRequest $request)
{
$diskEnv = [];
$driver = $request->get('app_domain');
if ($driver) {
$diskEnv['FILESYSTEM_DRIVER'] = $driver;
}
switch ($request->get('selected_driver')) {
case 's3':
$diskEnv = [
'AWS_KEY' => $request->get('aws_key'),
'AWS_SECRET' => $request->get('aws_secret'),
'AWS_REGION' => $request->get('aws_region'),
'AWS_BUCKET' => $request->get('aws_bucket'),
'AWS_ROOT' => $request->get('aws_root'),
];
break;
case 'doSpaces':
$diskEnv = [
'DO_SPACES_KEY' => $request->get('do_spaces_key'),
'DO_SPACES_SECRET' => $request->get('do_spaces_secret'),
'DO_SPACES_REGION' => $request->get('do_spaces_region'),
'DO_SPACES_BUCKET' => $request->get('do_spaces_bucket'),
'DO_SPACES_ENDPOINT' => $request->get('do_spaces_endpoint'),
'DO_SPACES_ROOT' => $request->get('do_spaces_root'),
];
break;
case 'dropbox':
$diskEnv = [
'DROPBOX_TOKEN' => $request->get('dropbox_token'),
'DROPBOX_KEY' => $request->get('dropbox_key'),
'DROPBOX_SECRET' => $request->get('dropbox_secret'),
'DROPBOX_APP' => $request->get('dropbox_app'),
'DROPBOX_ROOT' => $request->get('dropbox_root'),
];
break;
}
return $diskEnv;
}
/**
* Save sanctum stateful domain to the .env file.
*
@@ -507,35 +319,6 @@ class EnvironmentManager
];
}
/**
* Order the env contents
*
* @return void
*/
public function reoderEnv()
{
$contents = $this->getEnvContents();
$contents = explode($this->delimiter, $contents);
if (empty($contents)) {
return;
}
natsort($contents);
$formatted = '';
$previous = '';
foreach ($contents as $current) {
$parts_line = explode('_', $current);
$parts_last = explode('_', $previous);
if ($parts_line[0] != $parts_last[0]) {
$formatted .= $this->delimiter;
}
$formatted .= $current.$this->delimiter;
$previous = $current;
}
file_put_contents($this->envPath, trim($formatted));
}
private function getDomains(string $requestDomain): array
{
$appUrl = config('app.url');

View File

@@ -6,4 +6,5 @@ return [
App\Providers\DropboxServiceProvider::class,
App\Providers\ViewServiceProvider::class,
App\Providers\PDFServiceProvider::class,
App\Providers\AppConfigProvider::class,
];

View File

@@ -13,6 +13,19 @@ use App\Models\RecurringInvoice;
use App\Models\TaxType;
return [
/*
|--------------------------------------------------------------------------
| Abilities Configuration
|--------------------------------------------------------------------------
|
| This file is for defining the abilities used in the application. Each
| ability includes a name, a unique identifier (ability), the associated
| model, and any dependencies on other abilities. This configuration helps
| manage user permissions and access control throughout the application.
|
*/
'abilities' => [
// Customer

View File

@@ -4,11 +4,19 @@ use Illuminate\Support\Facades\Facade;
return [
/*
|--------------------------------------------------------------------------
| Class Aliases
|--------------------------------------------------------------------------
|
| This array of class aliases will be registered when this application
| is started. You may add any additional class aliases which should
| be loaded to the array. For speed, all aliases are lazy loaded.
|
*/
'aliases' => Facade::defaultAliases()->merge([
'Flash' => Laracasts\Flash\Flash::class,
'Menu' => Lavary\Menu\Facade::class,
'Pusher' => Pusher\Pusher::class,
'Redis' => Illuminate\Support\Facades\Redis::class,
])->toArray(),
];

View File

@@ -2,13 +2,49 @@
return [
/*
|--------------------------------------------------------------------------
| Authentication Defaults
|--------------------------------------------------------------------------
|
| This option defines the default authentication "guard" and password
| reset "broker" for your application. You may change these values
| as required, but they're a perfect start for most applications.
|
*/
'defaults' => [
'guard' => env('AUTH_GUARD', 'web'),
'passwords' => env('AUTH_PASSWORD_BROKER', 'users'),
],
/*
|--------------------------------------------------------------------------
| Authentication Guards
|--------------------------------------------------------------------------
|
| Next, you may define every authentication guard for your application.
| Of course, a great default configuration has been defined for you
| which utilizes session storage plus the Eloquent user provider.
|
| All authentication guards have a user provider, which defines how the
| users are actually retrieved out of your database or other storage
| system used by the application. Typically, Eloquent is utilized.
|
| Supported: "session"
|
*/
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'customer' => [
'driver' => 'session',
'provider' => 'customers',
@@ -17,13 +53,42 @@ return [
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => \App\Models\User::class,
],
'customers' => [
'driver' => 'eloquent',
'model' => \App\Models\Customer::class,
],
],
/*
|--------------------------------------------------------------------------
| Resetting Passwords
|--------------------------------------------------------------------------
|
| These configuration options specify the behavior of Laravel's password
| reset functionality, including the table utilized for token storage
| and the user provider that is invoked to actually retrieve users.
|
| The expiry time is the number of minutes that each reset token will be
| considered valid. This security feature keeps tokens short-lived so
| they have less time to be guessed. You may change this as needed.
|
| The throttle setting is the number of seconds a user must wait before
| generating more password reset tokens. This prevents the user from
| quickly generating a very large amount of password reset tokens.
|
*/
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_reset_tokens',
'expire' => 60,
'throttle' => 60,
],
'customers' => [
'provider' => 'customers',
'table' => 'password_reset_tokens',

View File

@@ -1,5 +1,17 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Cache Store
|--------------------------------------------------------------------------
|
| This option controls the default cache store that will be used by the
| framework. This connection is utilized if another isn't explicitly
| specified when running a cache operation inside the application.
|
*/
'default' => env('CACHE_STORE', 'file'),
];

View File

@@ -2,9 +2,41 @@
return [
'cloud' => env('FILESYSTEM_CLOUD', 's3'),
/*
|--------------------------------------------------------------------------
| Default Filesystem Disk
|--------------------------------------------------------------------------
|
| Here you may specify the default filesystem disk that should be used
| by the framework. The "local" disk, as well as a variety of cloud
| based disks are available to your application for file storage.
|
*/
'default' => env('FILESYSTEM_DISK', 'local'),
/*
|--------------------------------------------------------------------------
| Filesystem Disks
|--------------------------------------------------------------------------
|
| Below you may configure as many filesystem disks as necessary, and you
| may even configure multiple disks for the same driver. Examples for
| most supported storage drivers are configured here for reference.
|
| Supported drivers: "local", "ftp", "sftp", "s3"
|
*/
'disks' => [
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
'report' => false,
],
's3' => [
'driver' => 's3',
'key' => env('AWS_KEY'),
@@ -24,11 +56,6 @@ return [
'bucket' => env('S3_COMPAT_BUCKET'),
],
'media' => [
'driver' => 'local',
'root' => public_path('media'),
],
'doSpaces' => [
'type' => 'AwsS3',
'driver' => 's3',
@@ -51,6 +78,11 @@ return [
'root' => env('DROPBOX_ROOT'),
],
'media' => [
'driver' => 'local',
'root' => public_path('media'),
],
'views' => [
'driver' => 'local',
'root' => resource_path('views'),
@@ -60,13 +92,6 @@ return [
'driver' => 'local',
'root' => storage_path('app/templates/pdf'),
],
'local' => [
'driver' => 'local',
'root' => storage_path('app'),
'throw' => false,
'report' => false,
],
],
];

View File

@@ -2,13 +2,35 @@
return [
'driver' => env('PDF_DRIVER', 'gotenberg'),
/*
|--------------------------------------------------------------------------
| Default PDF Driver
|--------------------------------------------------------------------------
| Here you may specify which of the PDF drivers below you wish to use as
| your default driver for all PDF generation.
|
*/
'gotenberg' => [
'host' => env('GOTENBERG_HOST', 'http://pdf:3000'),
'papersize' => env('GOTENBERG_PAPERSIZE', '210mm 297mm'),
'driver' => env('PDF_DRIVER', 'dompdf'),
/*
|--------------------------------------------------------------------------
| PDF Connections
|--------------------------------------------------------------------------
|
| Here are each of the connections setup for your application. Example
| configuration has been included, but you may add as many connections as
| you would like.
|
*/
'connections' => [
'dompdf' => [],
'gotenberg' => [
'host' => env('GOTENBERG_HOST', 'http://pdf:3000'),
'papersize' => env('GOTENBERG_PAPERSIZE', '210mm 297mm'),
],
],
'dompdf' => [],
];

View File

@@ -1,5 +1,112 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default Queue Connection Name
|--------------------------------------------------------------------------
|
| Laravel's queue supports a variety of backends via a single, unified
| API, giving you convenient access to each backend using identical
| syntax for each. The default queue connection is defined below.
|
*/
'default' => env('QUEUE_CONNECTION', 'sync'),
/*
|--------------------------------------------------------------------------
| Queue Connections
|--------------------------------------------------------------------------
|
| Here you may configure the connection options for every queue backend
| used by your application. An example configuration is provided for
| each backend supported by Laravel. You're also free to add more.
|
| Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null"
|
*/
'connections' => [
'sync' => [
'driver' => 'sync',
],
'database' => [
'driver' => 'database',
'connection' => env('DB_QUEUE_CONNECTION'),
'table' => env('DB_QUEUE_TABLE', 'jobs'),
'queue' => env('DB_QUEUE', 'default'),
'retry_after' => (int) env('DB_QUEUE_RETRY_AFTER', 90),
'after_commit' => false,
],
'beanstalkd' => [
'driver' => 'beanstalkd',
'host' => env('BEANSTALKD_QUEUE_HOST', 'localhost'),
'queue' => env('BEANSTALKD_QUEUE', 'default'),
'retry_after' => (int) env('BEANSTALKD_QUEUE_RETRY_AFTER', 90),
'block_for' => 0,
'after_commit' => false,
],
'sqs' => [
'driver' => 'sqs',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'),
'queue' => env('SQS_QUEUE', 'default'),
'suffix' => env('SQS_SUFFIX'),
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
'after_commit' => false,
],
'redis' => [
'driver' => 'redis',
'connection' => env('REDIS_QUEUE_CONNECTION', 'default'),
'queue' => env('REDIS_QUEUE', 'default'),
'retry_after' => (int) env('REDIS_QUEUE_RETRY_AFTER', 90),
'block_for' => null,
'after_commit' => false,
],
],
/*
|--------------------------------------------------------------------------
| Job Batching
|--------------------------------------------------------------------------
|
| The following options configure the database and table that store job
| batching information. These options can be updated to any database
| connection and table which has been defined by your application.
|
*/
'batching' => [
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'job_batches',
],
/*
|--------------------------------------------------------------------------
| Failed Queue Jobs
|--------------------------------------------------------------------------
|
| These options configure the behavior of failed queue job logging so you
| can control how and where failed jobs are stored. Laravel ships with
| support for storing failed jobs in a simple file or in a database.
|
| Supported drivers: "database-uuids", "dynamodb", "file", "null"
|
*/
'failed' => [
'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'),
'database' => env('DB_CONNECTION', 'sqlite'),
'table' => 'failed_jobs',
],
];

View File

@@ -2,6 +2,18 @@
return [
/*
|--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
| This file is for storing the credentials for third party services such
| as Mailgun, Postmark, AWS and more. This file provides the de facto
| location for this type of information, allowing packages to have
| a conventional file to locate the various service credentials.
|
*/
'mailgun' => [
'domain' => env('MAILGUN_DOMAIN'),
'secret' => env('MAILGUN_SECRET'),
@@ -26,28 +38,6 @@ return [
],
],
'facebook' => [
'client_id' => env('FACEBOOK_CLIENT_ID'),
'client_secret' => env('FACEBOOK_CLIENT_SECRET'),
'redirect' => env('FACEBOOK_REDIRECT_URL'),
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
'redirect' => env('GOOGLE_REDIRECT_URL'),
],
'github' => [
'client_id' => env('GITHUB_CLIENT_ID'),
'client_secret' => env('GITHUB_CLIENT_SECRET'),
'redirect' => env('GITHUB_REDIRECT_URL'),
],
'cron_job' => [
'auth_token' => env('CRON_JOB_AUTH_TOKEN', 0),
],
'ses' => [
'key' => env('SES_KEY'),
'secret' => env('SES_SECRET'),

217
config/session.php Normal file
View File

@@ -0,0 +1,217 @@
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Session Driver
|--------------------------------------------------------------------------
|
| This option determines the default session driver that is utilized for
| incoming requests. Laravel supports a variety of storage options to
| persist session data. Database storage is a great default choice.
|
| Supported: "file", "cookie", "database", "apc",
| "memcached", "redis", "dynamodb", "array"
|
*/
'driver' => env('SESSION_DRIVER', 'file'),
/*
|--------------------------------------------------------------------------
| Session Lifetime
|--------------------------------------------------------------------------
|
| Here you may specify the number of minutes that you wish the session
| to be allowed to remain idle before it expires. If you want them
| to expire immediately when the browser is closed then you may
| indicate that via the expire_on_close configuration option.
|
*/
'lifetime' => (int) env('SESSION_LIFETIME', 1440),
'expire_on_close' => env('SESSION_EXPIRE_ON_CLOSE', false),
/*
|--------------------------------------------------------------------------
| Session Encryption
|--------------------------------------------------------------------------
|
| This option allows you to easily specify that all of your session data
| should be encrypted before it's stored. All encryption is performed
| automatically by Laravel and you may use the session like normal.
|
*/
'encrypt' => env('SESSION_ENCRYPT', false),
/*
|--------------------------------------------------------------------------
| Session File Location
|--------------------------------------------------------------------------
|
| When utilizing the "file" session driver, the session files are placed
| on disk. The default storage location is defined here; however, you
| are free to provide another location where they should be stored.
|
*/
'files' => storage_path('framework/sessions'),
/*
|--------------------------------------------------------------------------
| Session Database Connection
|--------------------------------------------------------------------------
|
| When using the "database" or "redis" session drivers, you may specify a
| connection that should be used to manage these sessions. This should
| correspond to a connection in your database configuration options.
|
*/
'connection' => env('SESSION_CONNECTION'),
/*
|--------------------------------------------------------------------------
| Session Database Table
|--------------------------------------------------------------------------
|
| When using the "database" session driver, you may specify the table to
| be used to store sessions. Of course, a sensible default is defined
| for you; however, you're welcome to change this to another table.
|
*/
'table' => env('SESSION_TABLE', 'sessions'),
/*
|--------------------------------------------------------------------------
| Session Cache Store
|--------------------------------------------------------------------------
|
| When using one of the framework's cache driven session backends, you may
| define the cache store which should be used to store the session data
| between requests. This must match one of your defined cache stores.
|
| Affects: "apc", "dynamodb", "memcached", "redis"
|
*/
'store' => env('SESSION_STORE'),
/*
|--------------------------------------------------------------------------
| Session Sweeping Lottery
|--------------------------------------------------------------------------
|
| Some session drivers must manually sweep their storage location to get
| rid of old sessions from storage. Here are the chances that it will
| happen on a given request. By default, the odds are 2 out of 100.
|
*/
'lottery' => [2, 100],
/*
|--------------------------------------------------------------------------
| Session Cookie Name
|--------------------------------------------------------------------------
|
| Here you may change the name of the session cookie that is created by
| the framework. Typically, you should not need to change this value
| since doing so does not grant a meaningful security improvement.
|
*/
'cookie' => env(
'SESSION_COOKIE',
Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
),
/*
|--------------------------------------------------------------------------
| Session Cookie Path
|--------------------------------------------------------------------------
|
| The session cookie path determines the path for which the cookie will
| be regarded as available. Typically, this will be the root path of
| your application, but you're free to change this when necessary.
|
*/
'path' => env('SESSION_PATH', '/'),
/*
|--------------------------------------------------------------------------
| Session Cookie Domain
|--------------------------------------------------------------------------
|
| This value determines the domain and subdomains the session cookie is
| available to. By default, the cookie will be available to the root
| domain and all subdomains. Typically, this shouldn't be changed.
|
*/
'domain' => env('SESSION_DOMAIN'),
/*
|--------------------------------------------------------------------------
| HTTPS Only Cookies
|--------------------------------------------------------------------------
|
| By setting this option to true, session cookies will only be sent back
| to the server if the browser has a HTTPS connection. This will keep
| the cookie from being sent to you when it can't be done securely.
|
*/
'secure' => env('SESSION_SECURE_COOKIE'),
/*
|--------------------------------------------------------------------------
| HTTP Access Only
|--------------------------------------------------------------------------
|
| Setting this value to true will prevent JavaScript from accessing the
| value of the cookie and the cookie will only be accessible through
| the HTTP protocol. It's unlikely you should disable this option.
|
*/
'http_only' => env('SESSION_HTTP_ONLY', true),
/*
|--------------------------------------------------------------------------
| Same-Site Cookies
|--------------------------------------------------------------------------
|
| This option determines how your cookies behave when cross-site requests
| take place, and can be used to mitigate CSRF attacks. By default, we
| will set this value to "lax" to permit secure cross-site requests.
|
| See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value
|
| Supported: "lax", "strict", "none", null
|
*/
'same_site' => env('SESSION_SAME_SITE', 'lax'),
/*
|--------------------------------------------------------------------------
| Partitioned Cookies
|--------------------------------------------------------------------------
|
| Setting this value to true will tie the cookie to the top-level site for
| a cross-site context. Partitioned cookies are accepted by the browser
| when flagged "secure" and the Same-Site attribute is set to "none".
|
*/
'partitioned' => env('SESSION_PARTITIONED_COOKIE', false),
];

View File

@@ -46,13 +46,6 @@ services:
- SESSION_LIFETIME=240
- SESSION_DOMAIN=localhost
- SANCTUM_STATEFUL_DOMAINS=localhost:8090
#- MAIL_MAILER=smtp
#- MAIL_HOST=smtp.mailtrap.io
#- MAIL_PORT=2525
#- MAIL_USERNAME=null
#- MAIL_PASSWORD=null
#- MAIL_PASSWORD_FILE=<filename>
#- MAIL_ENCRYPTION=null
volumes:
- invoiceshelf_storage:/var/www/html/storage/
ports:

View File

@@ -27,13 +27,6 @@ services:
- SESSION_LIFETIME=240
- SESSION_DOMAIN=localhost
- SANCTUM_STATEFUL_DOMAINS=localhost:8090
#- MAIL_DRIVER=smtp
#- MAIL_HOST=smtp.mailtrap.io
#- MAIL_PORT=2525
#- MAIL_USERNAME=null
#- MAIL_PASSWORD=null
#- MAIL_PASSWORD_FILE=<filename>
#- MAIL_ENCRYPTION=null
restart: unless-stopped
networks:
invoiceshelf:

View File

@@ -1,5 +1,3 @@
#!/bin/bash
function replace_or_insert() {
# Voodoo magic: https://superuser.com/a/976712
@@ -71,36 +69,6 @@ fi
if [ "$BROADCAST_CONNECTION" != '' ]; then
replace_or_insert "BROADCAST_CONNECTION" "$BROADCAST_CONNECTION"
fi
if [ "$MAIL_DRIVER" != '' ]; then
replace_or_insert "MAIL_MAILER" "$MAIL_DRIVER"
fi
if [ "$MAIL_MAILER" != '' ]; then
replace_or_insert "MAIL_MAILER" "$MAIL_MAILER"
fi
if [ "$MAIL_HOST" != '' ]; then
replace_or_insert "MAIL_HOST" "$MAIL_HOST"
fi
if [ "$MAIL_PORT" != '' ]; then
replace_or_insert "MAIL_PORT" "$MAIL_PORT"
fi
if [ "$MAIL_USERNAME" != '' ]; then
replace_or_insert "MAIL_USERNAME" "$MAIL_USERNAME"
fi
if [ "$MAIL_PASSWORD" != '' ]; then
replace_or_insert "MAIL_PASSWORD" "$MAIL_PASSWORD"
elif [ "$MAIL_PASSWORD_FILE" != '' ]; then
value=$(<$MAIL_PASSWORD_FILE)
replace_or_insert "MAIL_PASSWORD" "$value"
fi
if [ "$MAIL_SCHEME" != '' ]; then
replace_or_insert "MAIL_SCHEME" "$MAIL_SCHEME"
fi
if [ "$MAIL_FROM_NAME" != '' ]; then
replace_or_insert "MAIL_FROM_NAME" "$MAIL_FROM_NAME"
fi
if [ "$MAIL_FROM_ADDRESS" != '' ]; then
replace_or_insert "MAIL_FROM_ADDRESS" "$MAIL_FROM_ADDRESS"
fi
if [ "$TRUSTED_PROXIES" != '' ]; then
replace_or_insert "TRUSTED_PROXIES" "$TRUSTED_PROXIES"
fi

View File

@@ -162,7 +162,7 @@ const isSaving = ref(false)
let isFetchingInitialData = ref(false)
let currentPreferences = reactive({
currency: 1,
currency: 3,
language: 'en',
carbon_date_format: 'd M Y',
time_zone: 'UTC',