diff --git a/.phpactor.json b/.phpactor.json
new file mode 100644
index 00000000..969af7a8
--- /dev/null
+++ b/.phpactor.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "/Applications/Tinkerwell.app/Contents/Resources/phpactor/phpactor.schema.json",
+ "language_server_phpstan.enabled": false
+}
\ No newline at end of file
diff --git a/app/Events/Models/FormIntegrationCreated.php b/app/Events/Models/FormIntegrationCreated.php
new file mode 100644
index 00000000..34791c48
--- /dev/null
+++ b/app/Events/Models/FormIntegrationCreated.php
@@ -0,0 +1,20 @@
+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)
]);
}
diff --git a/app/Http/Controllers/OAuth/OAuthProviderController.php b/app/Http/Controllers/OAuth/OAuthProviderController.php
new file mode 100644
index 00000000..fadcbe0c
--- /dev/null
+++ b/app/Http/Controllers/OAuth/OAuthProviderController.php
@@ -0,0 +1,64 @@
+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();
+ }
+}
diff --git a/app/Http/Requests/Integration/FormIntegrationsRequest.php b/app/Http/Requests/Integration/FormIntegrationsRequest.php
index 7174837d..128a4c0e 100644
--- a/app/Http/Requests/Integration/FormIntegrationsRequest.php
+++ b/app/Http/Requests/Integration/FormIntegrationsRequest.php
@@ -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'),
]);
}
}
diff --git a/app/Http/Resources/FormIntegrationResource.php b/app/Http/Resources/FormIntegrationResource.php
new file mode 100644
index 00000000..02bd90d3
--- /dev/null
+++ b/app/Http/Resources/FormIntegrationResource.php
@@ -0,0 +1,19 @@
+ OAuthProviderResource::make($this->resource->provider),
+ ];
+ }
+}
diff --git a/app/Http/Resources/OAuthProviderResource.php b/app/Http/Resources/OAuthProviderResource.php
new file mode 100644
index 00000000..a7fa2b47
--- /dev/null
+++ b/app/Http/Resources/OAuthProviderResource.php
@@ -0,0 +1,37 @@
+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,
+ ),
+ ];
+ }
+}
diff --git a/app/Http/Resources/OAuthProviderUserResource.php b/app/Http/Resources/OAuthProviderUserResource.php
new file mode 100644
index 00000000..05cfa792
--- /dev/null
+++ b/app/Http/Resources/OAuthProviderUserResource.php
@@ -0,0 +1,19 @@
+ $this->resource->name,
+ 'email' => $this->resource->email,
+ ];
+ }
+}
diff --git a/app/Integrations/Data/SpreadsheetData.php b/app/Integrations/Data/SpreadsheetData.php
new file mode 100644
index 00000000..3ea9faba
--- /dev/null
+++ b/app/Integrations/Data/SpreadsheetData.php
@@ -0,0 +1,15 @@
+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);
+ }
+}
diff --git a/app/Integrations/Google/Sheets/SpreadsheetManager.php b/app/Integrations/Google/Sheets/SpreadsheetManager.php
new file mode 100644
index 00000000..6c7a9928
--- /dev/null
+++ b/app/Integrations/Google/Sheets/SpreadsheetManager.php
@@ -0,0 +1,189 @@
+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";
+ }
+}
diff --git a/app/Service/Forms/Integrations/AbstractIntegrationHandler.php b/app/Integrations/Handlers/AbstractIntegrationHandler.php
similarity index 92%
rename from app/Service/Forms/Integrations/AbstractIntegrationHandler.php
rename to app/Integrations/Handlers/AbstractIntegrationHandler.php
index 344b63e2..b637e99c 100644
--- a/app/Service/Forms/Integrations/AbstractIntegrationHandler.php
+++ b/app/Integrations/Handlers/AbstractIntegrationHandler.php
@@ -1,6 +1,6 @@
client = new Google($formIntegration);
+ }
+
+ public function handle(): void
+ {
+ $this->client->sheets()
+ ->create($this->formIntegration->form);
+ }
+}
diff --git a/app/Integrations/Handlers/GoogleSheetsIntegration.php b/app/Integrations/Handlers/GoogleSheetsIntegration.php
new file mode 100644
index 00000000..c04f4c7a
--- /dev/null
+++ b/app/Integrations/Handlers/GoogleSheetsIntegration.php
@@ -0,0 +1,72 @@
+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();
+ }
+}
diff --git a/app/Service/Forms/Integrations/SlackIntegration.php b/app/Integrations/Handlers/SlackIntegration.php
similarity index 98%
rename from app/Service/Forms/Integrations/SlackIntegration.php
rename to app/Integrations/Handlers/SlackIntegration.php
index 926d7829..f0673f23 100644
--- a/app/Service/Forms/Integrations/SlackIntegration.php
+++ b/app/Integrations/Handlers/SlackIntegration.php
@@ -1,6 +1,6 @@
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();
+ }
+}
diff --git a/app/Integrations/OAuth/OAuthProviderService.php b/app/Integrations/OAuth/OAuthProviderService.php
new file mode 100644
index 00000000..2ef4bd7f
--- /dev/null
+++ b/app/Integrations/OAuth/OAuthProviderService.php
@@ -0,0 +1,18 @@
+ new OAuthGoogleDriver()
+ };
+ }
+}
diff --git a/app/Listeners/Forms/FormIntegrationCreatedHandler.php b/app/Listeners/Forms/FormIntegrationCreatedHandler.php
new file mode 100644
index 00000000..f0ee9e3a
--- /dev/null
+++ b/app/Listeners/Forms/FormIntegrationCreatedHandler.php
@@ -0,0 +1,37 @@
+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();
+ }
+}
diff --git a/app/Listeners/Forms/NotifyFormSubmission.php b/app/Listeners/Forms/NotifyFormSubmission.php
index a88fb859..e4774746 100644
--- a/app/Listeners/Forms/NotifyFormSubmission.php
+++ b/app/Listeners/Forms/NotifyFormSubmission.php
@@ -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!');
diff --git a/app/Models/Integration/FormIntegration.php b/app/Models/Integration/FormIntegration.php
index 37cfdafa..97deeefe 100644
--- a/app/Models/Integration/FormIntegration.php
+++ b/app/Models/Integration/FormIntegration.php
@@ -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);
diff --git a/app/Models/OAuthProvider.php b/app/Models/OAuthProvider.php
index 7c6139cf..4f780fe1 100644
--- a/app/Models/OAuthProvider.php
+++ b/app/Models/OAuthProvider.php
@@ -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
*/
diff --git a/app/Models/Workspace.php b/app/Models/Workspace.php
index 89c24ced..d2e39d9e 100644
--- a/app/Models/Workspace.php
+++ b/app/Models/Workspace.php
@@ -187,4 +187,5 @@ class Workspace extends Model implements CachableAttributes
{
return $this->hasMany(Form::class);
}
+
}
diff --git a/app/Policies/OAuthProviderPolicy.php b/app/Policies/OAuthProviderPolicy.php
new file mode 100644
index 00000000..e3b0dce9
--- /dev/null
+++ b/app/Policies/OAuthProviderPolicy.php
@@ -0,0 +1,82 @@
+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);
+ }
+}
diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php
index afd477c8..ee8025ee 100644
--- a/app/Providers/AuthServiceProvider.php
+++ b/app/Providers/AuthServiceProvider.php
@@ -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,
];
/**
diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php
index ab6ba28d..81817985 100644
--- a/app/Providers/EventServiceProvider.php
+++ b/app/Providers/EventServiceProvider.php
@@ -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,
],
diff --git a/app/helpers.php b/app/helpers.php
index b8f030e7..e8cc08a9 100644
--- a/app/helpers.php
+++ b/app/helpers.php
@@ -1,11 +1,13 @@
- Coming Soon...
+
+
+
+
+ Add an entry to spreadsheets on each form submission.
+
+ Click here
+
+ to connect another account.
+
+
+
+
+
+
+ Connect Google account
+
@@ -19,4 +51,11 @@ const props = defineProps({
integrationData: { type: Object, required: true },
formIntegrationId: { type: Number, required: false, default: null },
})
+
+const providersStore = useOAuthProvidersStore()
+const providers = computed(() => providersStore.getAll.filter(provider => provider.provider == 'google'))
+
+function connect() {
+ providersStore.connect('google', true)
+}
diff --git a/client/components/open/integrations/components/GoogleSheetsIntegrationActions.vue b/client/components/open/integrations/components/GoogleSheetsIntegrationActions.vue
new file mode 100644
index 00000000..63d503c9
--- /dev/null
+++ b/client/components/open/integrations/components/GoogleSheetsIntegrationActions.vue
@@ -0,0 +1,28 @@
+
+
+
+
+ {{ integration.provider.user.name }}
+
+
+ {{ integration.provider.user.email }}
+
+
+
+
+
+ Open spreadsheet
+
+
+
+
+
+
diff --git a/client/components/open/integrations/components/IntegrationCard.vue b/client/components/open/integrations/components/IntegrationCard.vue
index 025ed197..efd4ac4f 100644
--- a/client/components/open/integrations/components/IntegrationCard.vue
+++ b/client/components/open/integrations/components/IntegrationCard.vue
@@ -2,7 +2,7 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Edit
-
-
-
- Past Events
-
-
-
+
- Delete Integration
-
-
{
+ if(integrationTypeInfo.value.actions_file_name) {
+ return resolveComponent(integrationTypeInfo.value.actions_file_name)
+ }
+
+ return null
+})
+
const deleteFormIntegration = (integrationid) => {
alert.confirm("Do you really want to delete this form integration?", () => {
opnFetch(
diff --git a/client/components/open/integrations/components/IntegrationModal.vue b/client/components/open/integrations/components/IntegrationModal.vue
index 61d2a5c5..e3880a10 100644
--- a/client/components/open/integrations/components/IntegrationModal.vue
+++ b/client/components/open/integrations/components/IntegrationModal.vue
@@ -51,6 +51,7 @@ const props = defineProps({
integrationKey: { type: String, required: true },
integration: { type: Object, required: true },
formIntegrationId: { type: Number, required: false, default: null },
+ providers: { type: Array, required: true }
})
const alert = useAlert()
@@ -92,6 +93,7 @@ const initIntegrationData = () => {
? formIntegration.value.logic
: null
: null,
+ oauth_id: formIntegration.value?.oauth_id ?? null,
})
}
initIntegrationData()
diff --git a/client/components/settings/ProviderCard.vue b/client/components/settings/ProviderCard.vue
new file mode 100644
index 00000000..471e50b9
--- /dev/null
+++ b/client/components/settings/ProviderCard.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+ {{ provider.name }}
+
+
+
+ {{ provider.email }}
+
+
+
+
+
+
+
+
+
diff --git a/client/components/settings/ProviderModal.vue b/client/components/settings/ProviderModal.vue
new file mode 100644
index 00000000..4ca6ac5f
--- /dev/null
+++ b/client/components/settings/ProviderModal.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+ Connect account
+
+
+
+
+
+
+
+
+ {{ service.title }}
+
+
+
+
+
+
+
+
diff --git a/client/data/forms/integrations.json b/client/data/forms/integrations.json
index cb2812db..e193ad15 100644
--- a/client/data/forms/integrations.json
+++ b/client/data/forms/integrations.json
@@ -48,7 +48,7 @@
"icon": "mdi:google-spreadsheet",
"section_name": "Databases",
"file_name": "GoogleSheetsIntegration",
- "is_pro": true,
- "coming_soon": true
+ "actions_file_name": "GoogleSheetsIntegrationActions",
+ "is_pro": false
}
}
diff --git a/client/pages/forms/[slug]/show/integrations/index.vue b/client/pages/forms/[slug]/show/integrations/index.vue
index d9f0f07e..b5f8f2f9 100644
--- a/client/pages/forms/[slug]/show/integrations/index.vue
+++ b/client/pages/forms/[slug]/show/integrations/index.vue
@@ -84,6 +84,8 @@ useOpnSeoMeta({
const alert = useAlert()
+const oAuthProvidersStore = useOAuthProvidersStore()
+
const formIntegrationsStore = useFormIntegrationsStore()
const integrationsLoading = computed(() => formIntegrationsStore.loading)
const integrations = computed(
@@ -102,6 +104,7 @@ const selectedIntegration = ref(null)
onMounted(() => {
formIntegrationsStore.fetchFormIntegrations(props.form.id)
+ oAuthProvidersStore.fetchOAuthProviders(props.form.workspace_id)
})
const openIntegrationModal = (itemKey) => {
diff --git a/client/pages/settings.vue b/client/pages/settings.vue
index 7b00cda8..75175640 100644
--- a/client/pages/settings.vue
+++ b/client/pages/settings.vue
@@ -66,6 +66,10 @@ const tabsList = computed(() => {
name: "Workspace Settings",
route: "settings-workspace",
},
+ {
+ name: "Connections",
+ route: "settings-connections",
+ },
{
name: "Password",
route: "settings-password",
diff --git a/client/pages/settings/connections.vue b/client/pages/settings/connections.vue
new file mode 100644
index 00000000..82a6d23e
--- /dev/null
+++ b/client/pages/settings/connections.vue
@@ -0,0 +1,116 @@
+
+
+
+
+
+ Connections
+
+ Manage your external connections.
+
+
+
+
+
+ Connect new account
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/stores/oauth_providers.js b/client/stores/oauth_providers.js
new file mode 100644
index 00000000..126107a8
--- /dev/null
+++ b/client/stores/oauth_providers.js
@@ -0,0 +1,57 @@
+import { defineStore } from "pinia"
+import { useContentStore } from "~/composables/stores/useContentStore.js"
+
+export const providersEndpoint = "/open/providers"
+
+export const useOAuthProvidersStore = defineStore("oauth_providers", () => {
+ const contentStore = useContentStore()
+ const alert = useAlert()
+
+ const fetchOAuthProviders = () => {
+ contentStore.resetState()
+ contentStore.startLoading()
+
+ return opnFetch(providersEndpoint).then(
+ (data) => {
+ contentStore.save(data)
+ contentStore.stopLoading()
+ },
+ )
+ }
+
+ const connect = (service, redirect = false) => {
+ contentStore.resetState()
+ contentStore.startLoading()
+
+ const intention = new URL(window.location.href).pathname
+
+ opnFetch(`/settings/providers/connect/${service}`, {
+ method: 'POST',
+ body: {
+ ...redirect ? { intention } : {},
+ }
+ })
+ .then((data) => {
+ window.location.href = data.url
+ })
+ .catch((error) => {
+ try {
+ alert.error(error.data.message)
+ } catch (e) {
+ alert.error("An error occurred while connecting an account")
+ }
+ })
+ .finally(() => {
+ contentStore.stopLoading()
+ })
+ }
+
+ const providers = computed(() => contentStore.getAll.value)
+
+ return {
+ ...contentStore,
+ fetchOAuthProviders,
+ providers,
+ connect
+ }
+})
diff --git a/composer.json b/composer.json
index 3ef3e48b..510fded1 100644
--- a/composer.json
+++ b/composer.json
@@ -17,6 +17,7 @@
"aws/aws-sdk-php": "^3.183",
"doctrine/dbal": "^3.4",
"giggsey/libphonenumber-for-php": "^8.13",
+ "google/apiclient": "^2.15.0",
"guzzlehttp/guzzle": "^7.0.1",
"jhumanj/laravel-model-stats": "^0.4.0",
"laravel/cashier": "^13.4",
@@ -33,6 +34,7 @@
"openai-php/client": "^0.6.4",
"propaganistas/laravel-disposable-email": "^2.2",
"sentry/sentry-laravel": "^2.11.0",
+ "spatie/laravel-data": "^3.12",
"spatie/laravel-sitemap": "^6.0",
"spatie/laravel-sluggable": "^3.0",
"stevebauman/purify": "^v6.2.0",
diff --git a/composer.lock b/composer.lock
index d120247d..b03e8437 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,867 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "6d742b79739f9d1ca839f2b9c52aaa8f",
+ "content-hash": "8616475a141747e8021db6c7b53c2062",
"packages": [
+ {
+ "name": "amphp/amp",
+ "version": "v3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/amp.git",
+ "reference": "138801fb68cfc9c329da8a7b39d01ce7291ee4b0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/amp/zipball/138801fb68cfc9c329da8a7b39d01ce7291ee4b0",
+ "reference": "138801fb68cfc9c329da8a7b39d01ce7291ee4b0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "5.23.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php",
+ "src/Future/functions.php",
+ "src/Internal/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Bob Weinand",
+ "email": "bobwei9@hotmail.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@php.net"
+ }
+ ],
+ "description": "A non-blocking concurrency framework for PHP applications.",
+ "homepage": "https://amphp.org/amp",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "awaitable",
+ "concurrency",
+ "event",
+ "event-loop",
+ "future",
+ "non-blocking",
+ "promise"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/amp/issues",
+ "source": "https://github.com/amphp/amp/tree/v3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-05-10T21:37:46+00:00"
+ },
+ {
+ "name": "amphp/byte-stream",
+ "version": "v2.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/byte-stream.git",
+ "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/byte-stream/zipball/daa00f2efdbd71565bf64ffefa89e37542addf93",
+ "reference": "daa00f2efdbd71565bf64ffefa89e37542addf93",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/parser": "^1.1",
+ "amphp/pipeline": "^1",
+ "amphp/serialization": "^1",
+ "amphp/sync": "^2",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2.3"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "5.22.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php",
+ "src/Internal/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\ByteStream\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "A stream abstraction to make working with non-blocking I/O simple.",
+ "homepage": "https://amphp.org/byte-stream",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "io",
+ "non-blocking",
+ "stream"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/byte-stream/issues",
+ "source": "https://github.com/amphp/byte-stream/tree/v2.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-02-17T04:49:38+00:00"
+ },
+ {
+ "name": "amphp/cache",
+ "version": "v2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/cache.git",
+ "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/cache/zipball/46912e387e6aa94933b61ea1ead9cf7540b7797c",
+ "reference": "46912e387e6aa94933b61ea1ead9cf7540b7797c",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/serialization": "^1",
+ "amphp/sync": "^2",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Amp\\Cache\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@php.net"
+ }
+ ],
+ "description": "A fiber-aware cache API based on Amp and Revolt.",
+ "homepage": "https://amphp.org/cache",
+ "support": {
+ "issues": "https://github.com/amphp/cache/issues",
+ "source": "https://github.com/amphp/cache/tree/v2.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-04-19T03:38:06+00:00"
+ },
+ {
+ "name": "amphp/dns",
+ "version": "v2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/dns.git",
+ "reference": "758266b0ea7470e2e42cd098493bc6d6c7100cf7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/dns/zipball/758266b0ea7470e2e42cd098493bc6d6c7100cf7",
+ "reference": "758266b0ea7470e2e42cd098493bc6d6c7100cf7",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/cache": "^2",
+ "amphp/parser": "^1",
+ "amphp/windows-registry": "^1.0.1",
+ "daverandom/libdns": "^2.0.2",
+ "ext-filter": "*",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "5.20"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Dns\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Chris Wright",
+ "email": "addr@daverandom.com"
+ },
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@php.net"
+ },
+ {
+ "name": "Bob Weinand",
+ "email": "bobwei9@hotmail.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ }
+ ],
+ "description": "Async DNS resolution for Amp.",
+ "homepage": "https://github.com/amphp/dns",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "client",
+ "dns",
+ "resolve"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/dns/issues",
+ "source": "https://github.com/amphp/dns/tree/v2.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-06-02T19:54:12+00:00"
+ },
+ {
+ "name": "amphp/parallel",
+ "version": "v2.2.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/parallel.git",
+ "reference": "73d293f1fc4df1bebc3c4fce1432e82dd7032238"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/parallel/zipball/73d293f1fc4df1bebc3c4fce1432e82dd7032238",
+ "reference": "73d293f1fc4df1bebc3c4fce1432e82dd7032238",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/cache": "^2",
+ "amphp/parser": "^1",
+ "amphp/pipeline": "^1",
+ "amphp/process": "^2",
+ "amphp/serialization": "^1",
+ "amphp/socket": "^2",
+ "amphp/sync": "^2",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.18"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Context/functions.php",
+ "src/Context/Internal/functions.php",
+ "src/Ipc/functions.php",
+ "src/Worker/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Parallel\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Stephen Coakley",
+ "email": "me@stephencoakley.com"
+ }
+ ],
+ "description": "Parallel processing component for Amp.",
+ "homepage": "https://github.com/amphp/parallel",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "concurrent",
+ "multi-processing",
+ "multi-threading"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/parallel/issues",
+ "source": "https://github.com/amphp/parallel/tree/v2.2.9"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-24T18:27:44+00:00"
+ },
+ {
+ "name": "amphp/parser",
+ "version": "v1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/parser.git",
+ "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/parser/zipball/3cf1f8b32a0171d4b1bed93d25617637a77cded7",
+ "reference": "3cf1f8b32a0171d4b1bed93d25617637a77cded7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Amp\\Parser\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "A generator parser to make streaming parsers simple.",
+ "homepage": "https://github.com/amphp/parser",
+ "keywords": [
+ "async",
+ "non-blocking",
+ "parser",
+ "stream"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/parser/issues",
+ "source": "https://github.com/amphp/parser/tree/v1.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-21T19:16:53+00:00"
+ },
+ {
+ "name": "amphp/pipeline",
+ "version": "v1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/pipeline.git",
+ "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/pipeline/zipball/f1c2ce35d27ae86ead018adb803eccca7421dd9b",
+ "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.18"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Amp\\Pipeline\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Asynchronous iterators and operators.",
+ "homepage": "https://amphp.org/pipeline",
+ "keywords": [
+ "amp",
+ "amphp",
+ "async",
+ "io",
+ "iterator",
+ "non-blocking"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/pipeline/issues",
+ "source": "https://github.com/amphp/pipeline/tree/v1.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-10T14:48:16+00:00"
+ },
+ {
+ "name": "amphp/process",
+ "version": "v2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/process.git",
+ "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/process/zipball/52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d",
+ "reference": "52e08c09dec7511d5fbc1fb00d3e4e79fc77d58d",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/sync": "^2",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.4"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Process\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bob Weinand",
+ "email": "bobwei9@hotmail.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "A fiber-aware process manager based on Amp and Revolt.",
+ "homepage": "https://amphp.org/process",
+ "support": {
+ "issues": "https://github.com/amphp/process/issues",
+ "source": "https://github.com/amphp/process/tree/v2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-04-19T03:13:44+00:00"
+ },
+ {
+ "name": "amphp/serialization",
+ "version": "v1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/serialization.git",
+ "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1",
+ "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "dev-master",
+ "phpunit/phpunit": "^9 || ^8 || ^7"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Serialization\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Serialization tools for IPC and data storage in PHP.",
+ "homepage": "https://github.com/amphp/serialization",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "serialization",
+ "serialize"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/serialization/issues",
+ "source": "https://github.com/amphp/serialization/tree/master"
+ },
+ "time": "2020-03-25T21:39:07+00:00"
+ },
+ {
+ "name": "amphp/socket",
+ "version": "v2.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/socket.git",
+ "reference": "58e0422221825b79681b72c50c47a930be7bf1e1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/socket/zipball/58e0422221825b79681b72c50c47a930be7bf1e1",
+ "reference": "58e0422221825b79681b72c50c47a930be7bf1e1",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/byte-stream": "^2",
+ "amphp/dns": "^2",
+ "ext-openssl": "*",
+ "kelunik/certificate": "^1.1",
+ "league/uri": "^6.5 | ^7",
+ "league/uri-interfaces": "^2.3 | ^7",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "amphp/process": "^2",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "5.20"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php",
+ "src/Internal/functions.php",
+ "src/SocketAddress/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Socket\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Daniel Lowrey",
+ "email": "rdlowrey@gmail.com"
+ },
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Non-blocking socket connection / server implementations based on Amp and Revolt.",
+ "homepage": "https://github.com/amphp/socket",
+ "keywords": [
+ "amp",
+ "async",
+ "encryption",
+ "non-blocking",
+ "sockets",
+ "tcp",
+ "tls"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/socket/issues",
+ "source": "https://github.com/amphp/socket/tree/v2.3.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-04-21T14:33:03+00:00"
+ },
+ {
+ "name": "amphp/sync",
+ "version": "v2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/sync.git",
+ "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/sync/zipball/375ef5b54a0d12c38e12728dde05a55e30f2fbec",
+ "reference": "375ef5b54a0d12c38e12728dde05a55e30f2fbec",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^3",
+ "amphp/pipeline": "^1",
+ "amphp/serialization": "^1",
+ "php": ">=8.1",
+ "revolt/event-loop": "^1 || ^0.2"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "amphp/phpunit-util": "^3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "5.23"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "Amp\\Sync\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ },
+ {
+ "name": "Stephen Coakley",
+ "email": "me@stephencoakley.com"
+ }
+ ],
+ "description": "Non-blocking synchronization primitives for PHP based on Amp and Revolt.",
+ "homepage": "https://github.com/amphp/sync",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "mutex",
+ "semaphore",
+ "synchronization"
+ ],
+ "support": {
+ "issues": "https://github.com/amphp/sync/issues",
+ "source": "https://github.com/amphp/sync/tree/v2.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-12T01:00:01+00:00"
+ },
+ {
+ "name": "amphp/windows-registry",
+ "version": "v1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/amphp/windows-registry.git",
+ "reference": "0d569e8f256cca974e3842b6e78b4e434bf98306"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/amphp/windows-registry/zipball/0d569e8f256cca974e3842b6e78b4e434bf98306",
+ "reference": "0d569e8f256cca974e3842b6e78b4e434bf98306",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/byte-stream": "^2",
+ "amphp/process": "^2",
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "psalm/phar": "^5.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Amp\\WindowsRegistry\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Windows Registry Reader.",
+ "support": {
+ "issues": "https://github.com/amphp/windows-registry/issues",
+ "source": "https://github.com/amphp/windows-registry/tree/v1.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/amphp",
+ "type": "github"
+ }
+ ],
+ "time": "2024-01-30T23:01:51+00:00"
+ },
{
"name": "aws/aws-crt-php",
"version": "v1.2.5",
@@ -426,6 +1285,50 @@
],
"time": "2023-08-31T09:50:34+00:00"
},
+ {
+ "name": "daverandom/libdns",
+ "version": "v2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/DaveRandom/LibDNS.git",
+ "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/DaveRandom/LibDNS/zipball/b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a",
+ "reference": "b84c94e8fe6b7ee4aecfe121bfe3b6177d303c8a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "php": ">=7.1"
+ },
+ "suggest": {
+ "ext-intl": "Required for IDN support"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "LibDNS\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "DNS protocol implementation written in pure PHP",
+ "keywords": [
+ "dns"
+ ],
+ "support": {
+ "issues": "https://github.com/DaveRandom/LibDNS/issues",
+ "source": "https://github.com/DaveRandom/LibDNS/tree/v2.1.0"
+ },
+ "time": "2024-04-12T12:12:48+00:00"
+ },
{
"name": "dflydev/dot-access-data",
"version": "v3.0.2",
@@ -1527,6 +2430,179 @@
},
"time": "2024-04-18T19:31:19+00:00"
},
+ {
+ "name": "google/apiclient",
+ "version": "v2.16.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/googleapis/google-api-php-client.git",
+ "reference": "017400f609c1fb71ab5ad824c50eabd4c3eaf779"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/googleapis/google-api-php-client/zipball/017400f609c1fb71ab5ad824c50eabd4c3eaf779",
+ "reference": "017400f609c1fb71ab5ad824c50eabd4c3eaf779",
+ "shasum": ""
+ },
+ "require": {
+ "firebase/php-jwt": "~6.0",
+ "google/apiclient-services": "~0.350",
+ "google/auth": "^1.37",
+ "guzzlehttp/guzzle": "^6.5.8||^7.4.5",
+ "guzzlehttp/psr7": "^1.9.1||^2.2.1",
+ "monolog/monolog": "^2.9||^3.0",
+ "php": "^7.4|^8.0",
+ "phpseclib/phpseclib": "^3.0.36"
+ },
+ "require-dev": {
+ "cache/filesystem-adapter": "^1.1",
+ "composer/composer": "^1.10.23",
+ "phpcompatibility/php-compatibility": "^9.2",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.5",
+ "squizlabs/php_codesniffer": "^3.8",
+ "symfony/css-selector": "~2.1",
+ "symfony/dom-crawler": "~2.1"
+ },
+ "suggest": {
+ "cache/filesystem-adapter": "For caching certs and tokens (using Google\\Client::setCache)"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/aliases.php"
+ ],
+ "psr-4": {
+ "Google\\": "src/"
+ },
+ "classmap": [
+ "src/aliases.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "description": "Client library for Google APIs",
+ "homepage": "http://developers.google.com/api-client-library/php",
+ "keywords": [
+ "google"
+ ],
+ "support": {
+ "issues": "https://github.com/googleapis/google-api-php-client/issues",
+ "source": "https://github.com/googleapis/google-api-php-client/tree/v2.16.0"
+ },
+ "time": "2024-04-24T00:59:47+00:00"
+ },
+ {
+ "name": "google/apiclient-services",
+ "version": "v0.358.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/googleapis/google-api-php-client-services.git",
+ "reference": "a6daf60ee25cb45b6e3dbd04b62d1df39a609fbd"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/googleapis/google-api-php-client-services/zipball/a6daf60ee25cb45b6e3dbd04b62d1df39a609fbd",
+ "reference": "a6daf60ee25cb45b6e3dbd04b62d1df39a609fbd",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^8.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.6"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "autoload.php"
+ ],
+ "psr-4": {
+ "Google\\Service\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "description": "Client library for Google APIs",
+ "homepage": "http://developers.google.com/api-client-library/php",
+ "keywords": [
+ "google"
+ ],
+ "support": {
+ "issues": "https://github.com/googleapis/google-api-php-client-services/issues",
+ "source": "https://github.com/googleapis/google-api-php-client-services/tree/v0.358.0"
+ },
+ "time": "2024-06-03T01:02:16+00:00"
+ },
+ {
+ "name": "google/auth",
+ "version": "v1.40.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/googleapis/google-auth-library-php.git",
+ "reference": "bff9f2d01677e71a98394b5ac981b99523df5178"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/googleapis/google-auth-library-php/zipball/bff9f2d01677e71a98394b5ac981b99523df5178",
+ "reference": "bff9f2d01677e71a98394b5ac981b99523df5178",
+ "shasum": ""
+ },
+ "require": {
+ "firebase/php-jwt": "^6.0",
+ "guzzlehttp/guzzle": "^7.4.5",
+ "guzzlehttp/psr7": "^2.4.5",
+ "php": "^8.0",
+ "psr/cache": "^2.0||^3.0",
+ "psr/http-message": "^1.1||^2.0"
+ },
+ "require-dev": {
+ "guzzlehttp/promises": "^2.0",
+ "kelvinmo/simplejwt": "0.7.1",
+ "phpseclib/phpseclib": "^3.0.35",
+ "phpspec/prophecy-phpunit": "^2.1",
+ "phpunit/phpunit": "^9.6",
+ "sebastian/comparator": ">=1.2.3",
+ "squizlabs/php_codesniffer": "^3.5",
+ "symfony/process": "^6.0||^7.0",
+ "webmozart/assert": "^1.11"
+ },
+ "suggest": {
+ "phpseclib/phpseclib": "May be used in place of OpenSSL for signing strings or for token management. Please require version ^2."
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Google\\Auth\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "description": "Google Auth Library for PHP",
+ "homepage": "http://github.com/google/google-auth-library-php",
+ "keywords": [
+ "Authentication",
+ "google",
+ "oauth2"
+ ],
+ "support": {
+ "docs": "https://googleapis.github.io/google-auth-library-php/main/",
+ "issues": "https://github.com/googleapis/google-auth-library-php/issues",
+ "source": "https://github.com/googleapis/google-auth-library-php/tree/v1.40.0"
+ },
+ "time": "2024-05-31T19:16:15+00:00"
+ },
{
"name": "graham-campbell/manager",
"version": "v4.7.0",
@@ -2464,6 +3540,64 @@
],
"time": "2023-08-24T08:08:34+00:00"
},
+ {
+ "name": "kelunik/certificate",
+ "version": "v1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/kelunik/certificate.git",
+ "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/kelunik/certificate/zipball/7e00d498c264d5eb4f78c69f41c8bd6719c0199e",
+ "reference": "7e00d498c264d5eb4f78c69f41c8bd6719c0199e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-openssl": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "amphp/php-cs-fixer-config": "^2",
+ "phpunit/phpunit": "^6 | 7 | ^8 | ^9"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Kelunik\\Certificate\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Access certificate details and transform between different formats.",
+ "keywords": [
+ "DER",
+ "certificate",
+ "certificates",
+ "openssl",
+ "pem",
+ "x509"
+ ],
+ "support": {
+ "issues": "https://github.com/kelunik/certificate/issues",
+ "source": "https://github.com/kelunik/certificate/tree/v1.1.3"
+ },
+ "time": "2023-02-03T21:26:53+00:00"
+ },
{
"name": "laravel/cashier",
"version": "v13.17.0",
@@ -4008,6 +5142,180 @@
},
"time": "2022-04-15T14:02:14+00:00"
},
+ {
+ "name": "league/uri",
+ "version": "7.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/uri.git",
+ "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/uri/zipball/bedb6e55eff0c933668addaa7efa1e1f2c417cc4",
+ "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4",
+ "shasum": ""
+ },
+ "require": {
+ "league/uri-interfaces": "^7.3",
+ "php": "^8.1"
+ },
+ "conflict": {
+ "league/uri-schemes": "^1.0"
+ },
+ "suggest": {
+ "ext-bcmath": "to improve IPV4 host parsing",
+ "ext-fileinfo": "to create Data URI from file contennts",
+ "ext-gmp": "to improve IPV4 host parsing",
+ "ext-intl": "to handle IDN host with the best performance",
+ "jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain",
+ "league/uri-components": "Needed to easily manipulate URI objects components",
+ "php-64bit": "to improve IPV4 host parsing",
+ "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://nyamsprod.com"
+ }
+ ],
+ "description": "URI manipulation library",
+ "homepage": "https://uri.thephpleague.com",
+ "keywords": [
+ "data-uri",
+ "file-uri",
+ "ftp",
+ "hostname",
+ "http",
+ "https",
+ "middleware",
+ "parse_str",
+ "parse_url",
+ "psr-7",
+ "query-string",
+ "querystring",
+ "rfc3986",
+ "rfc3987",
+ "rfc6570",
+ "uri",
+ "uri-template",
+ "url",
+ "ws"
+ ],
+ "support": {
+ "docs": "https://uri.thephpleague.com",
+ "forum": "https://thephpleague.slack.com",
+ "issues": "https://github.com/thephpleague/uri-src/issues",
+ "source": "https://github.com/thephpleague/uri/tree/7.4.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-23T07:42:40+00:00"
+ },
+ {
+ "name": "league/uri-interfaces",
+ "version": "7.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/uri-interfaces.git",
+ "reference": "8d43ef5c841032c87e2de015972c06f3865ef718"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/8d43ef5c841032c87e2de015972c06f3865ef718",
+ "reference": "8d43ef5c841032c87e2de015972c06f3865ef718",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "php": "^8.1",
+ "psr/http-factory": "^1",
+ "psr/http-message": "^1.1 || ^2.0"
+ },
+ "suggest": {
+ "ext-bcmath": "to improve IPV4 host parsing",
+ "ext-gmp": "to improve IPV4 host parsing",
+ "ext-intl": "to handle IDN host with the best performance",
+ "php-64bit": "to improve IPV4 host parsing",
+ "symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "7.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Uri\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ignace Nyamagana Butera",
+ "email": "nyamsprod@gmail.com",
+ "homepage": "https://nyamsprod.com"
+ }
+ ],
+ "description": "Common interfaces and classes for URI representation and interaction",
+ "homepage": "https://uri.thephpleague.com",
+ "keywords": [
+ "data-uri",
+ "file-uri",
+ "ftp",
+ "hostname",
+ "http",
+ "https",
+ "parse_str",
+ "parse_url",
+ "psr-7",
+ "query-string",
+ "querystring",
+ "rfc3986",
+ "rfc3987",
+ "rfc6570",
+ "uri",
+ "url",
+ "ws"
+ ],
+ "support": {
+ "docs": "https://uri.thephpleague.com",
+ "forum": "https://thephpleague.slack.com",
+ "issues": "https://github.com/thephpleague/uri-src/issues",
+ "source": "https://github.com/thephpleague/uri-interfaces/tree/7.4.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sponsors/nyamsprod",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-23T07:42:40+00:00"
+ },
{
"name": "maatwebsite/excel",
"version": "3.1.55",
@@ -5926,6 +7234,117 @@
},
"time": "2024-03-15T13:55:21+00:00"
},
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
+ },
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.8.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "153ae662783729388a584b4361f2545e4d841e3c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c",
+ "reference": "153ae662783729388a584b4361f2545e4d841e3c",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/deprecations": "^1.0",
+ "php": "^7.3 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0",
+ "phpstan/phpdoc-parser": "^1.13"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan": "^1.8",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpunit/phpunit": "^9.5",
+ "rector/rector": "^0.13.9",
+ "vimeo/psalm": "^4.25"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2"
+ },
+ "time": "2024-02-23T11:10:43+00:00"
+ },
{
"name": "phpoffice/phpspreadsheet",
"version": "1.29.0",
@@ -6216,6 +7635,53 @@
],
"time": "2024-03-03T02:14:58+00:00"
},
+ {
+ "name": "phpstan/phpdoc-parser",
+ "version": "1.29.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpdoc-parser.git",
+ "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4",
+ "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/annotations": "^2.0",
+ "nikic/php-parser": "^4.15",
+ "php-parallel-lint/php-parallel-lint": "^1.2",
+ "phpstan/extension-installer": "^1.0",
+ "phpstan/phpstan": "^1.5",
+ "phpstan/phpstan-phpunit": "^1.1",
+ "phpstan/phpstan-strict-rules": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "symfony/process": "^5.2"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PHPStan\\PhpDocParser\\": [
+ "src/"
+ ]
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPDoc parser with support for nullable, intersection and generic types",
+ "support": {
+ "issues": "https://github.com/phpstan/phpdoc-parser/issues",
+ "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1"
+ },
+ "time": "2024-05-31T08:52:43+00:00"
+ },
{
"name": "propaganistas/laravel-disposable-email",
"version": "2.2.18",
@@ -7056,6 +8522,78 @@
],
"time": "2024-04-27T21:32:50+00:00"
},
+ {
+ "name": "revolt/event-loop",
+ "version": "v1.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/revoltphp/event-loop.git",
+ "reference": "25de49af7223ba039f64da4ae9a28ec2d10d0254"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/25de49af7223ba039f64da4ae9a28ec2d10d0254",
+ "reference": "25de49af7223ba039f64da4ae9a28ec2d10d0254",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.1"
+ },
+ "require-dev": {
+ "ext-json": "*",
+ "jetbrains/phpstorm-stubs": "^2019.3",
+ "phpunit/phpunit": "^9",
+ "psalm/phar": "^5.15"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Revolt\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Piotrowski",
+ "email": "aaron@trowski.com"
+ },
+ {
+ "name": "Cees-Jan Kiewiet",
+ "email": "ceesjank@gmail.com"
+ },
+ {
+ "name": "Christian Lück",
+ "email": "christian@clue.engineering"
+ },
+ {
+ "name": "Niklas Keller",
+ "email": "me@kelunik.com"
+ }
+ ],
+ "description": "Rock-solid event loop for concurrent PHP applications.",
+ "keywords": [
+ "async",
+ "asynchronous",
+ "concurrency",
+ "event",
+ "event-loop",
+ "non-blocking",
+ "scheduler"
+ ],
+ "support": {
+ "issues": "https://github.com/revoltphp/event-loop/issues",
+ "source": "https://github.com/revoltphp/event-loop/tree/v1.0.6"
+ },
+ "time": "2023-11-30T05:34:44+00:00"
+ },
{
"name": "riverline/multipart-parser",
"version": "2.1.2",
@@ -7680,6 +9218,91 @@
},
"time": "2024-05-16T08:48:33+00:00"
},
+ {
+ "name": "spatie/laravel-data",
+ "version": "3.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/laravel-data.git",
+ "reference": "d44e04839407bc32b029be59ba80090a5f720e91"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/laravel-data/zipball/d44e04839407bc32b029be59ba80090a5f720e91",
+ "reference": "d44e04839407bc32b029be59ba80090a5f720e91",
+ "shasum": ""
+ },
+ "require": {
+ "illuminate/contracts": "^9.30|^10.0|^11.0",
+ "php": "^8.1",
+ "phpdocumentor/type-resolver": "^1.5",
+ "spatie/laravel-package-tools": "^1.9.0",
+ "spatie/php-structure-discoverer": "^2.0"
+ },
+ "require-dev": {
+ "fakerphp/faker": "^1.14",
+ "friendsofphp/php-cs-fixer": "^3.0",
+ "inertiajs/inertia-laravel": "^0.6.3",
+ "mockery/mockery": "^1.6",
+ "nesbot/carbon": "^2.63",
+ "nette/php-generator": "^3.5",
+ "nunomaduro/larastan": "^2.0",
+ "orchestra/testbench": "^7.6|^8.0",
+ "pestphp/pest": "^1.22",
+ "pestphp/pest-plugin-laravel": "^1.3",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/extension-installer": "^1.1",
+ "phpunit/phpunit": "^9.3",
+ "spatie/invade": "^1.0",
+ "spatie/laravel-typescript-transformer": "^2.1.6",
+ "spatie/pest-plugin-snapshots": "^1.1",
+ "spatie/phpunit-snapshot-assertions": "^4.2",
+ "spatie/test-time": "^1.2"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\LaravelData\\LaravelDataServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\LaravelData\\": "src",
+ "Spatie\\LaravelData\\Database\\Factories\\": "database/factories"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ruben Van Assche",
+ "email": "ruben@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Create unified resources and data transfer objects",
+ "homepage": "https://github.com/spatie/laravel-data",
+ "keywords": [
+ "laravel",
+ "laravel-data",
+ "spatie"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/laravel-data/issues",
+ "source": "https://github.com/spatie/laravel-data/tree/3.12.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/spatie",
+ "type": "github"
+ }
+ ],
+ "time": "2024-04-24T09:27:45+00:00"
+ },
{
"name": "spatie/laravel-package-tools",
"version": "1.16.4",
@@ -7873,6 +9496,86 @@
],
"time": "2024-02-26T08:33:29+00:00"
},
+ {
+ "name": "spatie/php-structure-discoverer",
+ "version": "2.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/spatie/php-structure-discoverer.git",
+ "reference": "d2e4e6cba962ce2a058ea415a123dd84b37765ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/spatie/php-structure-discoverer/zipball/d2e4e6cba962ce2a058ea415a123dd84b37765ac",
+ "reference": "d2e4e6cba962ce2a058ea415a123dd84b37765ac",
+ "shasum": ""
+ },
+ "require": {
+ "amphp/amp": "^v3.0",
+ "amphp/parallel": "^2.2",
+ "illuminate/collections": "^9.30|^10.0",
+ "php": "^8.1",
+ "spatie/laravel-package-tools": "^1.4.3",
+ "symfony/finder": "^6.0|^7.0"
+ },
+ "require-dev": {
+ "illuminate/console": "^9.30|^10.0",
+ "laravel/pint": "^1.0",
+ "nunomaduro/collision": "^6.0",
+ "nunomaduro/larastan": "^2.0.1",
+ "orchestra/testbench": "^7.0|^8.0",
+ "pestphp/pest": "^1.21",
+ "pestphp/pest-plugin-laravel": "^1.1",
+ "phpstan/extension-installer": "^1.1",
+ "phpstan/phpstan-deprecation-rules": "^1.0",
+ "phpstan/phpstan-phpunit": "^1.0",
+ "phpunit/phpunit": "^9.5",
+ "spatie/laravel-ray": "^1.26"
+ },
+ "type": "library",
+ "extra": {
+ "laravel": {
+ "providers": [
+ "Spatie\\StructureDiscoverer\\StructureDiscovererServiceProvider"
+ ]
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Spatie\\StructureDiscoverer\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ruben Van Assche",
+ "email": "ruben@spatie.be",
+ "role": "Developer"
+ }
+ ],
+ "description": "Automatically discover structures within your PHP application",
+ "homepage": "https://github.com/spatie/php-structure-discoverer",
+ "keywords": [
+ "discover",
+ "laravel",
+ "php",
+ "php-structure-discoverer"
+ ],
+ "support": {
+ "issues": "https://github.com/spatie/php-structure-discoverer/issues",
+ "source": "https://github.com/spatie/php-structure-discoverer/tree/2.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/LaravelAutoDiscoverer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-01-08T21:01:26+00:00"
+ },
{
"name": "spatie/robots-txt",
"version": "2.2.0",
@@ -13113,164 +14816,6 @@
},
"time": "2023-10-20T12:21:20+00:00"
},
- {
- "name": "phpdocumentor/reflection-common",
- "version": "2.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
- "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
- "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
- "shasum": ""
- },
- "require": {
- "php": "^7.2 || ^8.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-2.x": "2.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jaap van Otterdijk",
- "email": "opensource@ijaap.nl"
- }
- ],
- "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
- "homepage": "http://www.phpdoc.org",
- "keywords": [
- "FQSEN",
- "phpDocumentor",
- "phpdoc",
- "reflection",
- "static analysis"
- ],
- "support": {
- "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
- "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
- },
- "time": "2020-06-27T09:03:43+00:00"
- },
- {
- "name": "phpdocumentor/type-resolver",
- "version": "1.8.2",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "153ae662783729388a584b4361f2545e4d841e3c"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c",
- "reference": "153ae662783729388a584b4361f2545e4d841e3c",
- "shasum": ""
- },
- "require": {
- "doctrine/deprecations": "^1.0",
- "php": "^7.3 || ^8.0",
- "phpdocumentor/reflection-common": "^2.0",
- "phpstan/phpdoc-parser": "^1.13"
- },
- "require-dev": {
- "ext-tokenizer": "*",
- "phpbench/phpbench": "^1.2",
- "phpstan/extension-installer": "^1.1",
- "phpstan/phpstan": "^1.8",
- "phpstan/phpstan-phpunit": "^1.1",
- "phpunit/phpunit": "^9.5",
- "rector/rector": "^0.13.9",
- "vimeo/psalm": "^4.25"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-1.x": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
- "support": {
- "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
- "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2"
- },
- "time": "2024-02-23T11:10:43+00:00"
- },
- {
- "name": "phpstan/phpdoc-parser",
- "version": "1.29.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpstan/phpdoc-parser.git",
- "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/fcaefacf2d5c417e928405b71b400d4ce10daaf4",
- "reference": "fcaefacf2d5c417e928405b71b400d4ce10daaf4",
- "shasum": ""
- },
- "require": {
- "php": "^7.2 || ^8.0"
- },
- "require-dev": {
- "doctrine/annotations": "^2.0",
- "nikic/php-parser": "^4.15",
- "php-parallel-lint/php-parallel-lint": "^1.2",
- "phpstan/extension-installer": "^1.0",
- "phpstan/phpstan": "^1.5",
- "phpstan/phpstan-phpunit": "^1.1",
- "phpstan/phpstan-strict-rules": "^1.0",
- "phpunit/phpunit": "^9.5",
- "symfony/process": "^5.2"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "PHPStan\\PhpDocParser\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "PHPDoc parser with support for nullable, intersection and generic types",
- "support": {
- "issues": "https://github.com/phpstan/phpdoc-parser/issues",
- "source": "https://github.com/phpstan/phpdoc-parser/tree/1.29.1"
- },
- "time": "2024-05-31T08:52:43+00:00"
- },
{
"name": "phpstan/phpstan",
"version": "1.11.3",
diff --git a/config/services.php b/config/services.php
index d3e04a3f..f056acb6 100644
--- a/config/services.php
+++ b/config/services.php
@@ -64,4 +64,10 @@ return [
],
'crisp_website_id' => env('CRISP_WEBSITE_ID'),
+
+ 'google' => [
+ 'client_id' => env('GOOGLE_CLIENT_ID'),
+ 'client_secret' => env('GOOGLE_CLIENT_SECRET'),
+ 'redirect' => env('GOOGLE_REDIRECT_URL'),
+ ]
];
diff --git a/database/factories/Integration/FormIntegrationFactory.php b/database/factories/Integration/FormIntegrationFactory.php
new file mode 100644
index 00000000..e5b31162
--- /dev/null
+++ b/database/factories/Integration/FormIntegrationFactory.php
@@ -0,0 +1,21 @@
+ 'i_test',
+ 'status' => 'active',
+ 'logic' => [],
+ 'data' => [],
+ ];
+ }
+}
diff --git a/database/factories/OAuthProviderFactory.php b/database/factories/OAuthProviderFactory.php
new file mode 100644
index 00000000..f73f34c7
--- /dev/null
+++ b/database/factories/OAuthProviderFactory.php
@@ -0,0 +1,23 @@
+ 'google',
+ 'provider_user_id' => 'u_test',
+ 'email' => 'user@example.com',
+ 'name' => 'user',
+ 'access_token' => 'ac_test',
+ 'refresh_token' => 're_test',
+ ];
+ }
+}
diff --git a/database/migrations/2024_05_10_063347_add_user_data_to_oauth_providers_table.php b/database/migrations/2024_05_10_063347_add_user_data_to_oauth_providers_table.php
new file mode 100644
index 00000000..698db9f4
--- /dev/null
+++ b/database/migrations/2024_05_10_063347_add_user_data_to_oauth_providers_table.php
@@ -0,0 +1,33 @@
+string('email')->after('provider_user_id')->nullable();
+ $table->string('name')->after('email')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('oauth_providers', function (Blueprint $table) {
+ $table->dropColumn('email');
+ $table->dropColumn('name');
+ });
+ }
+};
diff --git a/database/migrations/2024_05_29_002132_add_expires_at_to_oauth_providers_table.php b/database/migrations/2024_05_29_002132_add_expires_at_to_oauth_providers_table.php
new file mode 100644
index 00000000..b7cf4393
--- /dev/null
+++ b/database/migrations/2024_05_29_002132_add_expires_at_to_oauth_providers_table.php
@@ -0,0 +1,31 @@
+timestamp('token_expires_at')->nullable()->after('refresh_token');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::table('oauth_providers', function (Blueprint $table) {
+ $table->dropColumn('token_expires_at');
+ });
+ }
+};
diff --git a/routes/api.php b/routes/api.php
index 9cdb8e8a..636a6d70 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -15,6 +15,7 @@ use App\Http\Controllers\Forms\Integration\FormIntegrationsEventController;
use App\Http\Controllers\Forms\Integration\FormZapierWebhookController;
use App\Http\Controllers\Forms\PublicFormController;
use App\Http\Controllers\Forms\RecordController;
+use App\Http\Controllers\OAuth\OAuthProviderController;
use App\Http\Controllers\Settings\PasswordController;
use App\Http\Controllers\Settings\ProfileController;
use App\Http\Controllers\SubscriptionController;
@@ -42,8 +43,16 @@ Route::group(['middleware' => 'auth:api'], function () {
Route::get('user', [UserController::class, 'current'])->name('user.current');
Route::delete('user', [UserController::class, 'deleteAccount']);
- Route::patch('settings/profile', [ProfileController::class, 'update']);
- Route::patch('settings/password', [PasswordController::class, 'update']);
+ Route::prefix('/settings')->name('settings.')->group(function () {
+ Route::patch('/profile', [ProfileController::class, 'update']);
+ Route::patch('/password', [PasswordController::class, 'update']);
+
+ Route::prefix('/providers')->name('providers.')->group(function () {
+ Route::post('/connect/{service}', [OAuthProviderController::class, 'connect'])->name('connect');
+ Route::post('/callback/{service}', [OAuthProviderController::class, 'handleRedirect'])->name('callback');
+ Route::delete('/{provider}', [OAuthProviderController::class, 'destroy'])->name('destroy');
+ });
+ });
Route::prefix('subscription')->name('subscription.')->group(function () {
Route::put('/update-customer-details', [SubscriptionController::class, 'updateStripeDetails'])->name('update-stripe-details');
@@ -55,6 +64,8 @@ Route::group(['middleware' => 'auth:api'], function () {
});
Route::prefix('open')->name('open.')->group(function () {
+ Route::get('/providers', [OAuthProviderController::class, 'index'])->name('index');
+
Route::get('/forms', [FormController::class, 'indexAll'])->name('forms.index-all');
Route::get('/forms/{slug}', [FormController::class, 'show'])->name('forms.show');
diff --git a/tests/Feature/Integrations/Google/Sheets/SpreadsheetManagerTest.php b/tests/Feature/Integrations/Google/Sheets/SpreadsheetManagerTest.php
new file mode 100644
index 00000000..42b0c973
--- /dev/null
+++ b/tests/Feature/Integrations/Google/Sheets/SpreadsheetManagerTest.php
@@ -0,0 +1,164 @@
+createUser();
+
+ /** @var \App\Models\Workspace $workspace */
+ $workspace = $this->createUserWorkspace($user);
+
+ /** @var \App\Models\Forms $form */
+ $form = $this->createForm($user, $workspace);
+
+ /** @var \App\Models\OAuthProvider $provider */
+ $provider = OAuthProvider::factory()
+ ->for($user)
+ ->create();
+
+ /** @var FormIntegration $integration */
+ $integration = FormIntegration::factory()
+ ->for($form)
+ ->for($provider, 'provider')
+ ->create([
+ 'data' => new SpreadsheetData(
+ url: 'https://google.com',
+ spreadsheet_id: 'sp_test',
+ columns: []
+ )
+ ]);
+
+ $google = new Google($integration);
+
+ $manager = new SpreadsheetManager($google, $integration);
+
+ $columns = $manager->buildColumns();
+
+ assertCount(14, $columns);
+
+ foreach($columns as $key => $column) {
+ assertEquals($form->properties[$key]['id'], $column['id']);
+ assertEquals($form->properties[$key]['name'], $column['name']);
+ }
+});
+
+test('update columns', function () {
+ /** @var \App\Models\User $user */
+ $user = $this->createUser();
+
+ /** @var \App\Models\Workspace $workspace */
+ $workspace = $this->createUserWorkspace($user);
+
+ /** @var \App\Models\Forms $form */
+ $form = $this->createForm($user, $workspace);
+
+ $form->update([
+ 'properties' => [
+ ['id' => '000', 'name' => 'First'],
+ ['id' => '001', 'name' => 'Second'],
+ ]
+ ]);
+
+ /** @var \App\Models\OAuthProvider $provider */
+ $provider = OAuthProvider::factory()
+ ->for($user)
+ ->create();
+
+ /** @var FormIntegration $integration */
+ $integration = FormIntegration::factory()
+ ->for($form)
+ ->for($provider, 'provider')
+ ->create([
+ 'data' => new SpreadsheetData(
+ url: 'https://google.com',
+ spreadsheet_id: 'sp_test',
+ columns: [
+ ['id' => '000', 'name' => 'First'],
+ ['id' => '001', 'name' => 'Second'],
+ ]
+ )
+ ]);
+
+
+ $google = new Google($integration);
+ $manager = new SpreadsheetManager($google, $integration);
+
+ $manager->buildColumns();
+
+ $form->update([
+ 'properties' => [
+ ['id' => '000', 'name' => 'First name'],
+ ['id' => '002', 'name' => 'Email'],
+ ]
+ ]);
+
+ $integration->refresh();
+ $columns = $manager->buildColumns();
+
+ assertCount(3, $columns);
+ assertEquals('First name', $columns[0]['name']);
+ assertEquals('Second', $columns[1]['name']);
+ assertEquals('Email', $columns[2]['name']);
+});
+
+test('build row', function () {
+ /** @var \App\Models\User $user */
+ $user = $this->createUser();
+
+ /** @var \App\Models\Workspace $workspace */
+ $workspace = $this->createUserWorkspace($user);
+
+ /** @var \App\Models\Forms $form */
+ $form = $this->createForm($user, $workspace);
+
+ $form->update([
+ 'properties' => [
+ ['id' => '000', 'name' => 'First', 'type' => 'text'],
+ ['id' => '001', 'name' => 'Second', 'type' => 'text'],
+ ['id' => '002', 'name' => 'Third', 'type' => 'text'],
+ ]
+ ]);
+
+ /** @var \App\Models\OAuthProvider $provider */
+ $provider = OAuthProvider::factory()
+ ->for($user)
+ ->create();
+
+ /** @var FormIntegration $integration */
+ $integration = FormIntegration::factory()
+ ->for($form)
+ ->for($provider, 'provider')
+ ->create([
+ 'data' => new SpreadsheetData(
+ url: 'https://google.com',
+ spreadsheet_id: 'sp_test',
+ columns: [
+ ['id' => '000', 'name' => 'First'],
+ ['id' => '001', 'name' => 'Second'],
+ ['id' => '002', 'name' => 'Third'],
+ ]
+ )
+ ]);
+
+
+ $google = new Google($integration);
+ $manager = new SpreadsheetManager($google, $integration);
+
+ $submission = [
+ '002' => 'Third value',
+ '000' => 'First value',
+ ];
+
+ $row = $manager->buildRow($submission);
+
+ assertSame(['First value', '', 'Third value'], $row);
+});