Refactor for live previews

This commit is contained in:
David Bomba 2023-10-17 21:28:18 +11:00
parent fb3c0120ec
commit e4fc9e2cc8
5 changed files with 297 additions and 50 deletions

View File

@ -11,42 +11,43 @@
namespace App\Http\Controllers;
use App\DataMapper\Analytics\LivePreview;
use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory;
use App\Factory\QuoteFactory;
use App\Factory\RecurringInvoiceFactory;
use App\Http\Requests\Preview\DesignPreviewRequest;
use App\Http\Requests\Preview\PreviewInvoiceRequest;
use App\Jobs\Util\PreviewPdf;
use App\Libraries\MultiDB;
use App\Utils\Ninja;
use App\Models\Quote;
use App\Models\Client;
use App\Models\ClientContact;
use App\Models\Credit;
use App\Models\Invoice;
use App\Models\InvoiceInvitation;
use App\Models\Quote;
use App\Models\RecurringInvoice;
use App\Repositories\CreditRepository;
use App\Repositories\InvoiceRepository;
use App\Repositories\QuoteRepository;
use App\Repositories\RecurringInvoiceRepository;
use App\Utils\HtmlEngine;
use App\Libraries\MultiDB;
use App\Factory\QuoteFactory;
use App\Jobs\Util\PreviewPdf;
use App\Models\ClientContact;
use App\Services\Pdf\PdfMock;
use App\Factory\CreditFactory;
use App\Factory\InvoiceFactory;
use App\Utils\Traits\MakesHash;
use App\Models\RecurringInvoice;
use App\Utils\PhantomJS\Phantom;
use App\Models\InvoiceInvitation;
use App\Services\PdfMaker\Design;
use App\Utils\HostedPDF\NinjaPdf;
use Illuminate\Support\Facades\DB;
use App\Services\PdfMaker\PdfMaker;
use Illuminate\Support\Facades\App;
use App\Repositories\QuoteRepository;
use Illuminate\Support\Facades\Cache;
use App\Repositories\CreditRepository;
use App\Utils\Traits\MakesInvoiceHtml;
use Turbo124\Beacon\Facades\LightLogs;
use App\Repositories\InvoiceRepository;
use App\Utils\Traits\Pdf\PageNumbering;
use App\Factory\RecurringInvoiceFactory;
use Illuminate\Support\Facades\Response;
use App\DataMapper\Analytics\LivePreview;
use App\Repositories\RecurringInvoiceRepository;
use App\Http\Requests\Preview\DesignPreviewRequest;
use App\Services\PdfMaker\Design as PdfDesignModel;
use App\Services\PdfMaker\Design as PdfMakerDesign;
use App\Services\PdfMaker\PdfMaker;
use App\Utils\HostedPDF\NinjaPdf;
use App\Utils\HtmlEngine;
use App\Utils\Ninja;
use App\Utils\PhantomJS\Phantom;
use App\Utils\Traits\MakesHash;
use App\Utils\Traits\MakesInvoiceHtml;
use App\Utils\Traits\Pdf\PageNumbering;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Response;
use Turbo124\Beacon\Facades\LightLogs;
use App\Http\Requests\Preview\PreviewInvoiceRequest;
class PreviewController extends BaseController
{
@ -56,7 +57,108 @@ class PreviewController extends BaseController
public function __construct()
{
parent::__construct();
parent::__construct();
}
private function purgeCache()
{ nlog(auth()->user()->id);
Cache::pull("preview_".auth()->user()->id);
}
public function newLivePreview(PreviewInvoiceRequest $request)
{
$time = time();
nlog($time);
$invitation = $request->resolveInvitation();
App::forgetInstance('translator');
$t = app('translator');
App::setLocale($invitation->contact->preferredLocale());
$t->replace(Ninja::transformTranslations($invitation->{$request->entity}->client->getMergedSettings()));
$html = new HtmlEngine($invitation);
$variables = $html->generateLabelsAndValues();
$entity_obj = $invitation->{$request->entity};
// if (! $invitation->{$request->entity}->id ?? true) {
// $invitation->{$request->entity}->service()->fillDefaults();
// }
$design = \App\Models\Design::withTrashed()->find($entity_obj->design_id ?? 2);
/* Catch all in case migration doesn't pass back a valid design */
if (! $design) {
$design = \App\Models\Design::find(2);
}
if ($design->is_custom) {
$options = [
'custom_partials' => json_decode(json_encode($design->design), true),
];
$template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options);
} else {
$template = new PdfMakerDesign(strtolower($design->name));
}
$state = [
'template' => $template->elements([
'client' => $entity_obj->client,
'entity' => $entity_obj,
'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables,
'$product' => $design->design->product,
'variables' => $variables,
]),
'variables' => $variables,
'options' => [
'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'),
'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'),
],
'process_markdown' => $entity_obj->client->company->markdown_enabled,
];
$maker = new PdfMaker($state);
$maker
->design($template)
->build();
if (request()->query('html') == 'true') {
return $maker->getCompiledHTML();
}
//if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
}
/** @var \App\Models\User $user */
$user = auth()->user();
$company = $user->company();
if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') {
$pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true));
$numbered_pdf = $this->pageNumbering($pdf, $company);
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
return $pdf;
}
$pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle();
nlog("merpy derp {$time}");
return response()->streamDownload(function () use ($pdf) {
echo $pdf;
}, 'preview.pdf', ['Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache']);
}
/**
@ -173,10 +275,18 @@ class PreviewController extends BaseController
public function live(PreviewInvoiceRequest $request)
{
// if(Cache::has("preview_".auth()->user()->id))
// return response()->json(['message' => 'Please wait a few seconds before trying again, this many requests are not good.'], 400);
nlog("should not see a lot of these");
if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) {
return response()->json(['message' => 'This server cannot handle this request.'], 400);
}
Cache::put("preview_".auth()->user()->id, 60);
$start = microtime(true);
/** @var \App\Models\User $user */
@ -287,9 +397,13 @@ class PreviewController extends BaseController
DB::connection(config('database.default'))->rollBack();
if (request()->query('html') == 'true') {
$this->purgeCache();
return $maker->getCompiledHTML();
}
} catch(\Exception $e) {
$this->purgeCache();
DB::connection(config('database.default'))->rollBack();
@ -302,6 +416,7 @@ class PreviewController extends BaseController
//if phantom js...... inject here..
if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') {
$this->purgeCache();
return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true));
}
@ -319,7 +434,7 @@ class PreviewController extends BaseController
if ($numbered_pdf) {
$pdf = $numbered_pdf;
}
$this->purgeCache();
return $pdf;
}
@ -335,7 +450,8 @@ class PreviewController extends BaseController
$response->header('Content-Type', 'application/pdf');
$response->header('Server-Timing', microtime(true)-$start);
nlog("returning a response");
$this->purgeCache();
return $response;
}

