Google Sheet - OAuth "client" powered integrations (#415)

* fix `helpers.php`

* fix `.eslintrc.cjs`

* spreadsheet manager

* fetch providers. set `oauth_id` for integrations

* create spreadsheet on integration create event

* connect OAuth accounts

* display actions. connect account if missing

* cleanup

* handle form field change

* map integration data object to `SpreadsheetData`

* validate request

* wip

* redirect to integrations page

* fix refresh token

* add helper text

* add extra integration info

* refactor

* refresh google token

* fix validation

* add tests

* Fix linting issue

* Update composer lock file

---------

Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
Boris Lepikhin
2024-06-05 06:35:46 -07:00
committed by GitHub
parent 03d695c74e
commit 24d33a9ebb
53 changed files with 3383 additions and 298 deletions

View File

@@ -0,0 +1,20 @@
<?php
namespace App\Events\Models;
use App\Models\Integration\FormIntegration;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class FormIntegrationCreated
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
public function __construct(
public FormIntegration $formIntegration
) {
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Forms\Integration;
use App\Http\Controllers\Controller;
use App\Http\Requests\Integration\FormIntegrationsRequest;
use App\Http\Resources\FormIntegrationResource;
use App\Models\Forms\Form;
use App\Models\Integration\FormIntegration;
@@ -19,7 +20,12 @@ class FormIntegrationsController extends Controller
$form = Form::findOrFail((int)$id);
$this->authorize('view', $form);
return FormIntegration::where('form_id', $form->id)->get();
$integrations = FormIntegration::query()
->where('form_id', $form->id)
->with('provider.user')
->get();
return FormIntegrationResource::collection($integrations);
}
public function create(FormIntegrationsRequest $request, string $id)
@@ -27,15 +33,19 @@ class FormIntegrationsController extends Controller
$form = Form::findOrFail((int)$id);
$this->authorize('update', $form);
/** @var FormIntegration $formIntegration */
$formIntegration = FormIntegration::create(
array_merge([
'form_id' => $form->id,
], $request->toIntegrationData())
);
$formIntegration->refresh();
$formIntegration->load('provider.user');
return $this->success([
'message' => 'Form Integration was created.',
'form_integration' => $formIntegration
'form_integration' => FormIntegrationResource::make($formIntegration)
]);
}
@@ -46,10 +56,11 @@ class FormIntegrationsController extends Controller
$formIntegration = FormIntegration::findOrFail((int)$integrationid);
$formIntegration->update($request->toIntegrationData());
$formIntegration->load('provider.user');
return $this->success([
'message' => 'Form Integration was updated.',
'form_integration' => $formIntegration
'form_integration' => FormIntegrationResource::make($formIntegration)
]);
}

View File

@@ -0,0 +1,64 @@
<?php
namespace App\Http\Controllers\OAuth;
use App\Http\Controllers\Controller;
use App\Http\Resources\OAuthProviderResource;
use App\Integrations\OAuth\OAuthProviderService;
use App\Models\OAuthProvider;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class OAuthProviderController extends Controller
{
public function index()
{
/** @var \App\Models\User $user */
$user = Auth::user();
$providers = $user->oauthProviders()->get();
return OAuthProviderResource::collection($providers);
}
public function connect(Request $request, OAuthProviderService $service)
{
$userId = Auth::id();
cache()->put("oauth-intention:{$userId}", $request->input('intention'), 60 * 5);
return response()->json([
'url' => $service->getDriver()->getRedirectUrl(),
]);
}
public function handleRedirect(OAuthProviderService $service)
{
$driverUser = $service->getDriver()->getUser();
$provider = OAuthProvider::query()
->updateOrCreate(
[
'user_id' => Auth::id(),
'provider' => $service,
'provider_user_id' => $driverUser->getId(),
],
[
'access_token' => $driverUser->token,
'refresh_token' => $driverUser->refreshToken,
'name' => $driverUser->getName(),
'email' => $driverUser->getEmail(),
]
);
return OAuthProviderResource::make($provider);
}
public function destroy(OAuthProvider $provider)
{
$this->authorize('delete', $provider);
$provider->delete();
return response()->json();
}
}

View File

@@ -21,9 +21,9 @@ class FormIntegrationsRequest extends FormRequest
// Load integration class, and get rules
$integration = FormIntegration::getIntegration($request->integration_id);
if ($integration && isset($integration['file_name']) && class_exists(
'App\Service\Forms\Integrations\\' . $integration['file_name']
'App\Integrations\Handlers\\' . $integration['file_name']
)) {
$this->integrationClassName = 'App\Service\Forms\Integrations\\' . $integration['file_name'];
$this->integrationClassName = 'App\Integrations\Handlers\\' . $integration['file_name'];
$this->loadIntegrationRules();
return;
}
@@ -40,9 +40,13 @@ class FormIntegrationsRequest extends FormRequest
{
return array_merge([
'integration_id' => ['required', Rule::in(array_keys(FormIntegration::getAllIntegrations()))],
'oauth_id' => [
$this->isOAuthRequired() ? 'required' : 'nullable',
Rule::exists('oauth_providers', 'id')
],
'settings' => 'present|array',
'status' => 'required|boolean',
'logic' => [new IntegrationLogicRule()]
'logic' => [new IntegrationLogicRule()],
], $this->integrationRules);
}
@@ -53,16 +57,24 @@ class FormIntegrationsRequest extends FormRequest
*/
public function attributes()
{
$attributes = $this->integrationClassName::getValidationAttributes();
$fields = [];
foreach ($this->rules() as $key => $value) {
$fields[$key] = Str::of($key)
$fields[$key] = $attributes[$key] ?? Str::of($key)
->replace('settings.', '')
->headline();
->headline()
->toString();
}
return $fields;
}
protected function isOAuthRequired(): bool
{
return $this->integrationClassName::isOAuthRequired();
}
private function loadIntegrationRules()
{
foreach ($this->integrationClassName::getValidationRules() as $key => $value) {
@@ -78,7 +90,8 @@ class FormIntegrationsRequest extends FormRequest
)) ? FormIntegration::STATUS_ACTIVE : FormIntegration::STATUS_INACTIVE,
'integration_id' => $this->validated('integration_id'),
'data' => $this->validated('settings') ?? [],
'logic' => $this->validated('logic') ?? []
'logic' => $this->validated('logic') ?? [],
'oauth_id' => $this->validated('oauth_id'),
]);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @property \App\Models\Integration\FormIntegration $resource
*/
class FormIntegrationResource extends JsonResource
{
public function toArray($request)
{
return [
...parent::toArray($request),
'provider' => OAuthProviderResource::make($this->resource->provider),
];
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
/**
* @property \App\Models\OAuthProvider $resource
*/
class OAuthProviderResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
$userId = Auth::id();
$intention = cache()->get("oauth-intention:{$userId}");
return [
'id' => $this->resource->id,
'provider' => $this->resource->provider,
'name' => $this->resource->name,
'email' => $this->resource->email,
'intention' => $intention,
'user' => $this->whenLoaded(
'user',
fn () => OAuthProviderUserResource::make($this->resource->user),
null,
),
];
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
/**
* @property \App\Models\User $resource
*/
class OAuthProviderUserResource extends JsonResource
{
public function toArray($request)
{
return [
'name' => $this->resource->name,
'email' => $this->resource->email,
];
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace App\Integrations\Data;
use Spatie\LaravelData\Data;
class SpreadsheetData extends Data
{
public function __construct(
public string $url = '',
public string $spreadsheet_id = '',
public ?array $columns = []
) {
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Integrations\Google;
use App\Integrations\Google\Sheets\SpreadsheetManager;
use App\Models\Integration\FormIntegration;
use Google\Client as Client;
class Google
{
protected Client $client;
protected ?string $token;
protected ?string $refreshToken;
public function __construct(
protected FormIntegration $formIntegration
) {
$this->client = new Client();
$this->client->setClientId(config('services.google.client_id'));
$this->client->setClientSecret(config('services.google.client_secret'));
$this->client->setAccessToken([
'access_token' => $this->formIntegration->provider->access_token,
'created' => $this->formIntegration->provider->updated_at->getTimestamp(),
'expires_in' => 3600,
]);
}
public function getClient(): Client
{
if($this->client->isAccessTokenExpired()) {
$this->refreshToken();
}
return $this->client;
}
public function refreshToken(): static
{
$this->client->refreshToken($this->formIntegration->provider->refresh_token);
$token = $this->client->getAccessToken();
$this->formIntegration->provider->update([
'access_token' => $token['access_token'],
'refresh_token' => $token['refresh_token'],
]);
return $this;
}
public function sheets(): SpreadsheetManager
{
return new SpreadsheetManager($this, $this->formIntegration);
}
}

View File

@@ -0,0 +1,189 @@
<?php
namespace App\Integrations\Google\Sheets;
use App\Integrations\Data\SpreadsheetData;
use App\Integrations\Google\Google;
use App\Models\Forms\Form;
use App\Models\Integration\FormIntegration;
use App\Service\Forms\FormSubmissionFormatter;
use Google\Service\Sheets;
use Google\Service\Sheets\BatchUpdateValuesRequest;
use Google\Service\Sheets\Spreadsheet;
use Google\Service\Sheets\ValueRange;
use Illuminate\Support\Arr;
class SpreadsheetManager
{
protected Sheets $driver;
protected SpreadsheetData $data;
public function __construct(
protected Google $google,
protected FormIntegration $integration
) {
$this->driver = new Sheets($google->getClient());
$this->data = empty($this->integration->data)
? new SpreadsheetData()
: new SpreadsheetData(
url: $this->integration->data->url,
spreadsheet_id: $this->integration->data->spreadsheet_id,
columns: array_map(
fn ($column) => (array) $column,
$this->integration->data->columns
)
);
}
protected function convertToArray(mixed $object): array
{
return is_scalar($object) || is_null($object)
? $object
: $this->convertToArray((array) $object);
}
public function get(string $id): Spreadsheet
{
$spreadsheet = $this->driver
->spreadsheets
->get($id);
return $spreadsheet;
}
public function create(Form $form): Spreadsheet
{
$body = new Spreadsheet([
'properties' => [
'title' => $form->title
]
]);
$spreadsheet = $this->driver->spreadsheets->create($body);
$this->data->url = $spreadsheet->spreadsheetUrl;
$this->data->spreadsheet_id = $spreadsheet->spreadsheetId;
$this->data->columns = [];
$this->updateHeaders($spreadsheet->spreadsheetId);
return $spreadsheet;
}
public function buildColumns(): array
{
$properties = $this->integration->form->properties;
foreach ($properties as $property) {
$key = Arr::first(
array_keys($this->data->columns),
fn (int $key) => $this->data->columns[$key]['id'] === $property['id']
);
$column = Arr::only($property, ['id', 'name']);
if (!is_null($key)) {
$this->data->columns[$key] = $column;
} else {
$this->data->columns[] = $column;
}
}
$this->integration->update([
'data' => $this->data,
]);
return $this->data->columns;
}
public function updateHeaders(string $id): static
{
$columns = $this->buildColumns();
$headers = array_map(
fn ($column) => $column['name'],
$columns
);
return $this->setHeaders($id, $headers);
}
protected function setHeaders(string $id, array $headers): static
{
$valueRange = new ValueRange([
'values' => [$headers],
]);
$valueRange->setRange(
$this->buildRange($headers)
);
$body = new BatchUpdateValuesRequest([
'valueInputOption' => 'RAW',
'data' => [$valueRange]
]);
$this->driver
->spreadsheets_values
->batchUpdate($id, $body);
return $this;
}
public function buildRow(array $submissionData): array
{
$formatter = (new FormSubmissionFormatter($this->integration->form, $submissionData))->outputStringsOnly();
$fields = $formatter->getFieldsWithValue();
return collect($this->data->columns)
->map(function (array $column) use ($fields) {
$field = Arr::first($fields, fn ($field) => $field['id'] === $column['id']);
return $field ? $field['value'] : '';
})
->toArray();
}
public function submit(array $submissionData): static
{
$this->updateHeaders($this->data->spreadsheet_id);
$row = $this->buildRow($submissionData);
$this->addRow(
$this->data->spreadsheet_id,
$row
);
return $this;
}
public function addRow(string $id, array $values): static
{
$valueRange = new ValueRange([
'values' => [$values],
]);
$params = [
'valueInputOption' => 'RAW',
];
$this->driver
->spreadsheets_values
->append(
$id,
$this->buildRange($values),
$valueRange,
$params
);
return $this;
}
protected function buildRange(array $values): string
{
return "A1:" . chr(64 + count($values)) . "1";
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Service\Forms\Integrations;
namespace App\Integrations\Handlers;
use App\Models\Integration\FormIntegration;
use App\Events\Forms\FormSubmitted;
@@ -96,6 +96,11 @@ abstract class AbstractIntegrationHandler
}
}
public function created(): void
{
//
}
/**
* Default handle. Can be changed in child classes.
*/
@@ -110,6 +115,16 @@ abstract class AbstractIntegrationHandler
abstract public static function getValidationRules(): array;
public static function isOAuthRequired(): bool
{
return false;
}
public static function getValidationAttributes(): array
{
return [];
}
public static function formatData(array $data): array
{
return $data;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Service\Forms\Integrations;
namespace App\Integrations\Handlers;
use App\Service\Forms\FormSubmissionFormatter;
use Illuminate\Support\Arr;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Service\Forms\Integrations;
namespace App\Integrations\Handlers;
use App\Rules\OneEmailPerLine;
use Illuminate\Support\Facades\Log;

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Integrations\Handlers\Events;
use App\Models\Integration\FormIntegration;
class AbstractIntegrationCreated
{
public function __construct(
protected FormIntegration $formIntegration
) {
}
public function handle(): void
{
//
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace App\Integrations\Handlers\Events;
use App\Integrations\Google\Google;
use App\Models\Integration\FormIntegration;
class GoogleSheetsIntegrationCreated extends AbstractIntegrationCreated
{
protected Google $client;
public function __construct(
protected FormIntegration $formIntegration
) {
parent::__construct($formIntegration);
$this->client = new Google($formIntegration);
}
public function handle(): void
{
$this->client->sheets()
->create($this->formIntegration->form);
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace App\Integrations\Handlers;
use App\Events\Forms\FormSubmitted;
use App\Integrations\Google\Google;
use App\Models\Integration\FormIntegration;
use Exception;
use Illuminate\Support\Facades\Log;
class GoogleSheetsIntegration extends AbstractIntegrationHandler
{
protected Google $client;
public function __construct(
protected FormSubmitted $event,
protected FormIntegration $formIntegration,
protected array $integration
) {
parent::__construct($event, $formIntegration, $integration);
$this->client = new Google($formIntegration);
}
public static function getValidationRules(): array
{
return [
];
}
public static function isOAuthRequired(): bool
{
return true;
}
public static function getValidationAttributes(): array
{
return [
'oauth_id' => 'Google Account',
];
}
public function handle(): void
{
if (!$this->shouldRun()) {
return;
}
Log::debug('Creating Google Spreadsheet record', [
'spreadsheet_id' => $this->getSpreadsheetId(),
'form_id' => $this->form->id,
'form_slug' => $this->form->slug,
]);
$this->client->sheets()->submit($this->submissionData);
}
protected function getSpreadsheetId(): string
{
if(!isset($this->integrationData->spreadsheet_id)) {
throw new Exception('The spreadsheed is not instantiated');
}
return $this->integrationData->spreadsheet_id;
}
protected function shouldRun(): bool
{
return parent::shouldRun() && $this->formIntegration->oauth_id && $this->getSpreadsheetId();
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Service\Forms\Integrations;
namespace App\Integrations\Handlers;
use App\Service\Forms\FormSubmissionFormatter;
use Illuminate\Support\Arr;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Service\Forms\Integrations;
namespace App\Integrations\Handlers;
use App\Mail\Forms\SubmissionConfirmationMail;
use Illuminate\Support\Facades\Mail;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Service\Forms\Integrations;
namespace App\Integrations\Handlers;
class WebhookIntegration extends AbstractIntegrationHandler
{

View File

@@ -0,0 +1,11 @@
<?php
namespace App\Integrations\OAuth\Drivers\Contracts;
use Laravel\Socialite\Contracts\User;
interface OAuthDriver
{
public function getRedirectUrl(): string;
public function getUser(): User;
}

View File

@@ -0,0 +1,39 @@
<?php
namespace App\Integrations\OAuth\Drivers;
use App\Integrations\OAuth\Drivers\Contracts\OAuthDriver;
use Google\Service\Sheets;
use Laravel\Socialite\Contracts\User;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\GoogleProvider;
class OAuthGoogleDriver implements OAuthDriver
{
protected GoogleProvider $provider;
public function __construct()
{
$this->provider = Socialite::driver('google');
}
public function getRedirectUrl(): string
{
return $this->provider
->scopes([Sheets::DRIVE_FILE])
->stateless()
->with([
'access_type' => 'offline',
'prompt' => 'consent select_account'
])
->redirect()
->getTargetUrl();
}
public function getUser(): User
{
return $this->provider
->stateless()
->user();
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace App\Integrations\OAuth;
use App\Integrations\OAuth\Drivers\Contracts\OAuthDriver;
use App\Integrations\OAuth\Drivers\OAuthGoogleDriver;
enum OAuthProviderService: string
{
case Google = 'google';
public function getDriver(): OAuthDriver
{
return match($this) {
self::Google => new OAuthGoogleDriver()
};
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Listeners\Forms;
use App\Events\Models\FormIntegrationCreated;
use App\Models\Integration\FormIntegration;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class FormIntegrationCreatedHandler implements ShouldQueue
{
use InteractsWithQueue;
public function handle(FormIntegrationCreated $event)
{
$integration = FormIntegration::getIntegration($event->formIntegration->integration_id);
if(!$integration) {
return;
}
if(!isset($integration['file_name'])) {
return;
}
$className = 'App\Integrations\Handlers\Events\\' . $integration['file_name'] . 'Created';
if(!class_exists($className)) {
return;
}
/** @var \App\Integrations\Handlers\Events\AbstractIntegrationCreated $eventHandler */
$eventHandler = new $className($event->formIntegration);
$eventHandler->handle();
}
}

View File

@@ -4,7 +4,7 @@ namespace App\Listeners\Forms;
use App\Events\Forms\FormSubmitted;
use App\Models\Integration\FormIntegration;
use App\Service\Forms\Integrations\AbstractIntegrationHandler;
use App\Integrations\Handlers\AbstractIntegrationHandler;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
@@ -35,9 +35,9 @@ class NotifyFormSubmission implements ShouldQueue
): AbstractIntegrationHandler {
$integration = FormIntegration::getIntegration($formIntegration->integration_id);
if ($integration && isset($integration['file_name']) && class_exists(
'App\Service\Forms\Integrations\\' . $integration['file_name']
'App\Integrations\Handlers\\' . $integration['file_name']
)) {
$className = 'App\Service\Forms\Integrations\\' . $integration['file_name'];
$className = 'App\Integrations\Handlers\\' . $integration['file_name'];
return new $className($event, $formIntegration, $integration);
}
throw new \Exception('Unknown Integration!');

View File

@@ -2,7 +2,9 @@
namespace App\Models\Integration;
use App\Events\Models\FormIntegrationCreated;
use App\Models\Forms\Form;
use App\Models\OAuthProvider;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -26,6 +28,10 @@ class FormIntegration extends Model
'logic' => 'object'
];
protected $dispatchesEvents = [
'created' => FormIntegrationCreated::class,
];
/**
* Relationships
*/
@@ -39,6 +45,11 @@ class FormIntegration extends Model
return $this->hasMany(FormIntegrationsEvent::class, 'integration_id');
}
public function provider()
{
return $this->belongsTo(OAuthProvider::class, 'oauth_id');
}
public static function getAllIntegrations()
{
return json_decode(file_get_contents(resource_path('data/forms/integrations.json')), true);

View File

@@ -2,10 +2,14 @@
namespace App\Models;
use App\Integrations\OAuth\OAuthProviderService;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class OAuthProvider extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
@@ -29,6 +33,11 @@ class OAuthProvider extends Model
'access_token', 'refresh_token',
];
protected $casts = [
'provider' => OAuthProviderService::class,
'token_expires_at' => 'datetime',
];
/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/

View File

@@ -187,4 +187,5 @@ class Workspace extends Model implements CachableAttributes
{
return $this->hasMany(Form::class);
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace App\Policies;
use App\Models\OAuthProvider;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class OAuthProviderPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @return mixed
*/
public function viewAny(User $user)
{
return true;
}
/**
* Determine whether the user can view the model.
*
* @return mixed
*/
public function view(User $user, OAuthProvider $provider)
{
return $provider->user()->is($user);
}
/**
* Determine whether the user can create models.
*
* @return mixed
*/
public function create(User $user)
{
return true;
}
/**
* Determine whether the user can update the model.
*
* @return mixed
*/
public function update(User $user, OAuthProvider $provider)
{
return $provider->user()->is($user);
}
/**
* Determine whether the user can delete the model.
*
* @return mixed
*/
public function delete(User $user, OAuthProvider $provider)
{
return $provider->user()->is($user);
}
/**
* Determine whether the user can restore the model.
*
* @return mixed
*/
public function restore(User $user, OAuthProvider $provider)
{
return $provider->user()->is($user);
}
/**
* Determine whether the user can permanently delete the model.
*
* @return mixed
*/
public function forceDelete(User $user, OAuthProvider $provider)
{
return $provider->user()->is($user);
}
}

View File

@@ -4,10 +4,12 @@ namespace App\Providers;
use App\Models\Forms\Form;
use App\Models\Integration\FormZapierWebhook;
use App\Models\OAuthProvider;
use App\Models\Template;
use App\Models\Workspace;
use App\Policies\FormPolicy;
use App\Policies\Integration\FormZapierWebhookPolicy;
use App\Policies\OAuthProviderPolicy;
use App\Policies\TemplatePolicy;
use App\Policies\WorkspacePolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
@@ -24,6 +26,7 @@ class AuthServiceProvider extends ServiceProvider
Workspace::class => WorkspacePolicy::class,
FormZapierWebhook::class => FormZapierWebhookPolicy::class,
Template::class => TemplatePolicy::class,
OAuthProvider::class => OAuthProviderPolicy::class,
];
/**

View File

@@ -4,9 +4,11 @@ namespace App\Providers;
use App\Events\Forms\FormSubmitted;
use App\Events\Models\FormCreated;
use App\Events\Models\FormIntegrationCreated;
use App\Events\Models\FormIntegrationsEventCreated;
use App\Events\SubscriptionCreated;
use App\Listeners\Forms\FormCreationConfirmation;
use App\Listeners\Forms\FormIntegrationCreatedHandler;
use App\Listeners\Forms\FormIntegrationsEventListener;
use App\Listeners\Forms\NotifyFormSubmission;
use App\Listeners\HandleSubscriptionCreated;
@@ -31,6 +33,9 @@ class EventServiceProvider extends ServiceProvider
FormSubmitted::class => [
NotifyFormSubmission::class
],
FormIntegrationCreated::class => [
FormIntegrationCreatedHandler::class,
],
FormIntegrationsEventCreated::class => [
FormIntegrationsEventListener::class,
],

View File

@@ -1,11 +1,13 @@
<?php
function front_url($path = '')
{
$baseUrl = config('app.front_url');
if (! $baseUrl) {
return $path;
}
if(!function_exists('front_url')) {
function front_url($path = '')
{
$baseUrl = config('app.front_url');
if (! $baseUrl) {
return $path;
}
return rtrim($baseUrl, '/').'/'.ltrim($path, '/');
return rtrim($baseUrl, '/').'/'.ltrim($path, '/');
}
}