View File

@ -23,7 +23,7 @@ class ProtectedDownloadController extends BaseController
public function index(Request $request)
{
/** @var string $hashed_path */
$hashed_path = Cache::pull($request->hash);
if (!$hashed_path) {

View File

@ -11,15 +11,29 @@
namespace App\Http\Requests\Preview;
use App\Models\Quote;
use App\Models\Client;
use App\Models\Credit;
use App\Models\Invoice;
use App\Libraries\MultiDB;
use App\Http\Requests\Request;
use App\Utils\Traits\CleanLineItems;
use App\Models\CompanyGateway;
use App\Models\QuoteInvitation;
use App\Utils\Traits\MakesHash;
use Illuminate\Validation\Rule;
use App\Models\CreditInvitation;
use App\Models\RecurringInvoice;
use App\Models\InvoiceInvitation;
use App\Utils\Traits\CleanLineItems;
use App\Models\RecurringInvoiceInvitation;
class PreviewInvoiceRequest extends Request
{
use MakesHash;
use CleanLineItems;
private string $entity_plural = '';
/**
* Determine if the user is authorized to make this request.
*
@ -27,20 +41,32 @@ class PreviewInvoiceRequest extends Request
*/
public function authorize() : bool
{
return auth()->user()->hasIntersectPermissionsOrAdmin(['view_invoice', 'view_quote', 'view_recurring_invoice', 'view_credit', 'create_invoice', 'create_quote', 'create_recurring_invoice', 'create_credit','edit_invoice', 'edit_quote', 'edit_recurring_invoice', 'edit_credit']);
/** @var \App\Models\User $user */
$user = auth()->user();
return $user->hasIntersectPermissionsOrAdmin(['view_invoice', 'view_quote', 'view_recurring_invoice', 'view_credit', 'create_invoice', 'create_quote', 'create_recurring_invoice', 'create_credit','edit_invoice', 'edit_quote', 'edit_recurring_invoice', 'edit_credit']);
}
public function rules()
{
$rules = [];
/** @var \App\Models\User $user */
$user = auth()->user();
$rules['number'] = ['nullable'];
return [
'number' => 'nullable',
'entity' => 'bail|sometimes|in:invoice,quote,credit,recurring_invoice',
'entity_id' => ['bail','sometimes','integer',Rule::exists($this->entity_plural, 'id')->where('is_deleted',0)->where('company_id', $user->company()->id)],
'client_id' => ['required', Rule::exists(Client::class, 'id')->where('is_deleted', 0)->where('company_id', $user->company()->id)],
];
return $rules;
}
public function prepareForValidation()
{
/** @var \App\Models\User $user */
$user = auth()->user();
$input = $this->all();
$input = $this->decodePrimaryKeys($input);
@ -50,6 +76,96 @@ class PreviewInvoiceRequest extends Request
$input['balance'] = 0;
$input['number'] = isset($input['number']) ? $input['number'] : ctrans('texts.live_preview').' #'.rand(0, 1000);
if($input['entity_id'] ?? false)
$input['entity_id'] = $this->decodePrimaryKey($input['entity_id'], true);
$this->convertEntityPlural($input['entity'] ?? 'invoice');
$this->replace($input);
}
public function resolveInvitation()
{
$invitation = false;
if(! $this->entity_id ?? false)
return $this->stubInvitation();
match($this->entity){
'invoice' => $invitation = Invoice::withTrashed()->where('invoice_id', $this->entity_id)->first(),
'quote' => $invitation = Quote::withTrashed()->where('quote_id', $this->entity_id)->first(),
'credit' => $invitation = Credit::withTrashed()->where('credit_id', $this->entity_id)->first(),
'recurring_invoice' => $invitation = RecurringInvoice::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(),
};
if($invitation)
return $invitation;
$invitation = $this->stubInvitation();
}
public function stubInvitation()
{
$client = Client::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->client_id);
$invitation = false;
match($this->entity) {
'invoice' => $invitation = InvoiceInvitation::factory()->make(),
'quote' => $invitation = QuoteInvitation::factory()->make(),
'credit' => $invitation = CreditInvitation::factory()->make(),
'recurring_invoice' => $invitation = RecurringInvoiceInvitation::factory()->make(),
default => $invitation = InvoiceInvitation::factory()->make(),
};
$entity = $this->stubEntity($client);
$invitation->make();
$invitation->setRelation($this->entity, $entity);
$invitation->setRelation('contact', $client->contacts->first()->load('client.company'));
$invitation->setRelation('company', $client->company);
return $invitation;
}
private function stubEntity(Client $client)
{
$entity = false;
match($this->entity){
'invoice' => $entity = Invoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]),
'quote' => $entity = Quote::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]),
'credit' => $entity = Credit::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]),
'recurring_invoice' => $entity = RecurringInvoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]),
default => $entity = Invoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]),
};
$entity->setRelation('client', $client);
$entity->setRelation('company', $client->company);
$entity->setRelation('user', $client->user);
$entity->fill($this->all());
return $entity;
}
private function convertEntityPlural(string $entity) :self
{
switch ($entity) {
case 'invoice':
$this->entity_plural = 'invoices';
return $this;
case 'quote':
$this->entity_plural = 'quotes';
return $this;
case 'credit':
$this->entity_plural = 'credits';
return $this;
case 'recurring_invoice':
$this->entity_plural = 'invoices';
return $this;
default:
$this->entity_plural = 'invoices';
return $this;
}
}
}

View File

@ -24,25 +24,14 @@ class PreviewPdf implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, PdfMaker, PageNumbering;
public $company;
private $disk;
public $design_string;
/**
* Create a new job instance.
*
* @param $design_string
* @param Company $company
*/
public function __construct($design_string, Company $company)
public function __construct(public string $design_string, public Company $company)
{
$this->company = $company;
$this->design_string = $design_string;
$this->disk = $disk ?? config('filesystems.default');
}
public function handle()

View File

@ -13,7 +13,9 @@ namespace Tests\Feature;
use Tests\TestCase;
use App\Models\Design;
use App\Utils\HtmlEngine;
use Tests\MockAccountData;
use App\Models\InvoiceInvitation;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Foundation\Testing\DatabaseTransactions;
@ -41,6 +43,30 @@ class LiveDesignTest extends TestCase
}
}
public function testSyntheticInvitations()
{
$this->assertGreaterThanOrEqual(1, $this->client->contacts->count());
$ii = InvoiceInvitation::factory()
->for($this->invoice)
->for($this->client->contacts->first(), 'contact')
->for($this->company)
->for($this->user)
->make();
$this->assertInstanceOf(InvoiceInvitation::class, $ii);
$engine = new HtmlEngine($ii);
$this->assertNotNull($engine);
$data = $engine->generateLabelsAndValues();
$this->assertIsArray($data);
nlog($data);
}
public function testDesignRoute200()
{
$data = [